Compare commits

..

24 Commits

Author SHA1 Message Date
402f05b8ef Replace constant buffer access on shader with new Load instruction (#4646) 2023-05-20 16:19:26 -03:00
fb27042e01 Limit compute storage buffer size (#5028) 2023-05-20 16:15:07 +00:00
69a9de33d3 SPIR-V: Only allow implicit LOD sampling on fragment (#5026) 2023-05-20 15:52:26 +02:00
bba51c2eeb Fix macOS Update Script (#5014)
* Update updater.sh

* Better script

* Revert "Better script"

This reverts commit 9bf6be863892e5e10c2f2dba45f1d0a60daca688.
2023-05-19 21:20:01 +02:00
fc26189fe1 Eliminate redundant multiplications by gl_FragCoord.w on the shader (#4578)
* Eliminate redundant multiplications by gl_FragCoord.w on the shader

* Shader cache version bump
2023-05-19 11:52:31 -03:00
a40c90e7dd nuget: bump DynamicData from 7.13.5 to 7.13.8 (#5001)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.13.5 to 7.13.8.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/7.13.5...7.13.8)

---
updated-dependencies:
- dependency-name: DynamicData
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-19 06:52:44 +02:00
f864a49014 Fix Vulkan blit-like operations swizzle (#5003) 2023-05-18 18:16:03 -03:00
ecbf303266 GPU: Avoid using garbage size for non-cb0 storage buffers (#4999)
* GPU: Avoid using garbage size for non-cb0 storage buffers

In the depths area, Tears of the Kingdom uses a global memory access with address on constant buffer slot 6. This isn't standard and thus doesn't actually have a size 8 bytes after it, so we were reading back a garbage size that ended up very large (at least in version 1.1.0), and would synchronize a lot of data per frame.

This PR makes storage buffers created from addresses outside constant buffer slot 0 get their size as the number of bytes remaining in the GPU mapping starting at the given virtual address. This should bound the buffer to a reasonable size, and ideally stop it crossing into other memory.

* Limit max size

* Add TODO

* Feedback
2023-05-18 08:56:34 +02:00
b3bf05356b ava: Fix crash when extracting sections from NCA with no data section (#5002)
Tested against Omega Strickers.
2023-05-17 19:27:49 +00:00
cb4b58052f Start GPU performance counter at 0 instead of host GPU value (#4992)
* Start performance counter at 0 instead of host perf counter value

* whitespace

* init _firstTimestamp in constructer per feedback

* change comment

* punctuation

* address feedback

* revise comment
2023-05-17 15:38:59 -03:00
f8cdd5f484 macos: Fix relaunch with updater when no arguments were provided to the emulator (#4987)
The updater still seems to be erroring when replacing installed folder under normal operations.
2023-05-17 19:02:15 +02:00
22202be394 [GUI] Fix always hide cursor mode not hiding the cursor until it was moved (#4927)
* gtk: Add missing isMouseInClient check for hide-cursor

* ava: Add missing events and default isCursorInRenderer to true

This is necessary because we don't receive a initial PointerEnter event for some reason.
2023-05-14 16:34:31 +02:00
17ba217940 Vulkan: Device map buffers written more than flushed (#4911) 2023-05-13 15:15:05 +02:00
aae4595bdb Add timeout of 35 minutes to workflow jobs (#4928) 2023-05-13 13:24:43 +02:00
880fd3cfcb audio: sdl2: Do not report 5.1 if the device doesn't support it (#4908)
* amadeus: adjust VirtualDevice channel configuration reporting with HardwareDevice

* audio: sdl2: Do not report 5.1 if device doesn't support it

SDL2 5.1 to Stereo conversion is terrible and make everything sound
quiet.

Let's not expose 5.1 if not truly supported by the device.
2023-05-13 07:15:16 +00:00
f679f25e08 Set OpenGL PixelPackBuffer to 0 when done (#4921) 2023-05-13 00:59:46 +00:00
c2709b3bdd macOS CI Adjustments (#4910)
* Fix macOS build name in CI

Fixes updater

* Update build.yml

Don't publish x86 Mac builds

* Naming nitpick

* Berry changes

* Use the same prefix for PR and release build archives

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-05-13 00:53:52 +02:00
2b6e81deea Ava: Fix wrong MouseButton (#4900) 2023-05-12 21:39:42 +02:00
7271f1b18e Bump shader cache codegen version
That was missing from #4892
2023-05-12 18:53:14 +02:00
5fda543f84 Vulkan: Partially workaround MoltenVK InvalidResource error (#4880)
* Add MVK stage flags workaround

* Actually do the workaround

* Remove GS on VS stuff

* Address feedback
2023-05-11 22:06:15 -03:00
95c06de4c1 GPU: Remove swizzle undefined matching and rework depth aliasing (#4896)
* GPU: Remove swizzle undefined matching and rework depth aliasing

@gdkchan pointed out that UI textures in TOTK seemed to be setting their texture swizzle incorrectly (texture was RGB but was sampling A, swizzle for A was wrong), so I determined that SwizzleComponentMatches was the problem and set on eliminating it. This PR combines existing work to select the most recently modified texture (now used when selecting which aliased texture to use) with some additional changes to remove the swizzle check and support aliased view creation.

The original observation (#1538) was that we wanted to match depth textures for the purposes of aliasing with color textures, but they often had different swizzle from what was sampled (as it's generally the identity swizzle once rendered). At the time, I decided to allow swizzles to match if only the defined components matched, which fixed the issue in all known cases but could easily be broken by a game _expecting_ a given swizzle, such as a 1/0 value on a component.

This error case could also occur in textures that don't even depth alias, such as R11G11B10, as the rule was created to generally apply to all cases.

The solution is now to fail this exact match test, and allow the search for an R32 texture to create a swizzled view of a D32 texture (and other such cases). This allows the creation of a view that mismatches the requested format, which wasn't present before and was the reason for the swizzle matching approach.

The exact match and view creation rules now follow the same rules over what textures to select when there are multiple options (such as a "perfect" match and an "aliased" match at the same time). It now selects the most recently modified texture, which is done with a new sequence number in the GpuContext (because we don't have enough of these).

Reportedly fixes UI having weird coloured backgrounds in TOTK. This also fixes an issue in MK8D where returning from a race resulted in the character selection cubemaps being broken. May work around issues introduced by the "short texture cache" PR due to modification ordering, though they won't be truly fixed.

Should allow (#4365) to avoid copies in more cases. Need to test that.

I tested a bunch of games #1538 originally affected and they seem to be fine. This change affects all games so it would be good to get some wide testing on it.

* Address feedback 1, fix an issue

* Workaround: Do not allow copies for format alias.

These should be removed when D32<->R32 copy dependencies become legal
2023-05-11 21:30:47 -03:00
49c63ea077 Fix the restart after an update. (#4869)
* Fix the restart after an update.

* Fix the updater for the Ava UI too.

* Fixing up the code after some change requests.
Removed a line of code that was accidentally left in.

* Fix restarting on Linux Avalonia.

* Fix issues with escaped arguments.
2023-05-12 02:14:29 +02:00
531da8a1c0 Changed LastPlayed field from string to nullable DateTime (#4861)
* Changed LastPlayed field from string to nullable DateTime

Added ApplicationData.LastPlayedString property
Added NullableDateTimeConverter for the DateTime->string conversion in Avalonia

* Added migration from string-based last_played to DateTime-based last_played_utc

* Updated comment style

* Added MarkupExtension to NullableDateTimeConverter and changed its usage

Cleaned up leftover usings

* Missed one comment
2023-05-12 01:56:37 +02:00
5cbdfbc7a4 amadeus: Allow 5.1 sink output (#4894)
* amadeus: Allow 5.1 sink output

Also add a simple Stereo to 5.1 change for device sink.

Tested against NES - Nintendo Switch Online that output stereo on the
audio renderer.

* Remove outdated comment
2023-05-12 00:19:19 +02:00
89 changed files with 1488 additions and 873 deletions

View File

@ -27,6 +27,7 @@ jobs:
build:
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
timeout-minutes: 35
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
@ -38,7 +39,7 @@ jobs:
RELEASE_ZIP_OS_NAME: linux_x64
- os: macOS-latest
OS_NAME: MacOS x64
OS_NAME: macOS x64
DOTNET_RUNTIME_IDENTIFIER: osx-x64
RELEASE_ZIP_OS_NAME: osx_x64
@ -68,15 +69,15 @@ jobs:
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Publish Ryujinx.Ava
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Set executable bit
run: |
@ -90,25 +91,26 @@ jobs:
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v3
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Upload Ryujinx.Ava artifact
uses: actions/upload-artifact@v3
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_ava
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
build_macos:
name: MacOS universal (${{ matrix.configuration }})
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
timeout-minutes: 35
strategy:
matrix:
configuration: [ Debug, Release ]

View File

@ -12,6 +12,7 @@ concurrency: flatpak-release
jobs:
release:
timeout-minutes: 35
runs-on: ubuntu-latest
env:

View File

@ -7,6 +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
steps:
- uses: actions/github-script@v6
with:
@ -65,4 +66,4 @@ jobs:
} else {
core.info(`Creating a comment`);
await github.rest.issues.createComment({repo, owner, issue_number, body});
}
}

View File

@ -46,6 +46,7 @@ jobs:
release:
name: Release ${{ matrix.OS_NAME }}
runs-on: ${{ matrix.os }}
timeout-minutes: 35
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
@ -143,13 +144,14 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
global-json-file: global.json
- name: Setup LLVM 14
run: |
wget https://apt.llvm.org/llvm.sh
@ -205,4 +207,4 @@ jobs:
needs: release
with:
ryujinx_version: "1.1.${{ github.run_number }}"
secrets: inherit
secrets: inherit

View File

@ -13,7 +13,7 @@
<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.5" />
<PackageVersion Include="DynamicData" Version="7.13.8" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />

View File

@ -22,9 +22,9 @@ EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ];
then
RELEASE_TAR_FILE_NAME=Ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
else
RELEASE_TAR_FILE_NAME=Ryujinx-$VERSION-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
fi
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"

View File

@ -25,15 +25,20 @@ error_handler() {
exit 1
}
trap 'error_handler ${LINENO}' ERR
# 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
# Now replace and reopen.
rm -rf "$INSTALL_DIRECTORY"
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
open -a "$INSTALL_DIRECTORY" --args "$APP_ARGUMENTS"
if [ "$#" -le 3 ]; then
open -a "$INSTALL_DIRECTORY"
else
open -a "$INSTALL_DIRECTORY" --args "$APP_ARGUMENTS"
fi

View File

@ -5,6 +5,7 @@ using Ryujinx.Memory;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
@ -18,6 +19,13 @@ namespace Ryujinx.Audio.Backends.SDL2
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
private bool _supportSurroundConfiguration;
// TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec.
[DllImport("SDL2")]
private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture);
public SDL2HardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
@ -25,6 +33,20 @@ namespace Ryujinx.Audio.Backends.SDL2
_sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
SDL2Driver.Instance.Initialize();
int res = SDL_GetDefaultAudioInfo(IntPtr.Zero, out var spec, 0);
if (res != 0)
{
Logger.Error?.Print(LogClass.Application,
$"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
_supportSurroundConfiguration = true;
}
else
{
_supportSurroundConfiguration = spec.channels == 6;
}
}
public static bool IsSupported => IsSupportedInternal();
@ -164,6 +186,11 @@ namespace Ryujinx.Audio.Backends.SDL2
public bool SupportsChannelCount(uint channelCount)
{
if (channelCount == 6)
{
return _supportSurroundConfiguration;
}
return true;
}

View File

@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Device
/// <param name="name">The name of the <see cref="VirtualDevice"/>.</param>
/// <param name="channelCount">The count of channels supported by the <see cref="VirtualDevice"/>.</param>
/// <param name="isExternalOutput">Indicate if the <see cref="VirtualDevice"/> is provided by an external interface.</param>
private VirtualDevice(string name, uint channelCount, bool isExternalOutput)
public VirtualDevice(string name, uint channelCount, bool isExternalOutput)
{
Name = name;
ChannelCount = channelCount;

View File

@ -1,3 +1,4 @@
using Ryujinx.Audio.Integration;
using System.Collections.Generic;
namespace Ryujinx.Audio.Renderer.Device
@ -22,7 +23,23 @@ namespace Ryujinx.Audio.Renderer.Device
/// The current active <see cref="VirtualDevice"/>.
/// </summary>
// TODO: make this configurable
public VirtualDevice ActiveDevice = VirtualDevice.Devices[2];
public VirtualDevice ActiveDevice { get; }
public VirtualDeviceSessionRegistry(IHardwareDeviceDriver driver)
{
uint channelCount;
if (driver.GetRealDeviceDriver().SupportsChannelCount(6))
{
channelCount = 6;
}
else
{
channelCount = 2;
}
ActiveDevice = new VirtualDevice("AudioTvOutput", channelCount, false);
}
/// <summary>
/// Get the associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.

View File

@ -65,9 +65,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
{
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
// TODO: Before enabling this, we need up-mixing from stereo to 5.1.
// uint channelCount = GetHardwareChannelCount(deviceDriver);
uint channelCount = 2;
uint channelCount = GetHardwareChannelCount(deviceDriver);
for (int i = 0; i < OutputDevices.Length; i++)
{

View File

@ -67,7 +67,19 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
const int sampleCount = Constants.TargetSampleCount;
short[] outputBuffer = new short[bufferCount * sampleCount];
uint inputCount;
// In case of upmixing to 5.1, we allocate the right amount.
if (bufferCount != channelCount && channelCount == 6)
{
inputCount = (uint)channelCount;
}
else
{
inputCount = bufferCount;
}
short[] outputBuffer = new short[inputCount * sampleCount];
for (int i = 0; i < bufferCount; i++)
{
@ -79,7 +91,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
}
device.AppendBuffer(outputBuffer, InputCount);
device.AppendBuffer(outputBuffer, inputCount);
}
else
{

View File

@ -86,7 +86,7 @@ namespace Ryujinx.Ava
private KeyboardHotkeyState _prevHotkeyState;
private long _lastCursorMoveTime;
private bool _isCursorInRenderer;
private bool _isCursorInRenderer = true;
private bool _isStopped;
private bool _isActive;
@ -160,7 +160,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed;
_topLevel.PointerMoved += TopLevel_PointerMoved;
_topLevel.PointerMoved += TopLevel_PointerEnterOrMoved;
_topLevel.PointerEnter += TopLevel_PointerEnterOrMoved;
_topLevel.PointerLeave += TopLevel_PointerLeave;
if (OperatingSystem.IsWindows())
{
@ -183,7 +185,7 @@ namespace Ryujinx.Ava
_gpuCancellationTokenSource = new CancellationTokenSource();
}
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
private void TopLevel_PointerEnterOrMoved(object sender, PointerEventArgs e)
{
if (sender is MainWindow window)
{
@ -201,6 +203,12 @@ namespace Ryujinx.Ava
}
}
}
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
{
_isCursorInRenderer = false;
}
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
@ -446,7 +454,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
_topLevel.PointerMoved -= TopLevel_PointerMoved;
_topLevel.PointerMoved -= TopLevel_PointerEnterOrMoved;
_topLevel.PointerEnter -= TopLevel_PointerEnterOrMoved;
_topLevel.PointerLeave -= TopLevel_PointerLeave;
_gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
@ -671,7 +681,7 @@ namespace Ryujinx.Ava
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
appMetadata.LastPlayed = DateTime.UtcNow;
});
return true;

View File

@ -193,7 +193,7 @@ namespace Ryujinx.Ava.Common
if (nca.Header.ContentType == NcaContentType.Program)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
patchNca = nca;
}

View File

@ -70,11 +70,14 @@ namespace Ryujinx.Ava.Input
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
{
int button = (int)args.InitialPressMouseButton - 1;
if (PressedButtons.Count() >= button)
if (args.InitialPressMouseButton != Avalonia.Input.MouseButton.None)
{
PressedButtons[button] = false;
int button = (int)args.InitialPressMouseButton;
if (PressedButtons.Count() >= button)
{
PressedButtons[button] = false;
}
}
}

View File

@ -295,14 +295,7 @@ namespace Ryujinx.Modules
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string ryuName = Path.GetFileName(Environment.ProcessPath);
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
string executablePath = Path.Combine(executableDirectory, ryuName);
if (!Path.Exists(executablePath))
{
executablePath = Path.Combine(executableDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
}
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
@ -310,13 +303,42 @@ namespace Ryujinx.Modules
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(UpdateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Process.GetCurrentProcess().Id.ToString();
string currentPid = Environment.ProcessId.ToString();
executablePath = "/bin/bash";
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
}
else
{
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath);
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (!Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory
};
foreach (string argument in CommandLineState.Arguments)
{
processStart.ArgumentList.Add(argument);
}
Process.Start(processStart);
}
Process.Start(executablePath, arguments);
Environment.Exit(0);
}
}

View File

@ -129,7 +129,7 @@
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding LastPlayed}"
Text="{Binding LastPlayed, Converter={helpers:NullableDateTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
<TextBlock

View File

@ -0,0 +1,38 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
internal class NullableDateTimeConverter : MarkupExtension, IValueConverter
{
private static readonly NullableDateTimeConverter _instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return LocaleManager.Instance[LocaleKeys.Never];
}
if (value is DateTime dateTime)
{
return dateTime.ToLocalTime().ToString(culture);
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _instance;
}
}
}

View File

@ -1,4 +1,3 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
@ -14,20 +13,20 @@ namespace Ryujinx.Ava.UI.Models.Generic
public int Compare(ApplicationData x, ApplicationData y)
{
string aValue = x.LastPlayed;
string bValue = y.LastPlayed;
var aValue = x.LastPlayed;
var bValue = y.LastPlayed;
if (aValue == LocaleManager.Instance[LocaleKeys.Never])
if (!aValue.HasValue)
{
aValue = DateTime.UnixEpoch.ToString();
aValue = DateTime.UnixEpoch;
}
if (bValue == LocaleManager.Instance[LocaleKeys.Never])
if (!bValue.HasValue)
{
bValue = DateTime.UnixEpoch.ToString();
bValue = DateTime.UnixEpoch;
}
return (IsAscending ? 1 : -1) * DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
return (IsAscending ? 1 : -1) * DateTime.Compare(bValue.Value, aValue.Value);
}
}
}

View File

@ -1524,10 +1524,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
if (appMetadata.LastPlayed.HasValue)
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
});

View File

@ -40,22 +40,6 @@ namespace Ryujinx.Graphics.Gpu
/// </summary>
public const int TotalTransformFeedbackBuffers = 4;
/// <summary>
/// Maximum number of textures on a single shader stage.
/// </summary>
/// <remarks>
/// The maximum number of textures is API limited, the hardware supports an unlimited amount.
/// </remarks>
public const int TotalTextures = 32;
/// <summary>
/// Maximum number of images on a single shader stage.
/// </summary>
/// <remarks>
/// The maximum number of images is API limited, the hardware supports an unlimited amount.
/// </remarks>
public const int TotalImages = 8;
/// <summary>
/// Maximum number of render target color buffers.
/// </summary>
@ -100,5 +84,15 @@ namespace Ryujinx.Graphics.Gpu
/// Expected byte alignment for storage buffers
/// </summary>
public const int StorageAlignment = 16;
/// <summary>
/// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses.
/// </summary>
public const int DriverReservedUniformBuffer = 0;
/// <summary>
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary>
public const ulong MaxUnknownStorageSize = 0x100000;
}
}

View File

@ -162,7 +162,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
uint size;
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
{
// Only trust the SbDescriptor size if it comes from slot 0.
size = (uint)sbDescriptor.Size;
}
else
{
// TODO: Use full mapped size and somehow speed up buffer sync.
size = (uint)_channel.MemoryManager.GetMappedSize(sbDescriptor.PackAddress(), Constants.MaxUnknownStorageSize);
}
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
}
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)

View File

@ -356,7 +356,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
_channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
uint size;
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
{
// Only trust the SbDescriptor size if it comes from slot 0.
size = (uint)sbDescriptor.Size;
}
else
{
// TODO: Use full mapped size and somehow speed up buffer sync.
size = (uint)_channel.MemoryManager.GetMappedSize(sbDescriptor.PackAddress(), Constants.MaxUnknownStorageSize);
}
_channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
}
}
}

View File

@ -98,6 +98,9 @@ namespace Ryujinx.Graphics.Gpu
private Thread _gpuThread;
private bool _pendingSync;
private long _modifiedSequence;
private ulong _firstTimestamp;
/// <summary>
/// Creates a new instance of the GPU emulation context.
/// </summary>
@ -121,6 +124,8 @@ namespace Ryujinx.Graphics.Gpu
DeferredActions = new Queue<Action>();
PhysicalMemoryRegistry = new ConcurrentDictionary<ulong, PhysicalMemory>();
_firstTimestamp = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
}
/// <summary>
@ -200,13 +205,23 @@ namespace Ryujinx.Graphics.Gpu
return divided * NsToTicksFractionNumerator + errorBias;
}
/// <summary>
/// Gets a sequence number for resource modification ordering. This increments on each call.
/// </summary>
/// <returns>A sequence number for resource modification ordering</returns>
public long GetModifiedSequence()
{
return _modifiedSequence++;
}
/// <summary>
/// Gets the value of the GPU timer.
/// </summary>
/// <returns>The current GPU timestamp</returns>
public ulong GetTimestamp()
{
ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
// Guest timestamp will start at 0, instead of host value.
ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds) - _firstTimestamp;
if (GraphicsConfig.FastGpuTime)
{

View File

@ -1170,6 +1170,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="caps">Host GPU capabilities</param>
/// <param name="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
/// <param name="flags">Texture search flags</param>
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
public TextureViewCompatibility IsViewCompatible(
TextureInfo info,
@ -1178,11 +1179,12 @@ namespace Ryujinx.Graphics.Gpu.Image
int layerSize,
Capabilities caps,
out int firstLayer,
out int firstLevel)
out int firstLevel,
TextureSearchFlags flags = TextureSearchFlags.None)
{
TextureViewCompatibility result = TextureViewCompatibility.Full;
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps, flags));
if (result != TextureViewCompatibility.Incompatible)
{
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps));

View File

@ -569,7 +569,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture texture = null;
TextureMatchQuality bestQuality = TextureMatchQuality.NoMatch;
long bestSequence = 0;
for (int index = 0; index < sameAddressOverlapsCount; index++)
{
@ -601,17 +601,12 @@ namespace Ryujinx.Graphics.Gpu.Image
continue;
}
}
}
if (matchQuality == TextureMatchQuality.Perfect)
{
texture = overlap;
break;
}
else if (matchQuality > bestQuality)
{
texture = overlap;
bestQuality = matchQuality;
if (texture == null || overlap.Group.ModifiedSequence - bestSequence > 0)
{
texture = overlap;
bestSequence = overlap.Group.ModifiedSequence;
}
}
}
@ -664,6 +659,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int fullyCompatible = 0;
// Evaluate compatibility of overlaps, add temporary references
int preferredOverlap = -1;
for (int index = 0; index < overlapsCount; index++)
{
@ -675,17 +671,26 @@ namespace Ryujinx.Graphics.Gpu.Image
sizeInfo.LayerSize,
_context.Capabilities,
out int firstLayer,
out int firstLevel);
out int firstLevel,
flags);
if (overlapCompatibility == TextureViewCompatibility.Full)
if (overlapCompatibility >= TextureViewCompatibility.FormatAlias)
{
if (overlap.IsView)
{
overlapCompatibility = TextureViewCompatibility.CopyOnly;
overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ?
TextureViewCompatibility.Incompatible :
TextureViewCompatibility.CopyOnly;
}
else
{
fullyCompatible++;
if (preferredOverlap == -1 || overlap.Group.ModifiedSequence - bestSequence > 0)
{
preferredOverlap = index;
bestSequence = overlap.Group.ModifiedSequence;
}
}
}
@ -695,37 +700,50 @@ namespace Ryujinx.Graphics.Gpu.Image
// Search through the overlaps to find a compatible view and establish any copy dependencies.
for (int index = 0; index < overlapsCount; index++)
if (preferredOverlap != -1)
{
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
Texture overlap = _textureOverlaps[preferredOverlap];
OverlapInfo oInfo = _overlapInfo[preferredOverlap];
if (oInfo.Compatibility == TextureViewCompatibility.Full)
bool aliased = oInfo.Compatibility == TextureViewCompatibility.FormatAlias;
if (!isSamplerTexture)
{
if (!isSamplerTexture)
{
// If this is not a sampler texture, the size might be different from the requested size,
// so we need to make sure the texture information has the correct size for this base texture,
// before creating the view.
info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel);
}
// If this is not a sampler texture, the size might be different from the requested size,
// so we need to make sure the texture information has the correct size for this base texture,
// before creating the view.
texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
texture.SynchronizeMemory();
break;
info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel, aliased);
}
else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0)
else if (aliased)
{
// Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
// The format must be changed to match the parent.
info = info.CreateInfoWithFormat(overlap.Info.FormatInfo);
}
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode);
texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
texture.SynchronizeMemory();
}
else
{
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
texture.InitializeGroup(true, true, new List<TextureIncompatibleOverlap>());
texture.InitializeData(false, false);
if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0)
{
// Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
overlap.SynchronizeMemory();
overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
break;
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode);
texture.InitializeGroup(true, true, new List<TextureIncompatibleOverlap>());
texture.InitializeData(false, false);
overlap.SynchronizeMemory();
overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
break;
}
}
}
@ -740,7 +758,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias)
{
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
{

View File

@ -291,22 +291,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The minimum compatibility level of two provided view compatibility results</returns>
public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second)
{
if (first == TextureViewCompatibility.Incompatible || second == TextureViewCompatibility.Incompatible)
{
return TextureViewCompatibility.Incompatible;
}
else if (first == TextureViewCompatibility.LayoutIncompatible || second == TextureViewCompatibility.LayoutIncompatible)
{
return TextureViewCompatibility.LayoutIncompatible;
}
else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly)
{
return TextureViewCompatibility.CopyOnly;
}
else
{
return TextureViewCompatibility.Full;
}
return (TextureViewCompatibility)Math.Min((int)first, (int)second);
}
/// <summary>
@ -628,15 +613,21 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view</param>
/// <param name="caps">Host GPU capabilities</param>
/// <param name="flags">Texture search flags</param>
/// <returns>The view compatibility level of the texture formats</returns>
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps, TextureSearchFlags flags)
{
FormatInfo lhsFormat = lhs.FormatInfo;
FormatInfo rhsFormat = rhs.FormatInfo;
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
{
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch
{
TextureMatchQuality.Perfect => TextureViewCompatibility.Full,
TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias,
_ => TextureViewCompatibility.Incompatible
};
}
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
@ -754,49 +745,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
}
/// <summary>
/// Checks if a swizzle component in two textures functionally match, taking into account if the components are defined.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="swizzleLhs">Swizzle component for the first texture</param>
/// <param name="swizzleRhs">Swizzle component for the second texture</param>
/// <param name="component">Component index, starting at 0 for red</param>
/// <returns>True if the swizzle components functionally match, false othersize</returns>
private static bool SwizzleComponentMatches(TextureInfo lhs, TextureInfo rhs, SwizzleComponent swizzleLhs, SwizzleComponent swizzleRhs, int component)
{
int lhsComponents = lhs.FormatInfo.Components;
int rhsComponents = rhs.FormatInfo.Components;
if (lhsComponents == 4 && rhsComponents == 4)
{
return swizzleLhs == swizzleRhs;
}
// Swizzles after the number of components a format defines are "undefined".
// We allow these to not be equal under certain circumstances.
// This can only happen when there are less than 4 components in a format.
// It tends to happen when float depth textures are sampled.
bool lhsDefined = (swizzleLhs - SwizzleComponent.Red) < lhsComponents;
bool rhsDefined = (swizzleRhs - SwizzleComponent.Red) < rhsComponents;
if (lhsDefined == rhsDefined)
{
// If both are undefined, return true. Otherwise just check if they're equal.
return lhsDefined ? swizzleLhs == swizzleRhs : true;
}
else
{
SwizzleComponent defined = lhsDefined ? swizzleLhs : swizzleRhs;
SwizzleComponent undefined = lhsDefined ? swizzleRhs : swizzleLhs;
// Undefined swizzle can be matched by a forced value (0, 1), exact equality, or expected value.
// For example, R___ matches R001, RGBA but not RBGA.
return defined == undefined || defined < SwizzleComponent.Red || defined == SwizzleComponent.Red + component;
}
}
/// <summary>
/// Checks if the texture shader sampling parameters of two texture informations match.
/// </summary>
@ -806,10 +754,10 @@ namespace Ryujinx.Graphics.Gpu.Image
public static bool SamplerParamsMatches(TextureInfo lhs, TextureInfo rhs)
{
return lhs.DepthStencilMode == rhs.DepthStencilMode &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleR, rhs.SwizzleR, 0) &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleG, rhs.SwizzleG, 1) &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleB, rhs.SwizzleB, 2) &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleA, rhs.SwizzleA, 3);
lhs.SwizzleR == rhs.SwizzleR &&
lhs.SwizzleG == rhs.SwizzleG &&
lhs.SwizzleB == rhs.SwizzleB &&
lhs.SwizzleA == rhs.SwizzleA;
}
/// <summary>

View File

@ -68,6 +68,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public bool HasIncompatibleOverlaps => _incompatibleOverlaps.Count > 0;
/// <summary>
/// Number indicating the order this texture group was modified relative to others.
/// </summary>
public long ModifiedSequence { get; private set; }
private readonly GpuContext _context;
private readonly PhysicalMemory _physicalMemory;
@ -664,6 +669,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texture">The texture that has been modified</param>
public void SignalModified(Texture texture)
{
ModifiedSequence = _context.GetModifiedSequence();
ClearIncompatibleOverlaps(texture);
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
@ -684,6 +691,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="bound">True if this texture is being bound, false if unbound</param>
public void SignalModifying(Texture texture, bool bound)
{
ModifiedSequence = _context.GetModifiedSequence();
ClearIncompatibleOverlaps(texture);
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>

View File

@ -300,8 +300,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="parent">The parent texture</param>
/// <param name="firstLevel">The first level of the texture view</param>
/// <param name="parentFormat">True if the parent format should be inherited</param>
/// <returns>The adjusted texture information with the new size</returns>
public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel)
public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel, bool parentFormat)
{
// When the texture is used as view of another texture, we must
// ensure that the sizes are valid, otherwise data uploads would fail
@ -370,7 +371,36 @@ namespace Ryujinx.Graphics.Gpu.Image
GobBlocksInZ,
GobBlocksInTileX,
target,
FormatInfo,
parentFormat ? parent.Info.FormatInfo : FormatInfo,
DepthStencilMode,
SwizzleR,
SwizzleG,
SwizzleB,
SwizzleA);
}
/// <summary>
/// Creates texture information for a given format and this information.
/// </summary>
/// <param name="formatInfo">Format for the new texture info</param>
/// <returns>New info with the specified format</returns>
public TextureInfo CreateInfoWithFormat(FormatInfo formatInfo)
{
return new TextureInfo(
GpuAddress,
Width,
Height,
DepthOrLayers,
Levels,
SamplesInX,
SamplesInY,
Stride,
IsLinear,
GobBlocksInY,
GobBlocksInZ,
GobBlocksInTileX,
Target,
formatInfo,
DepthStencilMode,
SwizzleR,
SwizzleG,

View File

@ -9,6 +9,7 @@
Incompatible = 0,
LayoutIncompatible,
CopyOnly,
FormatAlias,
Full
}
}

View File

@ -637,6 +637,33 @@ namespace Ryujinx.Graphics.Gpu.Memory
return UnpackPaFromPte(pte) + (va & PageMask);
}
/// <summary>
/// Translates a GPU virtual address and returns the number of bytes that are mapped after it.
/// </summary>
/// <param name="va">GPU virtual address to be translated</param>
/// <param name="maxSize">Maximum size in bytes to scan</param>
/// <returns>Number of bytes, 0 if unmapped</returns>
public ulong GetMappedSize(ulong va, ulong maxSize)
{
if (!ValidateAddress(va))
{
return 0;
}
ulong startVa = va;
ulong endVa = va + maxSize;
ulong pte = GetPte(va);
while (pte != PteUnmapped && va < endVa)
{
va += PageSize - (va & PageMask);
pte = GetPte(va);
}
return Math.Min(maxSize, va - startVa);
}
/// <summary>
/// Gets the kind of a given memory page.
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.

View File

@ -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 = 4821;
private const uint CodeGenVersion = 4646;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
namespace Ryujinx.Graphics.Gpu.Shader
{
@ -16,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly ResourceCounts _resourceCounts;
private readonly int _stageIndex;
private readonly int[] _constantBufferBindings;
/// <summary>
/// Creates a new GPU accessor.
/// </summary>
@ -25,6 +28,12 @@ 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)
@ -36,7 +45,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
else
{
return _resourceCounts.UniformBuffersCount++;
int binding = _constantBufferBindings[index];
if (binding < 0)
{
binding = _resourceCounts.UniformBuffersCount++;
_constantBufferBindings[index] = binding;
}
return binding;
}
}

View File

@ -306,6 +306,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
int offset = WriteToPbo2D(range.Offset, layer, level);
Debug.Assert(offset == 0);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
}
public void WriteToPbo(int offset, bool forceBgra)

View File

@ -93,7 +93,7 @@ namespace Ryujinx.Graphics.OpenGL
oldView.Dispose();
}
}
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);

View File

@ -3,6 +3,7 @@ using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
@ -102,13 +103,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
var cBufferDescriptors = context.Config.GetConstantBufferDescriptors();
if (cBufferDescriptors.Length != 0)
{
DeclareUniforms(context, cBufferDescriptors);
context.AppendLine();
}
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
var sBufferDescriptors = context.Config.GetStorageBufferDescriptors();
if (sBufferDescriptors.Length != 0)
@ -265,18 +260,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
scaleElements++; // Also includes render target scale, for gl_FragCoord.
}
DeclareSupportUniformBlock(context, context.Config.Stage, scaleElements);
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0)
{
AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
context.AppendLine();
}
}
else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
{
DeclareSupportUniformBlock(context, context.Config.Stage, 0);
}
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
@ -389,36 +378,38 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
};
}
private static void DeclareUniforms(CodeGenContext context, BufferDescriptor[] descriptors)
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]";
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
foreach (BufferDefinition buffer in buffers)
{
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
ubName += "_" + DefaultNames.UniformNamePrefix;
string blockName = $"{ubName}_{DefaultNames.BlockSuffix}";
context.AppendLine($"layout (binding = {context.Config.FirstConstantBufferBinding}, std140) uniform {blockName}");
context.EnterScope();
context.AppendLine("vec4 " + DefaultNames.DataName + ubSize + ";");
context.LeaveScope($" {ubName}[{NumberFormatter.FormatInt(descriptors.Max(x => x.Slot) + 1)}];");
}
else
{
foreach (var descriptor in descriptors)
string layout = buffer.Layout switch
{
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
BufferLayout.Std140 => "std140",
_ => "std430"
};
ubName += "_" + DefaultNames.UniformNamePrefix + descriptor.Slot;
context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) uniform _{buffer.Name}");
context.EnterScope();
context.AppendLine($"layout (binding = {descriptor.Binding}, std140) uniform {ubName}");
context.EnterScope();
context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, descriptor.Slot, false) + ubSize + ";");
context.LeaveScope(";");
foreach (StructureField field in buffer.Type.Fields)
{
if (field.Type.HasFlag(AggregateType.Array))
{
string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
}
else
{
string typeName = GetVarTypeName(context, field.Type);
context.AppendLine($"{typeName} {field.Name};");
}
}
context.LeaveScope($" {buffer.Name};");
context.AppendLine();
}
}
@ -759,39 +750,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
{
bool needsSupportBlock = stage == ShaderStage.Fragment ||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
if (!needsSupportBlock && scaleElements == 0)
{
return;
}
context.AppendLine($"layout (binding = 0, std140) uniform {DefaultNames.SupportBlockName}");
context.EnterScope();
switch (stage)
{
case ShaderStage.Fragment:
case ShaderStage.Vertex:
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
break;
case ShaderStage.Compute:
context.AppendLine($"uint s_reserved[{SupportBuffer.ComputeRenderScaleOffset / SupportBuffer.FieldSize}];");
break;
}
context.AppendLine($"float {DefaultNames.SupportBlockRenderScaleName}[{SupportBuffer.RenderScaleMaxCount}];");
context.LeaveScope(";");
context.AppendLine();
}
private static void AppendHelperFunction(CodeGenContext context, string filename)
{
string code = EmbeddedResources.ReadAllText(filename);

View File

@ -15,18 +15,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string DataName = "data";
public const string SupportBlockName = "support_block";
public const string SupportBlockAlphaTestName = "s_alpha_test";
public const string SupportBlockIsBgraName = "s_is_bgra";
public const string SupportBlockViewportInverse = "s_viewport_inverse";
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
public const string SupportBlockRenderScaleName = "s_render_scale";
public const string BlockSuffix = "block";
public const string UniformNamePrefix = "c";
public const string UniformNameSuffix = "data";
public const string LocalMemoryName = "local_mem";
public const string SharedMemoryName = "shared_mem";

View File

@ -1,6 +1,6 @@
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
{
float scale = s_render_scale[samplerIndex];
float scale = support_buffer.s_render_scale[1 + samplerIndex];
if (scale == 1.0)
{
return inputVec;
@ -10,7 +10,7 @@
int Helper_TextureSizeUnscale(int size, int samplerIndex)
{
float scale = s_render_scale[samplerIndex];
float scale = support_buffer.s_render_scale[1 + samplerIndex];
if (scale == 1.0)
{
return size;

View File

@ -1,6 +1,6 @@
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
{
float scale = s_render_scale[1 + samplerIndex];
float scale = support_buffer.s_render_scale[1 + samplerIndex];
if (scale == 1.0)
{
return inputVec;
@ -17,7 +17,7 @@
int Helper_TextureSizeUnscale(int size, int samplerIndex)
{
float scale = abs(s_render_scale[1 + samplerIndex]);
float scale = abs(support_buffer.s_render_scale[1 + samplerIndex]);
if (scale == 1.0)
{
return size;

View File

@ -1,6 +1,6 @@
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
{
float scale = abs(s_render_scale[1 + samplerIndex + s_frag_scale_count]);
float scale = abs(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]);
if (scale == 1.0)
{
return inputVec;
@ -11,7 +11,7 @@
int Helper_TextureSizeUnscale(int size, int samplerIndex)
{
float scale = abs(s_render_scale[1 + samplerIndex + s_frag_scale_count]);
float scale = abs(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]);
if (scale == 1.0)
{
return size;

View File

@ -167,9 +167,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.Load:
return Load(context, operation);
case Instruction.LoadConstant:
return LoadConstant(context, operation);
case Instruction.LoadLocal:
return LoadLocal(context, operation);

View File

@ -83,7 +83,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.ImageAtomic, InstType.Special);
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
Add(Instruction.Load, InstType.Special);
Add(Instruction.LoadConstant, InstType.Special);
Add(Instruction.LoadLocal, InstType.Special);
Add(Instruction.LoadShared, InstType.Special);
Add(Instruction.LoadStorage, InstType.Special);

View File

@ -215,29 +215,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return GenerateLoadOrStore(context, operation, isStore: false);
}
public static string LoadConstant(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true);
var config = context.Config;
bool indexElement = !config.GpuAccessor.QueryHostHasVectorIndexingBug();
if (src1 is AstOperand operand && operand.Type == OperandType.Constant)
{
bool cbIndexable = config.UsedFeatures.HasFlag(Translation.FeatureFlags.CbIndexing);
return OperandManager.GetConstantBufferName(operand.Value, offsetExpr, config.Stage, cbIndexable, indexElement);
}
else
{
string slotExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
return OperandManager.GetConstantBufferName(slotExpr, offsetExpr, config.Stage, indexElement);
}
}
public static string LoadLocal(CodeGenContext context, AstOperation operation)
{
return LoadLocalOrShared(context, operation, DefaultNames.LocalMemoryName);
@ -809,9 +786,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string varName;
AggregateType varType;
int srcIndex = 0;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
switch (storageKind)
{
case StorageKind.ConstantBuffer:
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
int binding = bindingIndex.Value;
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[binding];
if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
StructureField field = buffer.Type.Fields[fieldIndex.Value];
varName = $"{buffer.Name}.{field.Name}";
varType = field.Type;
break;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
@ -864,40 +861,39 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
varName = $"gl_out[{expr}].{varName}";
}
}
int firstSrcIndex = srcIndex;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
// otherwise the compilation fails.
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
varName += "[gl_InvocationID]";
}
else
{
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
}
}
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
int firstSrcIndex = srcIndex;
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
// otherwise the compilation fails.
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
varName += "[gl_InvocationID]";
}
else
{
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
}
}
if (isStore)
{
varType &= AggregateType.ElementTypeMask;

View File

@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FragmentOutputIsBgra => (DefaultNames.SupportBlockIsBgraName, AggregateType.Array | AggregateType.Bool),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
@ -46,8 +45,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
IoVariable.SupportBlockRenderScale => (DefaultNames.SupportBlockRenderScaleName, AggregateType.Array | AggregateType.FP32),
IoVariable.SupportBlockViewInverse => (DefaultNames.SupportBlockViewportInverse, AggregateType.Vector2 | AggregateType.FP32),
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),

View File

@ -36,63 +36,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
OperandType.Argument => GetArgumentName(operand.Value),
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
OperandType.ConstantBuffer => GetConstantBufferName(operand, context.Config),
OperandType.LocalVariable => _locals[operand],
OperandType.Undefined => DefaultNames.UndefinedName,
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
};
}
private static string GetConstantBufferName(AstOperand operand, ShaderConfig config)
{
return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, config.Stage, config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing));
}
public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable)
{
return $"{GetUbName(stage, slot, cbIndexable)}[{offset >> 2}].{GetSwizzleMask(offset & 3)}";
}
private static string GetVec4Indexed(string vectorName, string indexExpr, bool indexElement)
{
if (indexElement)
{
return $"{vectorName}[{indexExpr}]";
}
string result = $"{vectorName}.x";
for (int i = 1; i < 4; i++)
{
result = $"(({indexExpr}) == {i}) ? ({vectorName}.{GetSwizzleMask(i)}) : ({result})";
}
return $"({result})";
}
public static string GetConstantBufferName(int slot, string offsetExpr, ShaderStage stage, bool cbIndexable, bool indexElement)
{
return GetVec4Indexed(GetUbName(stage, slot, cbIndexable) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
}
public static string GetConstantBufferName(string slotExpr, string offsetExpr, ShaderStage stage, bool indexElement)
{
return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
}
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
{
if (cbIndexable)
{
return GetUbName(stage, NumberFormatter.FormatInt(slot, AggregateType.S32));
}
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}{slot}_{DefaultNames.UniformNameSuffix}";
}
private static string GetUbName(ShaderStage stage, string slotExpr)
{
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}[{slotExpr}].{DefaultNames.DataName}";
}
public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
{
return GetSamplerName(stage, texOp.CbufSlot, texOp.Handle, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
@ -168,6 +117,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
switch (operation.StorageKind)
{
case StorageKind.ConstantBuffer:
if (!(operation.GetSource(0) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
if (!(operation.GetSource(1) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:

View File

@ -23,9 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public int InputVertices { get; }
public Dictionary<int, Instruction> UniformBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction SupportBuffer { get; set; }
public Instruction UniformBuffersArray { get; set; }
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction StorageBuffersArray { get; set; }
public Instruction LocalMemory { get; set; }
public Instruction SharedMemory { get; set; }
@ -217,7 +215,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
IrOperandType.Argument => GetArgument(type, operand),
IrOperandType.Constant => GetConstant(type, operand),
IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
IrOperandType.LocalVariable => GetLocal(type, operand),
IrOperandType.Undefined => GetUndefined(type),
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
@ -274,31 +271,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
};
}
public Instruction GetConstantBuffer(AggregateType type, AstOperand operand)
{
var i1 = Constant(TypeS32(), 0);
var i2 = Constant(TypeS32(), operand.CbufOffset >> 2);
var i3 = Constant(TypeU32(), operand.CbufOffset & 3);
Instruction elemPointer;
if (UniformBuffersArray != null)
{
var ubVariable = UniformBuffersArray;
var i0 = Constant(TypeS32(), operand.CbufSlot);
elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i0, i1, i2, i3);
}
else
{
var ubVariable = UniformBuffers[operand.CbufSlot];
elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i1, i2, i3);
}
return BitcastIfNeeded(type, AggregateType.FP32, Load(TypeFP32(), elemPointer));
}
public Instruction GetLocalPointer(AstOperand local)
{
return _locals[local];

View File

@ -98,8 +98,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
DeclareLocalMemory(context, localMemorySize);
}
DeclareSupportBuffer(context);
DeclareUniformBuffers(context, context.Config.GetConstantBufferDescriptors());
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
DeclareSamplers(context, context.Config.GetTextureDescriptors());
DeclareImages(context, context.Config.GetImageDescriptors());
@ -127,84 +126,63 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return variable;
}
private static void DeclareSupportBuffer(CodeGenContext context)
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
if (!context.Config.Stage.SupportsRenderScale() && !(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable()))
HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>();
foreach (BufferDefinition buffer in buffers)
{
return;
}
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
int alignmentMask = alignment - 1;
int offset = 0;
var isBgraArrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), SupportBuffer.FragmentIsBgraCount));
var viewportInverseVectorType = context.TypeVector(context.TypeFP32(), 4);
var renderScaleArrayType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), SupportBuffer.RenderScaleMaxCount));
SpvInstruction[] structFieldTypes = new SpvInstruction[buffer.Type.Fields.Length];
int[] structFieldOffsets = new int[buffer.Type.Fields.Length];
context.Decorate(isBgraArrayType, Decoration.ArrayStride, (LiteralInteger)SupportBuffer.FieldSize);
context.Decorate(renderScaleArrayType, Decoration.ArrayStride, (LiteralInteger)SupportBuffer.FieldSize);
for (int fieldIndex = 0; fieldIndex < buffer.Type.Fields.Length; fieldIndex++)
{
StructureField field = buffer.Type.Fields[fieldIndex];
int fieldSize = (field.Type.GetSizeInBytes() + alignmentMask) & ~alignmentMask;
var supportBufferStructType = context.TypeStruct(false, context.TypeU32(), isBgraArrayType, viewportInverseVectorType, context.TypeS32(), renderScaleArrayType);
structFieldTypes[fieldIndex] = context.GetType(field.Type, field.ArrayLength);
structFieldOffsets[fieldIndex] = offset;
context.MemberDecorate(supportBufferStructType, 0, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentAlphaTestOffset);
context.MemberDecorate(supportBufferStructType, 1, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentIsBgraOffset);
context.MemberDecorate(supportBufferStructType, 2, Decoration.Offset, (LiteralInteger)SupportBuffer.ViewportInverseOffset);
context.MemberDecorate(supportBufferStructType, 3, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentRenderScaleCountOffset);
context.MemberDecorate(supportBufferStructType, 4, Decoration.Offset, (LiteralInteger)SupportBuffer.GraphicsRenderScaleOffset);
context.Decorate(supportBufferStructType, Decoration.Block);
if (field.Type.HasFlag(AggregateType.Array))
{
// We can't decorate the type more than once.
if (decoratedTypes.Add(structFieldTypes[fieldIndex]))
{
context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize);
}
var supportBufferPointerType = context.TypePointer(StorageClass.Uniform, supportBufferStructType);
var supportBufferVariable = context.Variable(supportBufferPointerType, StorageClass.Uniform);
offset += fieldSize * field.ArrayLength;
}
else
{
offset += fieldSize;
}
}
context.Decorate(supportBufferVariable, Decoration.DescriptorSet, (LiteralInteger)0);
context.Decorate(supportBufferVariable, Decoration.Binding, (LiteralInteger)0);
var ubStructType = context.TypeStruct(false, structFieldTypes);
context.AddGlobalVariable(supportBufferVariable);
if (decoratedTypes.Add(ubStructType))
{
context.Decorate(ubStructType, Decoration.Block);
context.SupportBuffer = supportBufferVariable;
}
for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++)
{
context.MemberDecorate(ubStructType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]);
}
}
private static void DeclareUniformBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
{
if (descriptors.Length == 0)
{
return;
}
uint ubSize = Constants.ConstantBufferSize / 16;
var ubArrayType = context.TypeArray(context.TypeVector(context.TypeFP32(), 4), context.Constant(context.TypeU32(), ubSize), true);
context.Decorate(ubArrayType, Decoration.ArrayStride, (LiteralInteger)16);
var ubStructType = context.TypeStruct(true, ubArrayType);
context.Decorate(ubStructType, Decoration.Block);
context.MemberDecorate(ubStructType, 0, Decoration.Offset, (LiteralInteger)0);
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
{
int count = descriptors.Max(x => x.Slot) + 1;
var ubStructArrayType = context.TypeArray(ubStructType, context.Constant(context.TypeU32(), count));
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructArrayType);
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_u");
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)context.Config.FirstConstantBufferBinding);
context.Name(ubVariable, buffer.Name);
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)buffer.Binding);
context.AddGlobalVariable(ubVariable);
context.UniformBuffersArray = ubVariable;
}
else
{
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
foreach (var descriptor in descriptors)
{
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_c{descriptor.Slot}");
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
context.AddGlobalVariable(ubVariable);
context.UniformBuffers.Add(descriptor.Slot, ubVariable);
}
context.ConstantBuffers.Add(buffer.Binding, ubVariable);
}
}
@ -394,25 +372,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
foreach (var ioDefinition in info.IoDefinitions)
{
var ioVariable = ioDefinition.IoVariable;
// Those are actually from constant buffer, rather than being actual inputs or outputs,
// so we must ignore them here as they are declared as part of the support buffer.
// TODO: Delete this after we represent this properly on the IR (as a constant buffer rather than "input").
if (ioVariable == IoVariable.FragmentOutputIsBgra ||
ioVariable == IoVariable.SupportBlockRenderScale ||
ioVariable == IoVariable.SupportBlockViewInverse)
{
continue;
}
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
{
var ioVariable = ioDefinition.IoVariable;
if (ioVariable == IoVariable.UserDefined)
{
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
@ -429,6 +393,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
}
}

View File

@ -98,7 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.ImageStore, GenerateImageStore);
Add(Instruction.IsNan, GenerateIsNan);
Add(Instruction.Load, GenerateLoad);
Add(Instruction.LoadConstant, GenerateLoadConstant);
Add(Instruction.LoadLocal, GenerateLoadLocal);
Add(Instruction.LoadShared, GenerateLoadShared);
Add(Instruction.LoadStorage, GenerateLoadStorage);
@ -313,10 +312,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
for (int i = 0; i < args.Length; i++)
{
var operand = (AstOperand)operation.GetSource(i + 1);
var operand = operation.GetSource(i + 1);
if (i >= function.InArguments.Length)
{
args[i] = context.GetLocalPointer(operand);
args[i] = context.GetLocalPointer((AstOperand)operand);
}
else
{
@ -867,68 +867,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateLoadOrStore(context, operation, isStore: false);
}
private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
{
var src1 = operation.GetSource(0);
var src2 = context.Get(AggregateType.S32, operation.GetSource(1));
var i1 = context.Constant(context.TypeS32(), 0);
var i2 = context.ShiftRightArithmetic(context.TypeS32(), src2, context.Constant(context.TypeS32(), 2));
var i3 = context.BitwiseAnd(context.TypeS32(), src2, context.Constant(context.TypeS32(), 3));
SpvInstruction value = null;
if (context.Config.GpuAccessor.QueryHostHasVectorIndexingBug())
{
// Test for each component individually.
for (int i = 0; i < 4; i++)
{
var component = context.Constant(context.TypeS32(), i);
SpvInstruction elemPointer;
if (context.UniformBuffersArray != null)
{
var ubVariable = context.UniformBuffersArray;
var i0 = context.Get(AggregateType.S32, src1);
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2, component);
}
else
{
var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i1, i2, component);
}
SpvInstruction newValue = context.Load(context.TypeFP32(), elemPointer);
value = value != null ? context.Select(context.TypeFP32(), context.IEqual(context.TypeBool(), i3, component), newValue, value) : newValue;
}
}
else
{
SpvInstruction elemPointer;
if (context.UniformBuffersArray != null)
{
var ubVariable = context.UniformBuffersArray;
var i0 = context.Get(AggregateType.S32, src1);
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2, i3);
}
else
{
var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i1, i2, i3);
}
value = context.Load(context.TypeFP32(), elemPointer);
}
return new OperationResult(AggregateType.FP32, value);
}
private static OperationResult GenerateLoadLocal(CodeGenContext context, AstOperation operation)
{
return GenerateLoadLocalOrShared(context, operation, StorageClass.Private, context.LocalMemory);
@ -1623,7 +1561,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLodBias)
{
lodBias = Src(AggregateType.FP32);
lodBias = Src(AggregateType.FP32);
}
if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Config.Stage != ShaderStage.Fragment)
{
// Implicit LOD is only valid on fragment.
// Use the LOD bias as explicit LOD if available.
lod = lodBias ?? context.Constant(context.TypeFP32(), 0f);
lodBias = null;
hasLodBias = false;
hasLodLevel = true;
}
SpvInstruction compIdx = null;
@ -1978,12 +1928,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
StorageKind storageKind = operation.StorageKind;
SpvInstruction pointer;
StorageClass storageClass;
SpvInstruction baseObj;
AggregateType varType;
int srcIndex = 0;
switch (storageKind)
{
case StorageKind.ConstantBuffer:
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
if (!(operation.GetSource(srcIndex) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
storageClass = StorageClass.Uniform;
varType = field.Type & AggregateType.ElementTypeMask;
baseObj = context.ConstantBuffers[bindingIndex.Value];
break;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
@ -2026,33 +1996,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
varType = context.Config.GetFragmentOutputColorType(location);
}
else if (ioVariable == IoVariable.FragmentOutputIsBgra)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeU32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 1), elemIndex);
varType = AggregateType.U32;
break;
}
else if (ioVariable == IoVariable.SupportBlockRenderScale)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 4), elemIndex);
varType = AggregateType.FP32;
break;
}
else if (ioVariable == IoVariable.SupportBlockViewInverse)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 2), elemIndex);
varType = AggregateType.FP32;
break;
}
else
{
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
@ -2060,55 +2003,57 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
varType &= AggregateType.ElementTypeMask;
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
var ioDefinition = new IoDefinition(storageKind, ioVariable, location, component);
var dict = isPerPatch
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutput ? context.Outputs : context.Inputs);
SpvInstruction baseObj = dict[ioDefinition];
SpvInstruction e0, e1, e2;
switch (inputsCount)
{
case 0:
pointer = baseObj;
break;
case 1:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
break;
case 2:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
break;
case 3:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
break;
default:
var indexes = new SpvInstruction[inputsCount];
int index = 0;
for (; index < inputsCount; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
break;
}
baseObj = dict[ioDefinition];
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
SpvInstruction e0, e1, e2;
SpvInstruction pointer;
switch (inputsCount)
{
case 0:
pointer = baseObj;
break;
case 1:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
break;
case 2:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
break;
case 3:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
break;
default:
var indexes = new SpvInstruction[inputsCount];
int index = 0;
for (; index < inputsCount; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
break;
}
if (isStore)
{
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));

View File

@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (context.Config.Stage == ShaderStage.Vertex)
{
var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32());
var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 3));
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);
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1));
var scaleElemPointer = context.AccessChain(pointerType, context.SupportBuffer, fieldIndex, scaleIndex);
var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex);
var scale = context.Load(context.TypeFP32(), scaleElemPointer);
var ivector2Type = context.TypeVector(context.TypeS32(), 2);
@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (context.Config.Stage == ShaderStage.Vertex)
{
var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32());
var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 3));
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);
@ -209,7 +209,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1));
var scaleElemPointer = context.AccessChain(pointerType, context.SupportBuffer, fieldIndex, scaleIndex);
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));

View File

@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// FragCoord X/Y must be divided by the render target scale, if resolution scaling is active,
// because the shader code is not expecting scaled values.
res = context.FPDivide(res, context.Load(StorageKind.Input, IoVariable.SupportBlockRenderScale, null, Const(0)));
res = context.FPDivide(res, context.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0)));
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
{

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Numerics;
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@ -80,7 +81,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand addr = context.IAdd(srcA, Const(Imm16ToSInt(op.CbufOffset)));
Operand wordOffset = context.ShiftRightU32(addr, Const(2));
Operand bitOffset = GetBitOffset(context, addr);
for (int index = 0; index < count; index++)
{
@ -92,11 +92,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
Operand offset = context.IAdd(wordOffset, Const(index));
Operand value = context.LoadConstant(slot, offset);
Operand value = EmitLoadConstant(context, slot, offset);
if (isSmallInt)
{
value = ExtractSmallInt(context, (LsSize)op.LsSize, bitOffset, value);
value = ExtractSmallInt(context, (LsSize)op.LsSize, GetBitOffset(context, addr), value);
}
context.Copy(Register(dest), value);
@ -154,6 +154,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
EmitStore(context, MemoryRegion.Shared, op.LsSize, GetSrcReg(context, op.SrcA), op.Dest, Imm24ToSInt(op.Imm24));
}
private static Operand EmitLoadConstant(EmitterContext context, Operand slot, Operand offset)
{
Operand vecIndex = context.ShiftRightU32(offset, Const(2));
Operand elemIndex = context.BitwiseAnd(offset, Const(3));
if (slot.Type == OperandType.Constant)
{
int binding = context.Config.ResourceManager.GetConstantBufferBinding(slot.Value);
return context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
}
else
{
Operand value = Const(0);
uint cbUseMask = context.Config.GpuAccessor.QueryConstantBufferUse();
while (cbUseMask != 0)
{
int cbIndex = BitOperations.TrailingZeroCount(cbUseMask);
int binding = context.Config.ResourceManager.GetConstantBufferBinding(cbIndex);
Operand isCurrent = context.ICompareEqual(slot, Const(cbIndex));
Operand currentValue = context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
value = context.ConditionalSelect(isCurrent, currentValue, value);
cbUseMask &= ~(1u << cbIndex);
}
return value;
}
}
private static Operand EmitAtomicOp(
EmitterContext context,
StorageKind storageKind,

View File

@ -79,7 +79,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ImageAtomic,
IsNan,
Load,
LoadConstant,
LoadGlobal,
LoadLocal,
LoadShared,

View File

@ -15,7 +15,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
FragmentCoord,
FragmentOutputColor,
FragmentOutputDepth,
FragmentOutputIsBgra, // TODO: Remove and use constant buffer access.
FrontColorDiffuse,
FrontColorSpecular,
FrontFacing,
@ -34,8 +33,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
SubgroupLaneId,
SubgroupLeMask,
SubgroupLtMask,
SupportBlockViewInverse, // TODO: Remove and use constant buffer access.
SupportBlockRenderScale, // TODO: Remove and use constant buffer access.
TessellationCoord,
TessellationLevelInner,
TessellationLevelOuter,

View File

@ -15,9 +15,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public int Value { get; }
public int CbufSlot { get; }
public int CbufOffset { get; }
private AstOperand()
{
Defs = new HashSet<IAstNode>();
@ -29,16 +26,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public AstOperand(Operand operand) : this()
{
Type = operand.Type;
if (Type == OperandType.ConstantBuffer)
{
CbufSlot = operand.GetCbufSlot();
CbufOffset = operand.GetCbufOffset();
}
else
{
Value = operand.Value;
}
Value = operand.Value;
}
public AstOperand(OperandType type, int value = 0) : this()

View File

@ -0,0 +1,20 @@
namespace Ryujinx.Graphics.Shader.StructuredIr
{
readonly struct BufferDefinition
{
public BufferLayout Layout { get; }
public int Set { get; }
public int Binding { get; }
public string Name { get; }
public StructureType Type { get; }
public BufferDefinition(BufferLayout layout, int set, int binding, string name, StructureType type)
{
Layout = layout;
Set = set;
Binding = binding;
Name = name;
Type = type;
}
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Shader.StructuredIr
{
enum BufferLayout
{
Std140,
Std430
}
}

View File

@ -90,7 +90,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.ImageAtomic, AggregateType.S32);
Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar);
Add(Instruction.Load, AggregateType.FP32);
Add(Instruction.LoadConstant, AggregateType.FP32, AggregateType.S32, AggregateType.S32);
Add(Instruction.LoadGlobal, AggregateType.U32, AggregateType.S32, AggregateType.S32);
Add(Instruction.LoadLocal, AggregateType.U32, AggregateType.S32);
Add(Instruction.LoadShared, AggregateType.U32, AggregateType.S32);

View File

@ -24,7 +24,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
OperandType.Argument => AggregateType.S32,
OperandType.Constant => AggregateType.S32,
OperandType.ConstantBuffer => AggregateType.FP32,
OperandType.Undefined => AggregateType.S32,
_ => throw new ArgumentException($"Invalid operand type \"{type}\".")
};

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class ShaderProperties
{
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
public ShaderProperties()
{
_constantBuffers = new Dictionary<int, BufferDefinition>();
}
public void AddConstantBuffer(int binding, BufferDefinition definition)
{
_constantBuffers[binding] = definition;
}
}
}

View File

@ -0,0 +1,28 @@
using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
struct StructureField
{
public AggregateType Type { get; }
public string Name { get; }
public int ArrayLength { get; }
public StructureField(AggregateType type, string name, int arrayLength = 1)
{
Type = type;
Name = name;
ArrayLength = arrayLength;
}
}
class StructureType
{
public StructureField[] Fields { get; }
public StructureType(StructureField[] fields)
{
Fields = fields;
}
}
}

View File

@ -73,27 +73,34 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Instruction inst = operation.Inst;
StorageKind storageKind = operation.StorageKind;
if ((inst == Instruction.Load || inst == Instruction.Store) && storageKind.IsInputOrOutput())
if (inst == Instruction.Load || inst == Instruction.Store)
{
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
bool isOutput = storageKind.IsOutput();
bool perPatch = storageKind.IsPerPatch();
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (storageKind.IsInputOrOutput())
{
location = operation.GetSource(1).Value;
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
bool isOutput = storageKind.IsOutput();
bool perPatch = storageKind.IsPerPatch();
int location = 0;
int component = 0;
if (operation.SourcesCount > 2 &&
operation.GetSource(2).Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
component = operation.GetSource(2).Value;
}
}
location = operation.GetSource(1).Value;
context.Info.IoDefinitions.Add(new IoDefinition(storageKind, ioVariable, location, component));
if (operation.SourcesCount > 2 &&
operation.GetSource(2).Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
{
component = operation.GetSource(2).Value;
}
}
context.Info.IoDefinitions.Add(new IoDefinition(storageKind, ioVariable, location, component));
}
else if (storageKind == StorageKind.ConstantBuffer && operation.GetSource(0).Type == OperandType.Constant)
{
context.Config.ResourceManager.SetUsedConstantBufferBinding(operation.GetSource(0).Value);
}
}
bool vectorDest = IsVectorDestInst(inst);
@ -105,7 +112,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
for (int index = 0; index < operation.SourcesCount; index++)
{
sources[index] = context.GetOperand(operation.GetSource(index));
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
}
for (int index = 0; index < outDestsCount; index++)

View File

@ -298,6 +298,33 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return newTemp;
}
public IAstNode GetOperandOrCbLoad(Operand operand)
{
if (operand.Type == OperandType.ConstantBuffer)
{
int cbufSlot = operand.GetCbufSlot();
int cbufOffset = operand.GetCbufOffset();
int binding = Config.ResourceManager.GetConstantBufferBinding(cbufSlot);
int vecIndex = cbufOffset >> 2;
int elemIndex = cbufOffset & 3;
Config.ResourceManager.SetUsedConstantBufferBinding(binding);
IAstNode[] sources = new IAstNode[]
{
new AstOperand(OperandType.Constant, binding),
new AstOperand(OperandType.Constant, 0),
new AstOperand(OperandType.Constant, vecIndex),
new AstOperand(OperandType.Constant, elemIndex)
};
return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, sources, sources.Length);
}
return GetOperand(operand);
}
public AstOperand GetOperand(Operand operand)
{
if (operand == null)
@ -307,11 +334,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
if (operand.Type != OperandType.LocalVariable)
{
if (operand.Type == OperandType.ConstantBuffer)
{
Config.SetUsedConstantBuffer(operand.GetCbufSlot());
}
return new AstOperand(operand);
}

View File

@ -1,4 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Shader
@ -11,8 +13,20 @@ namespace Ryujinx.Graphics.Shader
public T W;
}
enum SupportBufferField
{
// Must match the order of the fields on the struct.
FragmentAlphaTest,
FragmentIsBgra,
ViewportInverse,
FragmentRenderScaleCount,
RenderScale
}
public struct SupportBuffer
{
internal const int Binding = 0;
public static int FieldSize;
public static int RequiredSize;
@ -47,6 +61,18 @@ namespace Ryujinx.Graphics.Shader
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
}
internal static StructureType GetStructureType()
{
return new StructureType(new[]
{
new StructureField(AggregateType.U32, "s_alpha_test"),
new StructureField(AggregateType.Array | AggregateType.U32, "s_is_bgra", FragmentIsBgraCount),
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_inverse"),
new StructureField(AggregateType.S32, "s_frag_scale_count"),
new StructureField(AggregateType.Array | AggregateType.FP32, "s_render_scale", RenderScaleMaxCount)
});
}
public Vector4<int> FragmentAlphaTest;
public Array8<Vector4<int>> FragmentIsBgra;
public Vector4<float> ViewportInverse;

View File

@ -22,4 +22,35 @@
Array = 1 << 10
}
static class AggregateTypeExtensions
{
public static int GetSizeInBytes(this AggregateType type)
{
int elementSize = (type & AggregateType.ElementTypeMask) switch
{
AggregateType.Bool or
AggregateType.FP32 or
AggregateType.S32 or
AggregateType.U32 => 4,
AggregateType.FP64 => 8,
_ => 0
};
switch (type & AggregateType.ElementCountMask)
{
case AggregateType.Vector2:
elementSize *= 2;
break;
case AggregateType.Vector3:
elementSize *= 3;
break;
case AggregateType.Vector4:
elementSize *= 4;
break;
}
return elementSize;
}
}
}

View File

@ -234,8 +234,8 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));
Operand y = this.Load(StorageKind.Output, IoVariable.Position, null, Const(1));
Operand xScale = this.Load(StorageKind.Input, IoVariable.SupportBlockViewInverse, null, Const(0));
Operand yScale = this.Load(StorageKind.Input, IoVariable.SupportBlockViewInverse, null, Const(1));
Operand xScale = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.ViewportInverse), Const(0));
Operand yScale = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.ViewportInverse), Const(1));
Operand negativeOne = ConstF(-1.0f);
this.Store(StorageKind.Output, IoVariable.Position, null, Const(0), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
@ -420,7 +420,7 @@ namespace Ryujinx.Graphics.Shader.Translation
// Perform B <-> R swap if needed, for BGRA formats (not supported on OpenGL).
if (!supportsBgra && (component == 0 || component == 2))
{
Operand isBgra = this.Load(StorageKind.Input, IoVariable.FragmentOutputIsBgra, null, Const(rtIndex));
Operand isBgra = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.FragmentIsBgra), Const(rtIndex));
Operand lblIsBgra = Label();
Operand lblEnd = Label();

View File

@ -549,11 +549,31 @@ namespace Ryujinx.Graphics.Shader.Translation
return context.Add(fpType | Instruction.IsNan, Local(), a);
}
public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding)
{
return context.Add(Instruction.Load, storageKind, Local(), Const(binding));
}
public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding, Operand e0)
{
return context.Add(Instruction.Load, storageKind, Local(), Const(binding), e0);
}
public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1)
{
return context.Add(Instruction.Load, storageKind, Local(), Const(binding), e0, e1);
}
public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand e2)
{
return context.Add(Instruction.Load, storageKind, Local(), Const(binding), e0, e1, e2);
}
public static Operand Load(this EmitterContext context, StorageKind storageKind, IoVariable ioVariable, Operand primVertex = null)
{
return primVertex != null
? context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), primVertex)
: context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable));
? context.Load(storageKind, (int)ioVariable, primVertex)
: context.Load(storageKind, (int)ioVariable);
}
public static Operand Load(
@ -564,8 +584,8 @@ namespace Ryujinx.Graphics.Shader.Translation
Operand elemIndex)
{
return primVertex != null
? context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), primVertex, elemIndex)
: context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), elemIndex);
? context.Load(storageKind, (int)ioVariable, primVertex, elemIndex)
: context.Load(storageKind, (int)ioVariable, elemIndex);
}
public static Operand Load(
@ -577,22 +597,8 @@ namespace Ryujinx.Graphics.Shader.Translation
Operand elemIndex)
{
return primVertex != null
? context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), primVertex, arrayIndex, elemIndex)
: context.Add(Instruction.Load, storageKind, Local(), Const((int)ioVariable), arrayIndex, elemIndex);
}
public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b)
{
if (a.Type == OperandType.Constant)
{
context.Config.SetUsedConstantBuffer(a.Value);
}
else
{
context.Config.SetUsedFeature(FeatureFlags.CbIndexing);
}
return context.Add(Instruction.LoadConstant, Local(), a, b);
? context.Load(storageKind, (int)ioVariable, primVertex, arrayIndex, elemIndex)
: context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex);
}
public static Operand LoadGlobal(this EmitterContext context, Operand a, Operand b)

View File

@ -19,7 +19,6 @@ namespace Ryujinx.Graphics.Shader.Translation
InstanceId = 1 << 3,
DrawParameters = 1 << 4,
RtLayer = 1 << 5,
CbIndexing = 1 << 6,
IaIndexing = 1 << 7,
OaIndexing = 1 << 8,
FixedFuncAttr = 1 << 9

View File

@ -32,25 +32,49 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
if (handleAsgOp.Inst != Instruction.LoadConstant)
if (handleAsgOp.Inst != Instruction.Load ||
handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
handleAsgOp.SourcesCount != 4)
{
continue;
}
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!config.ResourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != 2)
{
continue;
}
Operand ldcSrc1 = handleAsgOp.GetSource(1);
if (ldcSrc0.Type != OperandType.Constant || ldcSrc0.Value != 2)
// We expect field index 0 to be accessed.
if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
{
continue;
}
if (!(ldcSrc1.AsgOp is Operation shrOp) || shrOp.Inst != Instruction.ShiftRightU32)
Operand ldcSrc2 = handleAsgOp.GetSource(2);
// FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2.
// Might be not worth fixing since if that doesn't kick in, the result will be no texture
// to access anyway which is also wrong.
// Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size.
// Eventually, this should be entirely removed in favor of a implementation that supports true bindless
// texture access.
if (!(ldcSrc2.AsgOp is Operation shrOp) || shrOp.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (!(shrOp.GetSource(0).AsgOp is Operation addOp) || addOp.Inst != Instruction.Add)
if (!(shrOp.GetSource(0).AsgOp is Operation shrOp2) || shrOp2.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (!(shrOp2.GetSource(0).AsgOp is Operation addOp) || addOp.Inst != Instruction.Add)
{
continue;
}

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class ConstantFolding
{
public static void RunPass(Operation operation)
public static void RunPass(ShaderConfig config, Operation operation)
{
if (!AreAllSourcesConstant(operation))
{
@ -153,8 +153,21 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
EvaluateFPUnary(operation, (x) => float.IsNaN(x));
break;
case Instruction.LoadConstant:
operation.TurnIntoCopy(Cbuf(operation.GetSource(0).Value, operation.GetSource(1).Value));
case Instruction.Load:
if (operation.StorageKind == StorageKind.ConstantBuffer && operation.SourcesCount == 4)
{
int binding = operation.GetSource(0).Value;
int fieldIndex = operation.GetSource(1).Value;
if (config.ResourceManager.TryGetConstantBufferSlot(binding, out int cbufSlot) && fieldIndex == 0)
{
int vecIndex = operation.GetSource(2).Value;
int elemIndex = operation.GetSource(3).Value;
int cbufOffset = vecIndex * 4 + elemIndex;
operation.TurnIntoCopy(Cbuf(cbufSlot, cbufOffset));
}
}
break;
case Instruction.Maximum:

View File

@ -347,21 +347,23 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return wordOffset;
}
Operand[] sources = new Operand[operation.SourcesCount];
Operand cbufOffset = GetCbufOffset();
Operand vecIndex = Local();
Operand elemIndex = Local();
node.List.AddBefore(node, new Operation(Instruction.ShiftRightU32, 0, vecIndex, cbufOffset, Const(2)));
node.List.AddBefore(node, new Operation(Instruction.BitwiseAnd, 0, elemIndex, cbufOffset, Const(3)));
Operand[] sources = new Operand[4];
int cbSlot = UbeFirstCbuf + storageIndex;
sources[0] = Const(cbSlot);
sources[1] = GetCbufOffset();
sources[0] = Const(config.ResourceManager.GetConstantBufferBinding(cbSlot));
sources[1] = Const(0);
sources[2] = vecIndex;
sources[3] = elemIndex;
config.SetUsedConstantBuffer(cbSlot);
for (int index = 2; index < operation.SourcesCount; index++)
{
sources[index] = operation.GetSource(index);
}
Operation ldcOp = new Operation(Instruction.LoadConstant, operation.Dest, sources);
Operation ldcOp = new Operation(Instruction.Load, StorageKind.ConstantBuffer, operation.Dest, sources);
for (int index = 0; index < operation.SourcesCount; index++)
{

View File

@ -9,7 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
public static void RunPass(BasicBlock[] blocks, ShaderConfig config)
{
RunOptimizationPasses(blocks);
RunOptimizationPasses(blocks, config);
int sbUseMask = 0;
int ubeUseMask = 0;
@ -20,15 +20,21 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
GlobalToStorage.RunPass(blocks[blkIndex], config, ref sbUseMask, ref ubeUseMask);
BindlessToIndexed.RunPass(blocks[blkIndex], config);
BindlessElimination.RunPass(blocks[blkIndex], config);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages.
if (config.Stage == ShaderStage.Fragment)
{
EliminateMultiplyByFragmentCoordW(blocks[blkIndex]);
}
}
config.SetAccessibleBufferMasks(sbUseMask, ubeUseMask);
// Run optimizations one last time to remove any code that is now optimizable after above passes.
RunOptimizationPasses(blocks);
RunOptimizationPasses(blocks, config);
}
private static void RunOptimizationPasses(BasicBlock[] blocks)
private static void RunOptimizationPasses(BasicBlock[] blocks, ShaderConfig config)
{
bool modified;
@ -67,7 +73,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
ConstantFolding.RunPass(operation);
ConstantFolding.RunPass(config, operation);
Simplification.RunPass(operation);
if (DestIsLocalVar(operation))
@ -281,6 +287,75 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return modified;
}
private static void EliminateMultiplyByFragmentCoordW(BasicBlock block)
{
foreach (INode node in block.Operations)
{
if (node is Operation operation)
{
EliminateMultiplyByFragmentCoordW(operation);
}
}
}
private static void EliminateMultiplyByFragmentCoordW(Operation operation)
{
// We're looking for the pattern:
// y = x * gl_FragCoord.w
// v = y * (1.0 / gl_FragCoord.w)
// Then we transform it into:
// v = x
// This pattern is common on fragment shaders due to the way how perspective correction is done.
// We are expecting a multiplication by the reciprocal of gl_FragCoord.w.
if (operation.Inst != (Instruction.FP32 | Instruction.Multiply))
{
return;
}
Operand lhs = operation.GetSource(0);
Operand rhs = operation.GetSource(1);
// Check LHS of the the main multiplication operation. We expect an input being multiplied by gl_FragCoord.w.
if (!(lhs.AsgOp is Operation attrMulOp) || attrMulOp.Inst != (Instruction.FP32 | Instruction.Multiply))
{
return;
}
Operand attrMulLhs = attrMulOp.GetSource(0);
Operand attrMulRhs = attrMulOp.GetSource(1);
// LHS should be any input, RHS should be exactly gl_FragCoord.w.
if (!Utils.IsInputLoad(attrMulLhs.AsgOp) || !Utils.IsInputLoad(attrMulRhs.AsgOp, IoVariable.FragmentCoord, 3))
{
return;
}
// RHS of the main multiplication should be a reciprocal operation (1.0 / x).
if (!(rhs.AsgOp is Operation reciprocalOp) || reciprocalOp.Inst != (Instruction.FP32 | Instruction.Divide))
{
return;
}
Operand reciprocalLhs = reciprocalOp.GetSource(0);
Operand reciprocalRhs = reciprocalOp.GetSource(1);
// Check if the divisor is a constant equal to 1.0.
if (reciprocalLhs.Type != OperandType.Constant || reciprocalLhs.AsFloat() != 1.0f)
{
return;
}
// Check if the dividend is gl_FragCoord.w.
if (!Utils.IsInputLoad(reciprocalRhs.AsgOp, IoVariable.FragmentCoord, 3))
{
return;
}
// If everything matches, we can replace the operation with the input load result.
operation.TurnIntoCopy(attrMulLhs);
}
private static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
{
// Remove a node from the nodes list, and also remove itself

View File

@ -4,6 +4,35 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class Utils
{
public static bool IsInputLoad(INode node)
{
return (node is Operation operation) &&
operation.Inst == Instruction.Load &&
operation.StorageKind == StorageKind.Input;
}
public static bool IsInputLoad(INode node, IoVariable ioVariable, int elemIndex)
{
if (!(node is Operation operation) ||
operation.Inst != Instruction.Load ||
operation.StorageKind != StorageKind.Input ||
operation.SourcesCount != 2)
{
return false;
}
Operand ioVariableSrc = operation.GetSource(0);
if (ioVariableSrc.Type != OperandType.Constant || (IoVariable)ioVariableSrc.Value != ioVariable)
{
return false;
}
Operand elemIndexSrc = operation.GetSource(1);
return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex;
}
private static Operation FindBranchSource(BasicBlock block)
{
foreach (BasicBlock sourceBlock in block.Predecessors)

View File

@ -0,0 +1,126 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Ryujinx.Graphics.Shader.Translation
{
class ResourceManager
{
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly IGpuAccessor _gpuAccessor;
private readonly ShaderProperties _properties;
private readonly string _stagePrefix;
private readonly int[] _cbSlotToBindingMap;
private readonly HashSet<int> _usedConstantBufferBindings;
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ShaderProperties properties)
{
_gpuAccessor = gpuAccessor;
_properties = properties;
_stagePrefix = GetShaderStagePrefix(stage);
_cbSlotToBindingMap = new int[18];
_cbSlotToBindingMap.AsSpan().Fill(-1);
_usedConstantBufferBindings = new HashSet<int>();
properties.AddConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
}
public int GetConstantBufferBinding(int slot)
{
int binding = _cbSlotToBindingMap[slot];
if (binding < 0)
{
binding = _gpuAccessor.QueryBindingConstantBuffer(slot);
_cbSlotToBindingMap[slot] = binding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}");
}
return binding;
}
public bool TryGetConstantBufferSlot(int binding, out int slot)
{
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{
if (_cbSlotToBindingMap[slot] == binding)
{
return true;
}
}
slot = 0;
return false;
}
public void SetUsedConstantBufferBinding(int binding)
{
_usedConstantBufferBindings.Add(binding);
}
public BufferDescriptor[] GetConstantBufferDescriptors()
{
var descriptors = new BufferDescriptor[_usedConstantBufferBindings.Count];
int descriptorIndex = 0;
for (int slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{
int binding = _cbSlotToBindingMap[slot];
if (binding >= 0 && _usedConstantBufferBindings.Contains(binding))
{
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot);
}
}
if (descriptors.Length != descriptorIndex)
{
Array.Resize(ref descriptors, descriptorIndex);
}
return descriptors;
}
private void AddNewConstantBuffer(int binding, string name)
{
StructureType type = new StructureType(new[]
{
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16)
});
_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;
if (index >= _stagePrefixes.Length)
{
return "invalid";
}
return _stagePrefixes[index];
}
}
}

View File

@ -1,7 +1,6 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
@ -16,6 +15,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
bool isVertexShader = config.Stage == ShaderStage.Vertex;
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
@ -45,6 +45,11 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
if (hasVectorIndexingBug)
{
InsertVectorComponentSelect(node, config);
}
LinkedListNode<INode> nextNode = node.Next;
if (operation is TextureOperation texOp)
@ -71,6 +76,84 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config)
{
Operation operation = (Operation)node.Value;
if (operation.Inst != Instruction.Load ||
operation.StorageKind != StorageKind.ConstantBuffer ||
operation.SourcesCount < 3)
{
return;
}
Operand bindingIndex = operation.GetSource(0);
Operand fieldIndex = operation.GetSource(1);
Operand elemIndex = operation.GetSource(operation.SourcesCount - 1);
if (bindingIndex.Type != OperandType.Constant ||
fieldIndex.Type != OperandType.Constant ||
elemIndex.Type == OperandType.Constant)
{
return;
}
BufferDefinition buffer = config.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
int elemCount = (field.Type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1
};
if (elemCount == 1)
{
return;
}
Operand result = null;
for (int i = 0; i < elemCount; i++)
{
Operand value = Local();
Operand[] inputs = new Operand[operation.SourcesCount];
for (int srcIndex = 0; srcIndex < inputs.Length - 1; srcIndex++)
{
inputs[srcIndex] = operation.GetSource(srcIndex);
}
inputs[inputs.Length - 1] = Const(i);
Operation loadOp = new Operation(Instruction.Load, StorageKind.ConstantBuffer, value, inputs);
node.List.AddBefore(node, loadOp);
if (i == 0)
{
result = value;
}
else
{
Operand isCurrentIndex = Local();
Operand selection = Local();
Operation compareOp = new Operation(Instruction.CompareEqual, isCurrentIndex, new Operand[] { elemIndex, Const(i) });
Operation selectOp = new Operation(Instruction.ConditionalSelect, selection, new Operand[] { isCurrentIndex, value, result });
node.List.AddBefore(node, compareOp);
node.List.AddBefore(node, selectOp);
result = selection;
}
}
operation.TurnIntoCopy(result);
}
private static LinkedListNode<INode> RewriteGlobalAccess(LinkedListNode<INode> node, ShaderConfig config)
{
Operation operation = (Operation)node.Value;
@ -90,6 +173,15 @@ namespace Ryujinx.Graphics.Shader.Translation
return local;
}
Operand PrependStorageOperation(Instruction inst, StorageKind storageKind, params Operand[] sources)
{
Operand local = Local();
node.List.AddBefore(node, new Operation(inst, storageKind, local, sources));
return local;
}
Operand PrependExistingOperation(Operation operation)
{
Operand local = Local();
@ -204,8 +296,6 @@ namespace Ryujinx.Graphics.Shader.Translation
cbeUseMask &= ~(1 << slot);
config.SetUsedConstantBuffer(cbSlot);
Operand previousResult = PrependExistingOperation(storageOp);
int cbOffset = GetConstantUbeOffset(slot);
@ -216,18 +306,17 @@ namespace Ryujinx.Graphics.Shader.Translation
Operand byteOffsetConst = PrependOperation(Instruction.Subtract, addrLow, baseAddrTruncConst);
Operand cbIndex = PrependOperation(Instruction.ShiftRightU32, byteOffsetConst, Const(2));
Operand vecIndex = PrependOperation(Instruction.ShiftRightU32, cbIndex, Const(2));
Operand elemIndex = PrependOperation(Instruction.BitwiseAnd, cbIndex, Const(3));
Operand[] sourcesCb = new Operand[operation.SourcesCount];
Operand[] sourcesCb = new Operand[4];
sourcesCb[0] = Const(cbSlot);
sourcesCb[1] = cbIndex;
sourcesCb[0] = Const(config.ResourceManager.GetConstantBufferBinding(cbSlot));
sourcesCb[1] = Const(0);
sourcesCb[2] = vecIndex;
sourcesCb[3] = elemIndex;
for (int index = 2; index < operation.SourcesCount; index++)
{
sourcesCb[index] = operation.GetSource(index);
}
Operand ldcResult = PrependOperation(Instruction.LoadConstant, sourcesCb);
Operand ldcResult = PrependStorageOperation(Instruction.Load, StorageKind.ConstantBuffer, sourcesCb);
storageOp = new Operation(Instruction.ConditionalSelect, operation.Dest, inRange, ldcResult, previousResult);
}

View File

@ -39,6 +39,10 @@ namespace Ryujinx.Graphics.Shader.Translation
public TranslationOptions Options { get; }
public ShaderProperties Properties { get; }
public ResourceManager ResourceManager { get; }
public bool TransformFeedbackEnabled { get; }
private TransformFeedbackOutput[] _transformFeedbackOutputs;
@ -109,7 +113,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public int AccessibleStorageBuffersMask { get; private set; }
public int AccessibleConstantBuffersMask { get; private set; }
private int _usedConstantBuffers;
private int _usedStorageBuffers;
private int _usedStorageBuffersWrite;
@ -128,20 +131,17 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly Dictionary<int, int> _sbSlots;
private readonly Dictionary<int, int> _sbSlotsReverse;
private BufferDescriptor[] _cachedConstantBufferDescriptors;
private BufferDescriptor[] _cachedStorageBufferDescriptors;
private TextureDescriptor[] _cachedTextureDescriptors;
private TextureDescriptor[] _cachedImageDescriptors;
private int _firstConstantBufferBinding;
private int _firstStorageBufferBinding;
public int FirstConstantBufferBinding => _firstConstantBufferBinding;
public int FirstStorageBufferBinding => _firstStorageBufferBinding;
public ShaderConfig(IGpuAccessor gpuAccessor, TranslationOptions options)
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options)
{
Stage = ShaderStage.Compute;
Stage = stage;
GpuAccessor = gpuAccessor;
Options = options;
@ -158,6 +158,9 @@ namespace Ryujinx.Graphics.Shader.Translation
_sbSlots = new Dictionary<int, int>();
_sbSlotsReverse = new Dictionary<int, int>();
Properties = new ShaderProperties();
ResourceManager = new ResourceManager(stage, gpuAccessor, Properties);
}
public ShaderConfig(
@ -165,9 +168,8 @@ namespace Ryujinx.Graphics.Shader.Translation
OutputTopology outputTopology,
int maxOutputVertices,
IGpuAccessor gpuAccessor,
TranslationOptions options) : this(gpuAccessor, options)
TranslationOptions options) : this(stage, gpuAccessor, options)
{
Stage = stage;
ThreadsPerInputPrimitive = 1;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
@ -179,9 +181,8 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(gpuAccessor, options)
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(header.Stage, gpuAccessor, options)
{
Stage = header.Stage;
GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
ThreadsPerInputPrimitive = header.ThreadsPerInputPrimitive;
OutputTopology = header.OutputTopology;
@ -428,12 +429,13 @@ namespace Ryujinx.Graphics.Shader.Translation
public void InheritFrom(ShaderConfig other)
{
ResourceManager.InheritFrom(other.ResourceManager);
ClipDistancesWritten |= other.ClipDistancesWritten;
UsedFeatures |= other.UsedFeatures;
UsedInputAttributes |= other.UsedInputAttributes;
UsedOutputAttributes |= other.UsedOutputAttributes;
_usedConstantBuffers |= other._usedConstantBuffers;
_usedStorageBuffers |= other._usedStorageBuffers;
_usedStorageBuffersWrite |= other._usedStorageBuffersWrite;
@ -641,11 +643,6 @@ namespace Ryujinx.Graphics.Shader.Translation
AccessibleConstantBuffersMask = ubeMask;
}
public void SetUsedConstantBuffer(int slot)
{
_usedConstantBuffers |= 1 << slot;
}
public void SetUsedStorageBuffer(int slot, bool write)
{
int mask = 1 << slot;
@ -762,27 +759,6 @@ namespace Ryujinx.Graphics.Shader.Translation
return meta;
}
public BufferDescriptor[] GetConstantBufferDescriptors()
{
if (_cachedConstantBufferDescriptors != null)
{
return _cachedConstantBufferDescriptors;
}
int usedMask = _usedConstantBuffers;
if (UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
{
usedMask |= (int)GpuAccessor.QueryConstantBufferUse();
}
return _cachedConstantBufferDescriptors = GetUniformBufferDescriptors(
usedMask,
UsedFeatures.HasFlag(FeatureFlags.CbIndexing),
out _firstConstantBufferBinding,
GpuAccessor.QueryBindingConstantBuffer);
}
public BufferDescriptor[] GetStorageBufferDescriptors()
{
if (_cachedStorageBufferDescriptors != null)
@ -798,47 +774,6 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryBindingStorageBuffer);
}
private static BufferDescriptor[] GetUniformBufferDescriptors(int usedMask, bool isArray, out int firstBinding, Func<int, int> getBindingCallback)
{
firstBinding = 0;
int lastSlot = -1;
bool hasFirstBinding = false;
var descriptors = new BufferDescriptor[BitOperations.PopCount((uint)usedMask)];
for (int i = 0; i < descriptors.Length; i++)
{
int slot = BitOperations.TrailingZeroCount(usedMask);
if (isArray)
{
// The next array entries also consumes bindings, even if they are unused.
for (int j = lastSlot + 1; j < slot; j++)
{
int binding = getBindingCallback(j);
if (!hasFirstBinding)
{
firstBinding = binding;
hasFirstBinding = true;
}
}
}
lastSlot = slot;
descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot);
if (!hasFirstBinding)
{
firstBinding = descriptors[i].Binding;
hasFirstBinding = true;
}
usedMask &= ~(1 << slot);
}
return descriptors;
}
private BufferDescriptor[] GetStorageBufferDescriptors(
int usedMask,
int writtenMask,
@ -1009,7 +944,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
{
return new ShaderProgramInfo(
GetConstantBufferDescriptors(),
ResourceManager.GetConstantBufferDescriptors(),
GetStorageBufferDescriptors(),
GetTextureDescriptors(),
GetImageDescriptors(),

View File

@ -99,7 +99,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (options.Flags.HasFlag(TranslationFlags.Compute))
{
config = new ShaderConfig(gpuAccessor, options);
config = new ShaderConfig(ShaderStage.Compute, gpuAccessor, options);
program = Decoder.Decode(config, address);
}

View File

@ -56,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
private int _writeCount;
private int _flushCount;
private int _flushTemp;
private int _lastFlushWrite = -1;
private ReaderWriterLock _flushLock;
private FenceHolder _flushFence;
@ -200,14 +201,21 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_baseType == BufferAllocationType.Auto)
{
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
// When flushed, wait for a bit more info to make a decision.
bool wasFlushed = _flushTemp > 0;
int multiplier = wasFlushed ? 2 : 0;
if (_writeCount >= (WriteCountThreshold << multiplier) || _setCount >= (SetCountThreshold << multiplier) || _flushCount >= (FlushCountThreshold << multiplier))
{
if (_flushCount > 0 || _flushTemp-- > 0)
{
// Buffers that flush should ideally be mapped in host address space for easy copies.
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
bool hostMappingSensitive = _gd.Vendor == Vendor.Nvidia;
bool deviceLocalMapped = Size > DeviceLocalSizeThreshold || (wasFlushed && _writeCount > _flushCount * 10 && hostMappingSensitive) || _currentType == BufferAllocationType.DeviceLocalMapped;
DesiredType = deviceLocalMapped ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
// It's harder for a buffer that is flushed to revert to another type of mapping.
if (_flushCount > 0)
@ -215,17 +223,18 @@ namespace Ryujinx.Graphics.Vulkan
_flushTemp = 1000;
}
}
else if (_writeCount >= WriteCountThreshold)
else if (_writeCount >= (WriteCountThreshold << multiplier))
{
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
DesiredType = BufferAllocationType.DeviceLocal;
}
else if (_setCount > SetCountThreshold)
else if (_setCount > (SetCountThreshold << multiplier))
{
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
DesiredType = BufferAllocationType.HostMapped;
}
_lastFlushWrite = -1;
_flushCount = 0;
_writeCount = 0;
_setCount = 0;
@ -418,7 +427,12 @@ namespace Ryujinx.Graphics.Vulkan
WaitForFlushFence();
_flushCount++;
if (_lastFlushWrite != _writeCount)
{
// If it's on the same page as the last flush, ignore it.
_lastFlushWrite = _writeCount;
_flushCount++;
}
Span<byte> result;

View File

@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Runtime.CompilerServices;
@ -95,16 +96,27 @@ namespace Ryujinx.Graphics.Vulkan
return Unsafe.As<ulong, BufferHandle>(ref handle64);
}
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
public BufferHandle CreateWithHandle(
VulkanRenderer gd,
int size,
BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default)
{
return CreateWithHandle(gd, size, out _, baseType, storageHint);
}
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, out BufferHolder holder, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
public BufferHandle CreateWithHandle(
VulkanRenderer gd,
int size,
out BufferHolder holder,
BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default)
{
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
if (holder == null)
{
Logger.Error?.Print(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X} and type \"{baseType}\".");
return BufferHandle.Null;
}

View File

@ -228,7 +228,12 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Storage);
}
public void SetTextureAndSampler(CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture texture, ISampler sampler)
public void SetTextureAndSampler(
CommandBufferScoped cbs,
ShaderStage stage,
int binding,
ITexture texture,
ISampler sampler)
{
if (texture is TextureBuffer textureBuffer)
{
@ -251,6 +256,28 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Texture);
}
public void SetTextureAndSamplerIdentitySwizzle(
CommandBufferScoped cbs,
ShaderStage stage,
int binding,
ITexture texture,
ISampler sampler)
{
if (texture is TextureView view)
{
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = view.GetIdentityImageView();
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
SignalDirty(DirtyFlags.Texture);
}
else
{
SetTextureAndSampler(cbs, stage, binding, texture, sampler);
}
}
public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{
for (int i = 0; i < buffers.Length; i++)

View File

@ -415,7 +415,7 @@ namespace Ryujinx.Graphics.Vulkan
var sampler = linearFilter ? _samplerLinear : _samplerNearest;
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, src, sampler);
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
@ -625,7 +625,7 @@ namespace Ryujinx.Graphics.Vulkan
private void BlitDepthStencilDraw(TextureView src, bool isDepth)
{
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, _samplerNearest);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, src, _samplerNearest);
if (isDepth)
{
@ -1037,7 +1037,7 @@ namespace Ryujinx.Graphics.Vulkan
var srcView = Create2DLayerView(src, srcLayer + z, srcLevel + l, srcFormat);
var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(0, dstView, dstFormat);
int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32;
@ -1177,7 +1177,7 @@ namespace Ryujinx.Graphics.Vulkan
var srcView = Create2DLayerView(src, srcLayer + z, 0, format);
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(0, dstView, format);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
@ -1313,7 +1313,7 @@ namespace Ryujinx.Graphics.Vulkan
var srcView = Create2DLayerView(src, srcLayer + z, 0, format);
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, srcView, null);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null);
_pipeline.SetRenderTarget(
((TextureView)dstView).GetView(format).GetImageViewForAttachment(),
(uint)dst.Width,
@ -1384,7 +1384,7 @@ namespace Ryujinx.Graphics.Vulkan
private void CopyMSAspectDraw(TextureView src, bool fromMS, bool isDepth)
{
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, _samplerNearest);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, src, _samplerNearest);
if (isDepth)
{

View File

@ -1098,6 +1098,11 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetTextureAndSampler(Cbs, stage, binding, texture, sampler);
}
public void SetTextureAndSamplerIdentitySwizzle(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
{
_descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler);
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{
PauseTransformFeedbackInternal();

View File

@ -12,6 +12,28 @@ namespace Ryujinx.Graphics.Vulkan
ShaderStageFlags.FragmentBit |
ShaderStageFlags.ComputeBit;
private static ShaderStageFlags ActiveStages(uint stages)
{
ShaderStageFlags stageFlags = 0;
while (stages != 0)
{
int stage = BitOperations.TrailingZeroCount(stages);
stages &= ~(1u << stage);
stageFlags |= stage switch
{
1 => ShaderStageFlags.FragmentBit,
2 => ShaderStageFlags.GeometryBit,
3 => ShaderStageFlags.TessellationControlBit,
4 => ShaderStageFlags.TessellationEvaluationBit,
_ => ShaderStageFlags.VertexBit | ShaderStageFlags.ComputeBit
};
}
return stageFlags;
}
public static unsafe DescriptorSetLayout[] Create(VulkanRenderer gd, Device device, uint stages, bool usePd, out PipelineLayout layout)
{
int stagesCount = BitOperations.PopCount(stages);
@ -34,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
};
int iter = 0;
var activeStages = ActiveStages(stages);
while (stages != 0)
{
@ -67,12 +90,16 @@ namespace Ryujinx.Graphics.Vulkan
void SetStorage(DescriptorSetLayoutBinding* bindings, int maxPerStage, int start = 0)
{
// There's a bug on MoltenVK where using the same buffer across different stages
// causes invalid resource errors, allow the binding on all active stages as workaround.
var flags = gd.IsMoltenVk ? activeStages : stageFlags;
bindings[start + iter] = new DescriptorSetLayoutBinding
{
Binding = (uint)(start + stage * maxPerStage),
DescriptorType = DescriptorType.StorageBuffer,
DescriptorCount = (uint)maxPerStage,
StageFlags = stageFlags
StageFlags = flags
};
}

View File

@ -261,7 +261,7 @@ namespace Ryujinx.HLE.HOS
AudioInputManager = new AudioInputManager();
AudioRendererManager = new AudioRendererManager(tickSource);
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(Device.AudioDeviceDriver);
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];

View File

@ -10,27 +10,44 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using System;
using System.Globalization;
using System.IO;
using System.Text.Json.Serialization;
namespace Ryujinx.Ui.App.Common
{
public class ApplicationData
{
public bool Favorite { get; set; }
public byte[] Icon { get; set; }
public string TitleName { get; set; }
public string TitleId { get; set; }
public string Developer { get; set; }
public string Version { get; set; }
public string TimePlayed { get; set; }
public double TimePlayedNum { get; set; }
public string LastPlayed { get; set; }
public string FileExtension { get; set; }
public string FileSize { get; set; }
public double FileSizeBytes { get; set; }
public string Path { get; set; }
public bool Favorite { get; set; }
public byte[] Icon { get; set; }
public string TitleName { get; set; }
public string TitleId { get; set; }
public string Developer { get; set; }
public string Version { get; set; }
public string TimePlayed { get; set; }
public double TimePlayedNum { get; set; }
public DateTime? LastPlayed { get; set; }
public string FileExtension { get; set; }
public string FileSize { get; set; }
public double FileSizeBytes { get; set; }
public string Path { get; set; }
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
[JsonIgnore]
public string LastPlayedString
{
get
{
if (!LastPlayed.HasValue)
{
// TODO: maybe put localized string here instead of just "Never"
return "Never";
}
return LastPlayed.Value.ToLocalTime().ToString(CultureInfo.CurrentCulture);
}
}
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
{
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);

View File

@ -414,21 +414,28 @@ namespace Ryujinx.Ui.App.Common
ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId, appMetadata =>
{
appMetadata.Title = titleName;
});
if (appMetadata.LastPlayed != "Never")
{
if (!DateTime.TryParse(appMetadata.LastPlayed, out _))
if (appMetadata.LastPlayedOld == default || appMetadata.LastPlayed.HasValue)
{
Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)");
// Don't do the migration if last_played doesn't exist or last_played_utc already has a value.
return;
}
appMetadata.LastPlayed = "Never";
// Migrate from string-based last_played to DateTime-based last_played_utc.
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
{
Logger.Info?.Print(LogClass.Application, $"last_played found: \"{appMetadata.LastPlayedOld}\", migrating to last_played_utc");
appMetadata.LastPlayed = lastPlayedOldParsed;
// Migration successful: deleting last_played from the metadata file.
appMetadata.LastPlayedOld = default;
}
else
{
appMetadata.LastPlayed = appMetadata.LastPlayed[..^3];
// Migration failed: emitting warning but leaving the unparsable value in the metadata file so the user can fix it.
Logger.Warning?.Print(LogClass.Application, $"Last played string \"{appMetadata.LastPlayedOld}\" is invalid for current system culture, skipping (did current culture change?)");
}
}
});
ApplicationData data = new()
{

View File

@ -1,10 +1,19 @@
namespace Ryujinx.Ui.App.Common
using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Ui.App.Common
{
public class ApplicationMetadata
{
public string Title { get; set; }
public bool Favorite { get; set; }
public double TimePlayed { get; set; }
public string LastPlayed { get; set; } = "Never";
[JsonPropertyName("last_played_utc")]
public DateTime? LastPlayed { get; set; } = null;
[JsonPropertyName("last_played")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string LastPlayedOld { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using Gdk;
using Gtk;
using Ryujinx.Common;
using Ryujinx.Ui;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
@ -47,9 +48,19 @@ namespace Ryujinx.Modules
if (_restartQuery)
{
string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
string ryuExe = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
Process.Start(ryuExe, CommandLineState.Arguments);
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = ReleaseInformation.GetBaseApplicationDirectory()
};
foreach (string argument in CommandLineState.Arguments)
{
processStart.ArgumentList.Add(argument);
}
Process.Start(processStart);
Environment.Exit(0);
}

View File

@ -876,7 +876,7 @@ namespace Ryujinx.Ui
_applicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
appMetadata.LastPlayed = DateTime.UtcNow;
});
}
}
@ -1019,10 +1019,11 @@ namespace Ryujinx.Ui
{
_applicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
if (appMetadata.LastPlayed.HasValue)
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
});
}
}
@ -1089,7 +1090,7 @@ namespace Ryujinx.Ui
args.AppData.Developer,
args.AppData.Version,
args.AppData.TimePlayed,
args.AppData.LastPlayed,
args.AppData.LastPlayedString,
args.AppData.FileExtension,
args.AppData.FileSize,
args.AppData.Path,

View File

@ -321,27 +321,27 @@ namespace Ryujinx.Ui
_toggleDockedMode = toggleDockedMode;
if (ConfigurationState.Instance.Hid.EnableMouse.Value)
if (_isMouseInClient)
{
if (_isMouseInClient)
if (ConfigurationState.Instance.Hid.EnableMouse.Value)
{
Window.Cursor = _invisibleCursor;
}
}
else
{
switch (_hideCursorMode)
else
{
case HideCursorMode.OnIdle:
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null;
break;
case HideCursorMode.Always:
Window.Cursor = _invisibleCursor;
break;
case HideCursorMode.Never:
Window.Cursor = null;
break;
switch (_hideCursorMode)
{
case HideCursorMode.OnIdle:
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null;
break;
case HideCursorMode.Always:
Window.Cursor = _invisibleCursor;
break;
case HideCursorMode.Never:
Window.Cursor = null;
break;
}
}
}
}