Compare commits

...

34 Commits

Author SHA1 Message Date
dependabot[bot]
7aa6abc120 nuget: bump SharpZipLib from 1.3.3 to 1.4.1 (#3893)
Bumps [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) from 1.3.3 to 1.4.1.
- [Release notes](https://github.com/icsharpcode/SharpZipLib/releases)
- [Changelog](https://github.com/icsharpcode/SharpZipLib/blob/master/docs/Changes.txt)
- [Commits](https://github.com/icsharpcode/SharpZipLib/compare/v1.3.3...v1.4.1)

---
updated-dependencies:
- dependency-name: SharpZipLib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 20:30:17 +01:00
Mary-nyan
548bfd60a2 chore: Update Ryujinx.SDL2-CS to 2.24.2 (#3892)
* chore: Update Ryujinx.SDL2-CS to 2.24.2

* Disable SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS
2022-11-24 20:13:16 +01:00
riperiperi
65778a6b78 GPU: Don't trigger uploads for redundant buffer updates (#3828)
* Initial implementation

* Actually do The Thing

* Add remark about performance to IVirtualMemoryManager
2022-11-24 15:50:15 +01:00
Mary-nyan
f4e879a1e6 Reduce usage of Marshal.PtrToStructure and Marshal.StructureToPtr (#3805)
* common: Make BinaryReaderExtensions Read & Write take unamanged types

This allows us to not rely on Marshal.PtrToStructure and Marshal.StructureToPtr for those.

* common: Make MemoryHelper Read & Write takes unamanged types

* Update Marshal.SizeOf => Unsafe.SizeOf when appropriate and start moving software applet to unmanaged types
2022-11-24 15:26:29 +01:00
Ac_K
a1ddaa2736 ui: Fixes disposing on GTK/Avalonia and Firmware Messages on Avalonia (#3885)
* ui: Only wait on _exitEvent when MainLoop is active under GTK

This fixes a dispose issue under Horizon/GTK, we don't check if the ApplicationClient is null so it throw NCE. We don't check if the main loop is active and waiting an event which is set in the main loop... So that could lead to a freeze.

Everything works fine in GTK now.

Related issue: https://github.com/Ryujinx/Ryujinx/issues/3873

As a side note, same kind of issue appear in Avalonia UI too. Firmware's popup doesn't show anything and the emulator just freeze.

* TSRBerry's change

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Fix Avalonia crashing/freezing

* Add Avalonia OpenGL fixes

* Fix firmware popup on windows

* Fixes everything

* Add _initialized bool to VulkanRenderer and OpenGL Window

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2022-11-24 15:08:27 +01:00
Mary-nyan
008286b79f Ryujinx.Ava: Add missing redefinition of app name (#3890)
Before this, Ryujinx would possibly report as "Avalonia Application".
2022-11-24 14:52:39 +01:00
gdkchan
a0c77f8d11 Fix NRE on Avalonia for error applets with unknown error message (#3888) 2022-11-24 09:31:00 +01:00
riperiperi
ece36b274d GAL: Send all buffer assignments at once rather than individually (#3881)
* GAL: Send all buffer assignments at once rather than individually

The `(int first, BufferRange[] ranges)` method call has very significant performance implications when the bindings are spread out, which they generally always are in Vulkan. This change makes it so that these methods are only called a maximum of one time per draw.

Significantly improves GPU thread performance in Pokemon Scarlet/Violet.

* Address Feedback

Removed SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
2022-11-24 07:50:59 +00:00
riperiperi
f3cc2e5703 GPU: Access non-prefetch command buffers directly (#3882)
* GPU: Access non-prefetch command buffers directly

Saves allocating new arrays for them constantly - they can be quite small so it can be very wasteful. About 0.4% of GPU thread in SMO, but was a bit higher in S/V when I checked.

Assumes that non-prefetch command buffers won't be randomly clobbered before they finish executing, though that's probably a safe bet.

* Small change while I'm here

* Address feedback
2022-11-24 01:56:55 +00:00
riperiperi
5a39d3c4a1 GPU: Relax locking on Buffer Cache (#3883)
I did this on ncbuffer2 when we were using it for LDN 3, but I noticed that it can apply to the current buffer manager too, and it's an easy performance win.

The only buffer access that can come from another thread is the overlap search for buffers that have been unmapped. Everything else, including modifications, come from the main GPU thread. That means we only need to lock the range list when it's being modified, as that's the only time where we'll cause a race with the unmapped handler.

This has a significant performance improvements in situations where FIFO is high, like the other two PRs. Joined together they give a nice boost (73.6 master -> 79 -> 83 fps in SMO).
2022-11-24 01:41:16 +00:00
dependabot[bot]
cc51a03af9 nuget: bump Avalonia from 0.10.15 to 0.10.18 (#3817)
Bumps [Avalonia](https://github.com/AvaloniaUI/Avalonia) from 0.10.15 to 0.10.18.
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/0.10.15...0.10.18)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 01:26:53 +00:00
Ac_K
567c64e149 ava: Fix JsonSerializer warnings (#3884)
Since we move to .NET7, JsonSerializer now needs to have explicit options as arguments, which leads to some warnings in Avalonia project. This is fixed by using our `JsonHelper` class.
2022-11-23 17:55:26 +00:00
Alex Barney
36f00985d3 Update to LibHac 0.17.0 (#3878)
* Update to LibHac 0.17.0

* Don't clear SD card saves when starting the emulator

This was an old workaround for errors that happened when a user's SD card encryption seed changed. SD card saves have been unencrypted for over a year, so we should be fine to remove the workaround.
2022-11-23 18:32:35 +01:00
WilliamWsyHK
748d87adcc Stub IFriendService: 1 (Cancel) (#3841)
* Add friend/Cancel. Closes #3824

* Update according to review comments.

* Add comment base on request

* Update Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-11-23 16:25:49 +01:00
Emmanuel Hansen
0fd47ff490 remove redundant logs (#3877) 2022-11-23 11:28:46 +01:00
gdkchan
f088c3d344 Do not update shader state for DrawTextures (#3876) 2022-11-21 18:16:00 +01:00
TSRBerry
905a191e28 Use upstream unicorn for Ryujinx.Tests.Unicorn (#3771)
* unicorn: Add modified ver of unicorns const gen

* unicorn: Use upstream consts

These consts were generated from the dev branch of unicorn

* unicorn: Split common consts into multiple enums

* unicorn: Remove arch prefix from consts

* unicorn: Add new windows dll

Windows 10 - MSVC x64 shared build

* unicorn: Use absolute path for const generation

* unicorn: Remove fspcr patch

* unicorn: Fix using the wrong file extension

For some reason _NativeLibraryExtension evaluates to ".so" even on Windows.

* unicorn: Add linux shared object again

* unicron: Add DllImportResolver

* unicorn: Try to import unicorn using an absolute path

* unicorn: Add clean target

* unicorn: Replace IsUnicornAvailable() methods

* unicorn: Skip tests instead of silently passing them if unicorn is missing

* unicorn: Write error message to stderr

* unicorn: Make Interface static

* unicron: Include prefixed unicorn libs (libunicorn.so)

Co-authored-by: merry <git@mary.rs>

* unicorn: Add lib prefix to shared object for linux

Co-authored-by: merry <git@mary.rs>
2022-11-20 20:18:21 +01:00
EmulationFanatic
ab0491817e Reword the description of the 6GB expand DRAM hack to be less tantalizing (#3870)
* Reword the hack to something less tantalizing

* Address feedback
2022-11-20 17:15:57 +00:00
gdkchan
5de6ae426e Unsubscribe MemoryUnmappedHandler even when GPU channel is destroyed (#3872) 2022-11-19 23:54:33 -03:00
gdkchan
69ced3a6e8 Fix shader cache on Vulkan when geometry shaders are inserted (#3868) 2022-11-19 10:24:23 +01:00
gdkchan
2e43d01d36 Move gl_Layer from vertex to geometry if GPU does not support it on vertex (#3866)
* Move gl_Layer from vertex to geometry if GPU does not support it on vertex

* Shader cache version bump

* PR feedback
2022-11-18 23:27:54 -03:00
riperiperi
7373ec5792 Vulkan: Clear dummy texture to (0,0,0,0) on creation (#3867)
This might fix an issue with AMD gpus on linux where the data could contain random garbage data. On the switch, it always samples as 0.
2022-11-18 23:11:34 -03:00
riperiperi
de162a648b Gpu: Fix thread safety of ReregisterRanges (#3865)
A quick fix to prevent reading the wrong value of Count when reregistering ranges for a new target buffer. Buffer flushes from another thread can modify the range list when the lock isn't active, which can change the count.

This prevents some crashes in Pokemon Scarlet/Violet. It's probably likely that buffer migration during flush is causing some other issues in this game, but this at least prevents the crashing.
2022-11-18 21:47:29 +01:00
riperiperi
131baebe2a Vulkan: Don't create preload command buffer outside a render pass (#3864)
* Vulkan: Don't create preload buffer outside a render pass

The preload command buffer is used to avoid render pass splits and barriers when updating buffer data. However, when a render pass is not active (for example, at the start of a pass, or during compute invocations) buffer uploads can be performed at any time, so the optimization isn't as useful.

This PR makes it so that the preload command buffer is only used for buffer updates outside of a render pass. It's still used for textures as I don't want to shake things up right now regarding how the preload buffer is obtained before some other changes, and texture updates are a lot rarer anyways.

Improves performance slightly in Pokemon Scarlet/Violet (43 -> 48), as it was switching to compute, writing a bunch of buffers inline, then dispatching, then flushing commands... It uses 1 command buffer instead of 2 every time it does this now. Maybe it would be nice to find a faster way to sync without creating so many command buffers in a short period of time.

* Address feedback
2022-11-18 14:58:56 +00:00
riperiperi
187372cbde Prune ForceDirty and CheckModified caches on unmap (#3862)
* Prune ForceDirty and CheckModified caches on unmap

Since we're now using this for modified checks on the HLE indirect draw method, I'm worried that leaving these to forever gather cache entries isn't the best idea for performance in the long term, and it could keep old buffer objects alive for longer than they should be.

This PR adds the ability to prune invalid entries before checking these caches, and queues it whenever gpu memory is unmapped. It also aligns modified checks to the page size, as I figured it would be possible for a huge number of overlapping over a game's runtime.

This prevents Super Mario Odyssey from having 10s of thousands of entries in the modified cache in Metro Kingdom, and them duplicating when entering and leaving a building (should be cleared, as they were unmapped).

* Address Feedback
2022-11-18 14:58:24 +00:00
TSRBerry
022d495335 am: Stub GetSaveDataSizeMax (#3857)
* am: Stub GetSaveDataSizeMax()

* am: Remove todo comment for GetSaveDataSizeMax()

* am: saveDataSize & journalDataSize should be of type long

* am: Add explanation for returning default values in GetSaveDataSizeMax()
2022-11-18 03:29:01 +00:00
Berkan Diler
c1372ed775 Use ReadOnlySpan<byte> compiler optimization in more places (#3853)
* Use ReadOnlySpan<byte> compiler optimization in more places

* Revert changes in ShaderBinaries.cs

* Remove unused using;

* Use ReadOnlySpan<byte> compiler optimization in more places
2022-11-18 03:10:44 +00:00
riperiperi
a16682cfd3 Allow _volatile to be set from MultiRegionHandle checks again (#3830)
* Allow _volatile to be set from MultiRegionHandle checks again

Tracking handles have a `_volatile` flag which indicates that the resource being tracked is modified every time it is used under a new sequence number. This is used to reduce the time spent reprotecting memory for tracking writes to commonly modified buffers, like constant buffers.

This optimisation works by detecting if a buffer is modified every time a check happens. If a buffer is checked but it is not dirty, then that data is likely not modified every sequence number, and should use memory protection for write tracking. If the opposite is the case all the time, it is faster to just assume it's dirty as we'd just be wasting time protecting the memory.

The new MultiRegionBitmap could not notify handles that they had been checked as part of the fast bitmap lookup, so bindings larger than 4096 bytes wouldn't trigger it at all. This meant that they would be subject to a ton of reprotection if they were modified often.

This does mean there are two separate sources for a _volatile set: VolatileOrDirty + _checkCount, and the bitmap check. These shouldn't interfere with each other, though.

This fixes performance regressions from #3775 in Pokemon Sword, and hopefully Yu-Gi-Oh! RUSH DUEL: Dawn of the Battle Royale. May affect other games.

* Fix stupid mistake
2022-11-18 02:54:20 +00:00
riperiperi
7c53b69c30 SPIR-V: Fix unscaling helper not being able to find Array textures (#3863)
The type in the `texOp` in the textureSize instruction doesn't have the exact type on SPIR-V (for example, it is missing the Array flag). This PR gives it the proper type before giving it to the unscaling helper.

This fixes the ground textures being broken on Pokemon Scarlet/Violet when scaling. It wasn't finding the texture, so the descriptor index it provided was -1...
2022-11-18 02:37:37 +00:00
riperiperi
33a4d7d1ba GPU: Eliminate CB0 accesses when storage buffer accesses are resolved (#3847)
* Eliminate CB0 accesses

Still some work to do, decouple from hle?

* Forgot the important part somehow

* Fix and improve alignment test

* Address Feedback

* Remove some complexity when checking storage buffer alignment

* Update Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-11-17 18:47:41 +01:00
Mary-nyan
391e08dd27 ci: Clean up Actions leftovers (#3859)
As title say.

Fix Avalonia build versions for PRs.

Also ensure that the --self-contained doesn't warn at build.
2022-11-17 18:30:54 +01:00
Matthew Wells
b5cf8b8af9 Capitalization to be consistent (#3860)
Thread ID Register, Floating-point Control Register, and Floating-point Status Register all had Register capitalized, so the Register in Processor State register should be capitalized.
2022-11-17 18:13:37 +01:00
Alberto Fanjul
55043c8afc Allow to start Ryujinx in wayland environment (#3516)
PrimaryMonitor is only available on X11

At some point it will be deprecated, this change support wayland
2022-11-17 13:02:43 +01:00
Mary-nyan
5d73a9f5fc Fix Fedora support (#3815)
For some reasons, my fresh installation of Fedora 36 (KDE) doesn't have a
symlink for libX11.so.

This commit fixes this by trying to import the library with its major
version or fallback to the normal way.
2022-11-17 00:18:29 +01:00
134 changed files with 2404 additions and 1265 deletions

View File

@@ -52,26 +52,22 @@ jobs:
- uses: actions/setup-dotnet@v3 - uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 7.0.x
- name: Ensure NuGet Source
uses: fabriciomurta/ensure-nuget-source@v1
- name: Get git short hash - name: Get git short hash
id: git_short_hash id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash shell: bash
- name: Clear
run: dotnet clean && dotnet nuget locals all --clear
- name: Build - name: Build
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
- name: Test - name: Test
run: dotnet test --no-build -c "${{ matrix.configuration }}" run: dotnet test --no-build -c "${{ matrix.configuration }}"
- name: Publish Ryujinx - 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 Ryujinx --self-contained 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 Ryujinx --self-contained true
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Publish Ryujinx.Headless.SDL2 - 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 Ryujinx.Headless.SDL2 --self-contained 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 Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Publish Ryujinx.Ava - name: Publish Ryujinx.Ava
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER 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 Ryujinx.Ava --self-contained true
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Upload Ryujinx artifact - name: Upload Ryujinx artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -29,10 +29,6 @@ jobs:
- uses: actions/setup-dotnet@v3 - uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 7.0.x dotnet-version: 7.0.x
- name: Ensure NuGet Source
uses: fabriciomurta/ensure-nuget-source@v1
- name: Clear
run: dotnet clean && dotnet nuget locals all --clear
- name: Get version info - name: Get version info
id: version_info id: version_info
run: | run: |
@@ -51,9 +47,9 @@ jobs:
run: "mkdir release_output" run: "mkdir release_output"
- name: Publish Windows - name: Publish Windows
run: | run: |
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Ava --self-contained dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
- name: Packing Windows builds - name: Packing Windows builds
run: | run: |
pushd publish_windows pushd publish_windows
@@ -71,9 +67,9 @@ jobs:
- name: Publish Linux - name: Publish Linux
run: | run: |
dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Ava --self-contained dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
- name: Packing Linux builds - name: Packing Linux builds
run: | run: |

View File

@@ -1,15 +1,11 @@
using System;
using System.Numerics; using System.Numerics;
namespace ARMeilleure.Common namespace ARMeilleure.Common
{ {
static class BitUtils static class BitUtils
{ {
private static readonly sbyte[] HbsNibbleLut; private static ReadOnlySpan<sbyte> HbsNibbleLut => new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 };
static BitUtils()
{
HbsNibbleLut = new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 };
}
public static long FillWithOnes(int bits) public static long FillWithOnes(int bits)
{ {

View File

@@ -21,6 +21,8 @@ namespace Ryujinx.Ava
{ {
public override void Initialize() public override void Initialize()
{ {
Name = $"Ryujinx {Program.Version}";
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }

View File

@@ -60,7 +60,7 @@ namespace Ryujinx.Ava
private const float VolumeDelta = 0.05f; private const float VolumeDelta = 0.05f;
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None); private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private readonly long _ticksPerFrame; private readonly long _ticksPerFrame;
private readonly Stopwatch _chrono; private readonly Stopwatch _chrono;
@@ -349,7 +349,10 @@ namespace Ryujinx.Ava
_isActive = false; _isActive = false;
_renderingThread.Join(); if (_renderingThread.IsAlive)
{
_renderingThread.Join();
}
DisplaySleep.Restore(); DisplaySleep.Restore();
@@ -378,7 +381,7 @@ namespace Ryujinx.Ava
_gpuCancellationTokenSource.Cancel(); _gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose(); _gpuCancellationTokenSource.Dispose();
_chrono.Stop(); _chrono.Stop();
} }
@@ -393,7 +396,7 @@ namespace Ryujinx.Ava
Renderer?.MakeCurrent(); Renderer?.MakeCurrent();
Device.DisposeGpu(); Device.DisposeGpu();
Renderer?.MakeCurrent(null); Renderer?.MakeCurrent(null);
} }
@@ -417,7 +420,6 @@ namespace Ryujinx.Ava
public async Task<bool> LoadGuestApplication() public async Task<bool> LoadGuestApplication()
{ {
InitializeSwitchInstance(); InitializeSwitchInstance();
MainWindow.UpdateGraphicsConfig(); MainWindow.UpdateGraphicsConfig();
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
@@ -428,17 +430,16 @@ namespace Ryujinx.Ava
{ {
if (userError == UserError.NoFirmware) if (userError == UserError.NoFirmware)
{ {
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"],
firmwareVersion.VersionString);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"],
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
"");
if (result != UserResult.Yes) if (result != UserResult.Yes)
{ {
Dispatcher.UIThread.Post(async () => await await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
Device.Dispose(); Device.Dispose();
return false; return false;
@@ -447,8 +448,7 @@ namespace Ryujinx.Ava
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{ {
Dispatcher.UIThread.Post(async () => await await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
Device.Dispose(); Device.Dispose();
return false; return false;
@@ -461,11 +461,9 @@ namespace Ryujinx.Ava
_parent.RefreshFirmwareStatus(); _parent.RefreshFirmwareStatus();
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString), string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
message, string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
"", "",
LocaleManager.Instance["RyujinxInfo"]); LocaleManager.Instance["RyujinxInfo"]);
@@ -473,9 +471,7 @@ namespace Ryujinx.Ava
} }
else else
{ {
Dispatcher.UIThread.Post(async () => await await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
Device.Dispose(); Device.Dispose();
return false; return false;
@@ -514,7 +510,7 @@ namespace Ryujinx.Ava
} }
else if (File.Exists(ApplicationPath)) else if (File.Exists(ApplicationPath))
{ {
switch (System.IO.Path.GetExtension(ApplicationPath).ToLowerInvariant()) switch (Path.GetExtension(ApplicationPath).ToLowerInvariant())
{ {
case ".xci": case ".xci":
{ {
@@ -602,7 +598,7 @@ namespace Ryujinx.Ava
if (Renderer.IsVulkan) if (Renderer.IsVulkan)
{ {
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value; string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu); renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
} }
else else

View File

@@ -119,7 +119,7 @@
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " (may cause instability)", "SettingsTabSystemHacksNote": " (may cause instability)",
"SettingsTabSystemExpandDramSize": "Expand DRAM Size to 6GiB", "SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)",
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
"SettingsTabGraphics": "Graphics", "SettingsTabGraphics": "Graphics",
"SettingsTabGraphicsAPI": "Graphics API", "SettingsTabGraphicsAPI": "Graphics API",
@@ -440,7 +440,7 @@
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
"MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
"MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
"DRamTooltip": "Increases the amount of memory on the emulated system from 4GiB to 6GiB.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
"IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
"GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",

View File

@@ -1,5 +1,6 @@
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -93,7 +94,7 @@ namespace Ryujinx.Ava.Common.Locale
return; return;
} }
var strings = JsonSerializer.Deserialize<Dictionary<string, string>>(languageJson); var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
foreach (var item in strings) foreach (var item in strings)
{ {

View File

@@ -18,7 +18,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15" /> <PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" /> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />
@@ -36,8 +36,8 @@
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
<PackageReference Include="SPB" Version="0.0.4-build27" /> <PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup> </ItemGroup>

View File

@@ -57,14 +57,14 @@ namespace Ryujinx.Ava.Ui.Applet
bool opened = false; bool opened = false;
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent, UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title, title,
message, message,
"", "",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"], LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
"", "",
LocaleManager.Instance["SettingsButtonClose"], LocaleManager.Instance["SettingsButtonClose"],
(int)Symbol.Important, (int)Symbol.Important,
deferEvent, deferEvent,
async (window) => async (window) =>
{ {
@@ -168,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Applet
object response = await msgDialog.Run(); object response = await msgDialog.Run();
if (response != null && buttons.Length > 1 && (int)response != buttons.Length - 1) if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
{ {
showDetails = true; showDetails = true;
} }

View File

@@ -49,7 +49,7 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
} }
var flags = OpenGLContextFlags.Compat; var flags = OpenGLContextFlags.Compat;
if (_graphicsDebugLevel != GraphicsDebugLevel.None) if (_graphicsDebugLevel != GraphicsDebugLevel.None)
{ {
@@ -69,12 +69,12 @@ namespace Ryujinx.Ava.Ui.Controls
public void MakeCurrent() public void MakeCurrent()
{ {
Context.MakeCurrent(_window); Context?.MakeCurrent(_window);
} }
public void MakeCurrent(NativeWindowBase window) public void MakeCurrent(NativeWindowBase window)
{ {
Context.MakeCurrent(window); Context?.MakeCurrent(window);
} }
public void SwapBuffers() public void SwapBuffers()

View File

@@ -8,6 +8,7 @@ using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -189,7 +190,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
amiiboJsonString = File.ReadAllText(_amiiboJsonPath); amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
if (await NeedsUpdate(JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated)) if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
{ {
amiiboJsonString = await DownloadAmiiboJson(); amiiboJsonString = await DownloadAmiiboJson();
} }
@@ -206,7 +207,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
_amiiboList = JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo; _amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData(); ParseAmiiboData();

View File

@@ -460,8 +460,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null) if (gamepad != null)
{ {
Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}"));
@@ -472,8 +470,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null) if (gamepad != null)
{ {
if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id)))

View File

@@ -251,24 +251,29 @@ namespace Ryujinx.Ava.Ui.Windows
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this); AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
if (!AppHost.LoadGuestApplication().Result) Dispatcher.UIThread.Post(async () =>
{ {
AppHost.DisposeContext(); if (!await AppHost.LoadGuestApplication())
{
AppHost.DisposeContext();
AppHost = null;
return; return;
} }
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName; ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName; ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
SwitchToGameControl(startFullscreen); SwitchToGameControl(startFullscreen);
_currentEmulatedGamePath = path; _currentEmulatedGamePath = path;
Thread gameThread = new Thread(InitializeGame)
{ Thread gameThread = new(InitializeGame)
Name = "GUI.WindowThread" {
}; Name = "GUI.WindowThread"
gameThread.Start(); };
gameThread.Start();
});
} }
private void InitializeGame() private void InitializeGame()
@@ -546,10 +551,12 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{ {
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; {
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
}); });
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Common namespace Ryujinx.Common
@@ -7,49 +8,15 @@ namespace Ryujinx.Common
public static class BinaryReaderExtensions public static class BinaryReaderExtensions
{ {
public unsafe static T ReadStruct<T>(this BinaryReader reader) public unsafe static T ReadStruct<T>(this BinaryReader reader)
where T : struct where T : unmanaged
{ {
int size = Marshal.SizeOf<T>(); return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
byte[] data = reader.ReadBytes(size);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
}
public unsafe static T[] ReadStructArray<T>(this BinaryReader reader, int count)
where T : struct
{
int size = Marshal.SizeOf<T>();
T[] result = new T[count];
for (int i = 0; i < count; i++)
{
byte[] data = reader.ReadBytes(size);
fixed (byte* ptr = data)
{
result[i] = Marshal.PtrToStructure<T>((IntPtr)ptr);
}
}
return result;
} }
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value) public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : struct where T : unmanaged
{ {
long size = Marshal.SizeOf<T>(); ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
writer.Write(data); writer.Write(data);
} }

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Cpu
long TpidrroEl0 { get; set; } long TpidrroEl0 { get; set; }
/// <summary> /// <summary>
/// Processor State register. /// Processor State Register.
/// </summary> /// </summary>
uint Pstate { get; set; } uint Pstate { get; set; }
@@ -109,4 +109,4 @@ namespace Ryujinx.Cpu
/// </remarks> /// </remarks>
void StopRunning(); void StopRunning();
} }
} }

View File

@@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
WriteImpl(va, data); WriteImpl(va, data);
} }
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return false;
}
SignalMemoryTracking(va, (ulong)data.Length, false);
if (IsContiguousAndMapped(va, data.Length))
{
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
else
{
WriteImpl(va, data);
return true;
}
}
/// <summary> /// <summary>
/// Writes data to CPU mapped memory. /// Writes data to CPU mapped memory.
/// </summary> /// </summary>

View File

@@ -307,6 +307,34 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
try
{
SignalMemoryTracking(va, (ulong)data.Length, false);
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return true;
}
}
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {

View File

@@ -1,6 +1,7 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -23,34 +24,18 @@ namespace Ryujinx.Cpu
} }
} }
public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : struct public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : unmanaged
{ {
long size = Marshal.SizeOf<T>(); return MemoryMarshal.Cast<byte, T>(memory.GetSpan(position, Unsafe.SizeOf<T>()))[0];
byte[] data = new byte[size];
memory.Read(position, data);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
} }
public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : struct public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : unmanaged
{ {
long size = Marshal.SizeOf<T>(); ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
memory.Write(position, data); memory.Write(position, data);
return (ulong)size; return (ulong)data.Length;
} }
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1) public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)

View File

@@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL
{
public struct BufferAssignment
{
public readonly int Binding;
public readonly BufferRange Range;
public BufferAssignment(int binding, BufferRange range)
{
Binding = binding;
Range = range;
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsFragmentShaderOrderingIntel; public readonly bool SupportsFragmentShaderOrderingIntel;
public readonly bool SupportsGeometryShaderPassthrough; public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsImageLoadFormatted; public readonly bool SupportsImageLoadFormatted;
public readonly bool SupportsLayerVertexTessellation;
public readonly bool SupportsMismatchingViewFormat; public readonly bool SupportsMismatchingViewFormat;
public readonly bool SupportsCubemapView; public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset; public readonly bool SupportsNonConstantTextureOffset;
@@ -55,6 +56,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsFragmentShaderOrderingIntel, bool supportsFragmentShaderOrderingIntel,
bool supportsGeometryShaderPassthrough, bool supportsGeometryShaderPassthrough,
bool supportsImageLoadFormatted, bool supportsImageLoadFormatted,
bool supportsLayerVertexTessellation,
bool supportsMismatchingViewFormat, bool supportsMismatchingViewFormat,
bool supportsCubemapView, bool supportsCubemapView,
bool supportsNonConstantTextureOffset, bool supportsNonConstantTextureOffset,
@@ -86,6 +88,7 @@ namespace Ryujinx.Graphics.GAL
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsImageLoadFormatted = supportsImageLoadFormatted; SupportsImageLoadFormatted = supportsImageLoadFormatted;
SupportsLayerVertexTessellation = supportsLayerVertexTessellation;
SupportsMismatchingViewFormat = supportsMismatchingViewFormat; SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
SupportsCubemapView = supportsCubemapView; SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;

View File

@@ -86,12 +86,12 @@ namespace Ryujinx.Graphics.GAL
void SetStencilTest(StencilTestDescriptor stencilTest); void SetStencilTest(StencilTestDescriptor stencilTest);
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers); void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers); void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers); void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetUserClipDistance(int index, bool enableClip); void SetUserClipDistance(int index, bool enableClip);

View File

@@ -142,6 +142,30 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return ranges; return ranges;
} }
internal Span<BufferAssignment> MapBufferRanges(Span<BufferAssignment> ranges)
{
// Rewrite the buffer ranges to point to the mapped handles.
lock (_bufferMap)
{
for (int i = 0; i < ranges.Length; i++)
{
ref BufferAssignment assignment = ref ranges[i];
BufferRange range = assignment.Range;
BufferHandle result;
if (!_bufferMap.TryGetValue(range.Handle, out result))
{
result = BufferHandle.Null;
}
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size));
}
}
return ranges;
}
internal Span<VertexBufferDescriptor> MapBufferRanges(Span<VertexBufferDescriptor> ranges) internal Span<VertexBufferDescriptor> MapBufferRanges(Span<VertexBufferDescriptor> ranges)
{ {
// Rewrite the buffer ranges to point to the mapped handles. // Rewrite the buffer ranges to point to the mapped handles.

View File

@@ -6,19 +6,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetStorageBuffersCommand : IGALCommand struct SetStorageBuffersCommand : IGALCommand
{ {
public CommandType CommandType => CommandType.SetStorageBuffers; public CommandType CommandType => CommandType.SetStorageBuffers;
private int _first; private SpanRef<BufferAssignment> _buffers;
private SpanRef<BufferRange> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers) public void Set(SpanRef<BufferAssignment> buffers)
{ {
_first = first;
_buffers = buffers; _buffers = buffers;
} }
public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
Span<BufferRange> buffers = command._buffers.Get(threaded); Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetStorageBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers)); renderer.Pipeline.SetStorageBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded); command._buffers.Dispose(threaded);
} }
} }

View File

@@ -6,19 +6,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetUniformBuffersCommand : IGALCommand struct SetUniformBuffersCommand : IGALCommand
{ {
public CommandType CommandType => CommandType.SetUniformBuffers; public CommandType CommandType => CommandType.SetUniformBuffers;
private int _first; private SpanRef<BufferAssignment> _buffers;
private SpanRef<BufferRange> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers) public void Set(SpanRef<BufferAssignment> buffers)
{ {
_first = first;
_buffers = buffers; _buffers = buffers;
} }
public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
Span<BufferRange> buffers = command._buffers.Get(threaded); Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetUniformBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers)); renderer.Pipeline.SetUniformBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded); command._buffers.Dispose(threaded);
} }
} }

View File

@@ -275,9 +275,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_renderer.New<SetStorageBuffersCommand>().Set(first, _renderer.CopySpan(buffers)); _renderer.New<SetStorageBuffersCommand>().Set(_renderer.CopySpan(buffers));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
@@ -293,9 +293,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_renderer.New<SetUniformBuffersCommand>().Set(first, _renderer.CopySpan(buffers)); _renderer.New<SetUniformBuffersCommand>().Set(_renderer.CopySpan(buffers));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View File

@@ -422,7 +422,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
// Stop the GPU thread. // Stop the GPU thread.
_disposed = true; _disposed = true;
_gpuThread.Join();
if (_gpuThread != null && _gpuThread.IsAlive)
{
_gpuThread.Join();
}
// Dispose the renderer. // Dispose the renderer.
_baseRenderer.Dispose(); _baseRenderer.Dispose();
@@ -435,4 +439,4 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Sync.Dispose(); Sync.Dispose();
} }
} }
} }

View File

@@ -95,5 +95,10 @@ namespace Ryujinx.Graphics.Gpu
/// Byte alignment for block linear textures /// Byte alignment for block linear textures
/// </summary> /// </summary>
public const int GobAlignment = 64; public const int GobAlignment = 64;
/// <summary>
/// Expected byte alignment for storage buffers
/// </summary>
public const int StorageAlignment = 16;
} }
} }

View File

@@ -138,7 +138,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
qmd.CtaThreadDimension1, qmd.CtaThreadDimension1,
qmd.CtaThreadDimension2, qmd.CtaThreadDimension2,
localMemorySize, localMemorySize,
sharedMemorySize); sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
@@ -150,6 +151,33 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ShaderProgramInfo info = cs.Shaders[0].Info; ShaderProgramInfo info = cs.Shaders[0].Info;
bool hasUnaligned = _channel.BufferManager.HasUnalignedStorageBuffers;
for (int index = 0; index < info.SBuffers.Count; index++)
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
int sbDescOffset = 0x310 + sb.Slot * 0x10;
sbDescAddress += (ulong)sbDescOffset;
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
}
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)
{
// Refetch the shader, as assumptions about storage buffer alignment have changed.
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
info = cs.Shaders[0].Info;
}
for (int index = 0; index < info.CBuffers.Count; index++) for (int index = 0; index < info.CBuffers.Count; index++)
{ {
BufferDescriptor cb = info.CBuffers[index]; BufferDescriptor cb = info.CBuffers[index];
@@ -174,21 +202,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size); _channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size);
} }
for (int index = 0; index < info.SBuffers.Count; index++)
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
int sbDescOffset = 0x310 + sb.Slot * 0x10;
sbDescAddress += (ulong)sbDescOffset;
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
}
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers); _channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers); _channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);

View File

@@ -51,16 +51,35 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
/// </summary> /// </summary>
public uint EntryCount; public uint EntryCount;
/// <summary>
/// Get the entries for the command buffer from memory.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
/// <returns>The fetched data</returns>
private ReadOnlySpan<int> GetWords(MemoryManager memoryManager, bool flush)
{
return MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush));
}
/// <summary>
/// Prefetch the command buffer.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
public void Prefetch(MemoryManager memoryManager)
{
Words = GetWords(memoryManager, true).ToArray();
}
/// <summary> /// <summary>
/// Fetch the command buffer. /// Fetch the command buffer.
/// </summary> /// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param> /// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
public void Fetch(MemoryManager memoryManager, bool flush = true) /// <returns>The command buffer words</returns>
public ReadOnlySpan<int> Fetch(MemoryManager memoryManager, bool flush)
{ {
if (Words == null) return Words ?? GetWords(memoryManager, flush);
{
Words = MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)).ToArray();
}
} }
} }
@@ -158,7 +177,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
{ {
commandBuffer.Fetch(processor.MemoryManager); commandBuffer.Prefetch(processor.MemoryManager);
} }
if (commandBuffer.Type == CommandBufferType.NoPrefetch) if (commandBuffer.Type == CommandBufferType.NoPrefetch)
@@ -199,7 +218,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
} }
_currentCommandBuffer = entry; _currentCommandBuffer = entry;
_currentCommandBuffer.Fetch(entry.Processor.MemoryManager, flushCommandBuffer); ReadOnlySpan<int> words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
// If we are changing the current channel, // If we are changing the current channel,
// we need to force all the host state to be updated. // we need to force all the host state to be updated.
@@ -209,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
entry.Processor.ForceAllDirty(); entry.Processor.ForceAllDirty();
} }
entry.Processor.Process(entry.EntryAddress, _currentCommandBuffer.Words); entry.Processor.Process(entry.EntryAddress, words);
} }
_interrupt = false; _interrupt = false;

View File

@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
class ConstantBufferUpdater class ConstantBufferUpdater
{ {
private const int UniformDataCacheSize = 512;
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly DeviceStateWithShadow<ThreedClassState> _state; private readonly DeviceStateWithShadow<ThreedClassState> _state;
@@ -16,6 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private ulong _ubBeginCpuAddress = 0; private ulong _ubBeginCpuAddress = 0;
private ulong _ubFollowUpAddress = 0; private ulong _ubFollowUpAddress = 0;
private ulong _ubByteCount = 0; private ulong _ubByteCount = 0;
private int _ubIndex = 0;
private int[] _ubData = new int[UniformDataCacheSize];
/// <summary> /// <summary>
/// Creates a new instance of the constant buffer updater. /// Creates a new instance of the constant buffer updater.
@@ -108,9 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (_ubFollowUpAddress != 0) if (_ubFollowUpAddress != 0)
{ {
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
{
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
}
_ubFollowUpAddress = 0; _ubFollowUpAddress = 0;
_ubIndex = 0;
} }
} }
@@ -124,7 +135,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;
if (_ubFollowUpAddress != address) if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length)
{ {
FlushUboDirty(); FlushUboDirty();
@@ -132,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address); _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
} }
var byteData = MemoryMarshal.Cast<int, byte>(MemoryMarshal.CreateSpan(ref argument, 1)); _ubData[_ubIndex++] = argument;
_channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData);
_ubFollowUpAddress = address + 4; _ubFollowUpAddress = address + 4;
_ubByteCount += 4; _ubByteCount += 4;
@@ -153,7 +163,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong size = (ulong)data.Length * 4; ulong size = (ulong)data.Length * 4;
if (_ubFollowUpAddress != address) if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length)
{ {
FlushUboDirty(); FlushUboDirty();
@@ -161,8 +171,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address); _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
} }
var byteData = MemoryMarshal.Cast<int, byte>(data); data.CopyTo(_ubData.AsSpan(_ubIndex));
_channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); _ubIndex += data.Length;
_ubFollowUpAddress = address + size; _ubFollowUpAddress = address + size;
_ubByteCount += size; _ubByteCount += size;

View File

@@ -372,7 +372,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0; float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0;
float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0; float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0;
engine.UpdateState(); engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
_channel.TextureManager.UpdateRenderTargets();
int textureId = _state.State.DrawTextureTextureId; int textureId = _state.State.DrawTextureTextureId;
int samplerId = _state.State.DrawTextureSamplerId; int samplerId = _state.State.DrawTextureSamplerId;

View File

@@ -293,9 +293,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
private void CommitBindings() private void CommitBindings()
{ {
var buffers = _channel.BufferManager;
var hasUnaligned = buffers.HasUnalignedStorageBuffers;
UpdateStorageBuffers(); UpdateStorageBuffers();
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState)) if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || (buffers.HasUnalignedStorageBuffers != hasUnaligned))
{ {
// Shader must be reloaded. // Shader must be reloaded.
UpdateShaderState(); UpdateShaderState();
@@ -1361,7 +1364,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_state.State.AlphaTestFunc, _state.State.AlphaTestFunc,
_state.State.AlphaTestRef, _state.State.AlphaTestRef,
ref attributeTypes, ref attributeTypes,
_drawState.HasConstantBufferDrawParameters); _drawState.HasConstantBufferDrawParameters,
_channel.BufferManager.HasUnalignedStorageBuffers);
} }
/// <summary> /// <summary>

View File

@@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Gpu
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager. // Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
TextureManager.ReloadPools(); TextureManager.ReloadPools();
MemoryManager.Physical.BufferCache.QueuePrune();
} }
/// <summary> /// <summary>
@@ -77,6 +78,7 @@ namespace Ryujinx.Graphics.Gpu
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e) private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
{ {
TextureManager.ReloadPools(); TextureManager.ReloadPools();
MemoryManager.Physical.BufferCache.QueuePrune();
} }
/// <summary> /// <summary>
@@ -131,6 +133,7 @@ namespace Ryujinx.Graphics.Gpu
{ {
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind; oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
oldMemoryManager.Physical.DecrementReferenceCount(); oldMemoryManager.Physical.DecrementReferenceCount();
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
} }
} }
} }

View File

@@ -22,12 +22,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly PhysicalMemory _physicalMemory; private readonly PhysicalMemory _physicalMemory;
/// <remarks>
/// Only modified from the GPU thread. Must lock for add/remove.
/// Must lock for any access from other threads.
/// </remarks>
private readonly RangeList<Buffer> _buffers; private readonly RangeList<Buffer> _buffers;
private Buffer[] _bufferOverlaps; private Buffer[] _bufferOverlaps;
private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache; private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache; private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache;
private bool _pruneCaches;
public event Action NotifyBuffersModified; public event Action NotifyBuffersModified;
@@ -136,6 +141,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
public void ForceDirty(MemoryManager memoryManager, ulong gpuVa, ulong size) public void ForceDirty(MemoryManager memoryManager, ulong gpuVa, ulong size)
{ {
if (_pruneCaches)
{
Prune();
}
if (!_dirtyCache.TryGetValue(gpuVa, out BufferCacheEntry result) || if (!_dirtyCache.TryGetValue(gpuVa, out BufferCacheEntry result) ||
result.EndGpuAddress < gpuVa + size || result.EndGpuAddress < gpuVa + size ||
result.UnmappedSequence != result.Buffer.UnmappedSequence) result.UnmappedSequence != result.Buffer.UnmappedSequence)
@@ -158,17 +168,29 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <returns>True if modified, false otherwise</returns> /// <returns>True if modified, false otherwise</returns>
public bool CheckModified(MemoryManager memoryManager, ulong gpuVa, ulong size, out ulong outAddr) public bool CheckModified(MemoryManager memoryManager, ulong gpuVa, ulong size, out ulong outAddr)
{ {
if (!_modifiedCache.TryGetValue(gpuVa, out BufferCacheEntry result) || if (_pruneCaches)
result.EndGpuAddress < gpuVa + size ||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
{ {
ulong address = TranslateAndCreateBuffer(memoryManager, gpuVa, size); Prune();
result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size));
_modifiedCache[gpuVa] = result;
} }
outAddr = result.Address; // Align the address to avoid creating too many entries on the quick lookup dictionary.
ulong mask = BufferAlignmentMask;
ulong alignedGpuVa = gpuVa & (~mask);
ulong alignedEndGpuVa = (gpuVa + size + mask) & (~mask);
size = alignedEndGpuVa - alignedGpuVa;
if (!_modifiedCache.TryGetValue(alignedGpuVa, out BufferCacheEntry result) ||
result.EndGpuAddress < alignedEndGpuVa ||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
{
ulong address = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size);
result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size));
_modifiedCache[alignedGpuVa] = result;
}
outAddr = result.Address | (gpuVa & mask);
return result.Buffer.IsModified(result.Address, size); return result.Buffer.IsModified(result.Address, size);
} }
@@ -182,12 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size) private void CreateBufferAligned(ulong address, ulong size)
{ {
int overlapsCount; int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
lock (_buffers)
{
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
}
if (overlapsCount != 0) if (overlapsCount != 0)
{ {
@@ -392,10 +409,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0) if (size != 0)
{ {
lock (_buffers) buffer = _buffers.FindFirstOverlap(address, size);
{
buffer = _buffers.FindFirstOverlap(address, size);
}
buffer.SynchronizeMemory(address, size); buffer.SynchronizeMemory(address, size);
@@ -406,10 +420,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
lock (_buffers) buffer = _buffers.FindFirstOverlap(address, 1);
{
buffer = _buffers.FindFirstOverlap(address, 1);
}
} }
return buffer; return buffer;
@@ -424,17 +435,60 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (size != 0) if (size != 0)
{ {
Buffer buffer; Buffer buffer = _buffers.FindFirstOverlap(address, size);
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, size);
}
buffer.SynchronizeMemory(address, size); buffer.SynchronizeMemory(address, size);
} }
} }
/// <summary>
/// Prune any invalid entries from a quick access dictionary.
/// </summary>
/// <param name="dictionary">Dictionary to prune</param>
/// <param name="toDelete">List used to track entries to delete</param>
private void Prune(Dictionary<ulong, BufferCacheEntry> dictionary, ref List<ulong> toDelete)
{
foreach (var entry in dictionary)
{
if (entry.Value.UnmappedSequence != entry.Value.Buffer.UnmappedSequence)
{
(toDelete ??= new()).Add(entry.Key);
}
}
if (toDelete != null)
{
foreach (ulong entry in toDelete)
{
dictionary.Remove(entry);
}
}
}
/// <summary>
/// Prune any invalid entries from the quick access dictionaries.
/// </summary>
private void Prune()
{
List<ulong> toDelete = null;
Prune(_dirtyCache, ref toDelete);
toDelete?.Clear();
Prune(_modifiedCache, ref toDelete);
_pruneCaches = false;
}
/// <summary>
/// Queues a prune of invalid entries the next time a dictionary cache is accessed.
/// </summary>
public void QueuePrune()
{
_pruneCaches = true;
}
/// <summary> /// <summary>
/// Disposes all buffers in the cache. /// Disposes all buffers in the cache.
/// It's an error to use the buffer manager after disposal. /// It's an error to use the buffer manager after disposal.

View File

@@ -17,11 +17,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private int _unalignedStorageBuffers;
public bool HasUnalignedStorageBuffers => _unalignedStorageBuffers > 0;
private IndexBuffer _indexBuffer; private IndexBuffer _indexBuffer;
private readonly VertexBuffer[] _vertexBuffers; private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers; private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures; private readonly List<BufferTextureBinding> _bufferTextures;
private readonly BufferRange[] _ranges; private readonly BufferAssignment[] _ranges;
/// <summary> /// <summary>
/// Holds shader stage buffer state and binding information. /// Holds shader stage buffer state and binding information.
@@ -38,6 +41,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public BufferBounds[] Buffers { get; } public BufferBounds[] Buffers { get; }
/// <summary>
/// Flag indicating if this binding is unaligned.
/// </summary>
public bool[] Unaligned { get; }
/// <summary> /// <summary>
/// Total amount of buffers used on the shader. /// Total amount of buffers used on the shader.
/// </summary> /// </summary>
@@ -51,6 +59,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
Bindings = new BufferDescriptor[count]; Bindings = new BufferDescriptor[count];
Buffers = new BufferBounds[count]; Buffers = new BufferBounds[count];
Unaligned = new bool[count];
} }
/// <summary> /// <summary>
@@ -125,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures = new List<BufferTextureBinding>(); _bufferTextures = new List<BufferTextureBinding>();
_ranges = new BufferRange[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
} }
@@ -202,6 +211,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
_transformFeedbackBuffersDirty = true; _transformFeedbackBuffersDirty = true;
} }
/// <summary>
/// Records the alignment of a storage buffer.
/// Unaligned storage buffers disable some optimizations on the shader.
/// </summary>
/// <param name="buffers">The binding list to modify</param>
/// <param name="index">Index of the storage buffer</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
private void RecordStorageAlignment(BuffersPerStage buffers, int index, ulong gpuVa)
{
bool unaligned = (gpuVa & (Constants.StorageAlignment - 1)) != 0;
if (unaligned || HasUnalignedStorageBuffers)
{
// Check if the alignment changed for this binding.
ref bool currentUnaligned = ref buffers.Unaligned[index];
if (currentUnaligned != unaligned)
{
currentUnaligned = unaligned;
_unalignedStorageBuffers += unaligned ? 1 : -1;
}
}
}
/// <summary> /// <summary>
/// Sets a storage buffer on the compute pipeline. /// Sets a storage buffer on the compute pipeline.
/// Storage buffers can be read and written to on shaders. /// Storage buffers can be read and written to on shaders.
@@ -214,6 +248,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1);
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment);
ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
@@ -234,17 +270,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1);
BuffersPerStage buffers = _gpStorageBuffers[stage];
RecordStorageAlignment(buffers, index, gpuVa);
gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment);
ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
if (_gpStorageBuffers[stage].Buffers[index].Address != address || if (buffers.Buffers[index].Address != address ||
_gpStorageBuffers[stage].Buffers[index].Size != size) buffers.Buffers[index].Size != size)
{ {
_gpStorageBuffersDirty = true; _gpStorageBuffersDirty = true;
} }
_gpStorageBuffers[stage].SetBounds(index, address, size, flags); buffers.SetBounds(index, address, size, flags);
} }
/// <summary> /// <summary>
@@ -578,10 +618,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage) private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
{ {
int rangesFirst = 0;
int rangesCount = 0; int rangesCount = 0;
Span<BufferRange> ranges = _ranges; Span<BufferAssignment> ranges = _ranges;
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{ {
@@ -600,25 +639,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite) ? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size); : bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0) ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
} }
} }
} }
if (rangesCount != 0) if (rangesCount != 0)
{ {
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage); SetHostBuffers(ranges, rangesCount, isStorage);
} }
} }
@@ -631,10 +659,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage) private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
{ {
int rangesFirst = 0;
int rangesCount = 0; int rangesCount = 0;
Span<BufferRange> ranges = _ranges; Span<BufferAssignment> ranges = _ranges;
for (int index = 0; index < buffers.Count; index++) for (int index = 0; index < buffers.Count; index++)
{ {
@@ -649,24 +676,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite) ? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size); : bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0) ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
} }
} }
if (rangesCount != 0) if (rangesCount != 0)
{ {
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage); SetHostBuffers(ranges, rangesCount, isStorage);
} }
} }
@@ -678,15 +694,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="count">Number of bindings</param> /// <param name="count">Number of bindings</param>
/// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param> /// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetHostBuffers(ReadOnlySpan<BufferRange> ranges, int first, int count, bool isStorage) private void SetHostBuffers(ReadOnlySpan<BufferAssignment> ranges, int count, bool isStorage)
{ {
if (isStorage) if (isStorage)
{ {
_context.Renderer.Pipeline.SetStorageBuffers(first, ranges.Slice(0, count)); _context.Renderer.Pipeline.SetStorageBuffers(ranges.Slice(0, count));
} }
else else
{ {
_context.Renderer.Pipeline.SetUniformBuffers(first, ranges.Slice(0, count)); _context.Renderer.Pipeline.SetUniformBuffers(ranges.Slice(0, count));
} }
} }

View File

@@ -325,13 +325,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void ReregisterRanges(Action<ulong, ulong> rangeAction) public void ReregisterRanges(Action<ulong, ulong> rangeAction)
{ {
ref var ranges = ref ThreadStaticArray<BufferModifiedRange>.Get(); ref var ranges = ref ThreadStaticArray<BufferModifiedRange>.Get();
int count;
// Range list must be consistent for this operation. // Range list must be consistent for this operation.
lock (_lock) lock (_lock)
{ {
if (ranges.Length < Count) count = Count;
if (ranges.Length < count)
{ {
Array.Resize(ref ranges, Count); Array.Resize(ref ranges, count);
} }
int i = 0; int i = 0;
@@ -342,7 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
ulong currentSync = _context.SyncNumber; ulong currentSync = _context.SyncNumber;
for (int i = 0; i < Count; i++) for (int i = 0; i < count; i++)
{ {
BufferModifiedRange range = ranges[i]; BufferModifiedRange range = ranges[i];
if (range.SyncNumber != currentSync) if (range.SyncNumber != currentSync)

View File

@@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
WriteImpl(range, data, _cpuMemory.WriteUntracked); WriteImpl(range, data, _cpuMemory.WriteUntracked);
} }
/// <summary>
/// Writes data to the application process, returning false if the data was not changed.
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
/// </summary>
/// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
/// <param name="address">Address to write into</param>
/// <param name="data">Data to be written</param>
/// <returns>True if the data was changed, false otherwise</returns>
public bool WriteWithRedundancyCheck(ulong address, ReadOnlySpan<byte> data)
{
return _cpuMemory.WriteWithRedundancyCheck(address, data);
}
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data); private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
/// <summary> /// <summary>

View File

@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="computeState">Compute state</param>
/// <param name="gpuVa">GPU virtual address of the compute shader</param> /// <param name="gpuVa">GPU virtual address of the compute shader</param>
/// <param name="program">Cached host program for the given state, if found</param> /// <param name="program">Cached host program for the given state, if found</param>
/// <param name="cachedGuestCode">Cached guest code, if any found</param> /// <param name="cachedGuestCode">Cached guest code, if any found</param>
@@ -43,6 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool TryFind( public bool TryFind(
GpuChannel channel, GpuChannel channel,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelComputeState computeState,
ulong gpuVa, ulong gpuVa,
out CachedShaderProgram program, out CachedShaderProgram program,
out byte[] cachedGuestCode) out byte[] cachedGuestCode)
@@ -50,7 +52,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
program = null; program = null;
ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(channel.MemoryManager, gpuVa); ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(channel.MemoryManager, gpuVa);
bool hasSpecList = _cache.TryFindItem(codeAccessor, out var specList, out cachedGuestCode); bool hasSpecList = _cache.TryFindItem(codeAccessor, out var specList, out cachedGuestCode);
return hasSpecList && specList.TryFindForCompute(channel, poolState, out program); return hasSpecList && specList.TryFindForCompute(channel, poolState, computeState, out program);
} }
/// <summary> /// <summary>

View File

@@ -225,6 +225,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.EarlyZForce; return _oldSpecState.GraphicsState.EarlyZForce;
} }
/// <inheritdoc/>
public bool QueryHasUnalignedStorageBuffer()
{
return _oldSpecState.GraphicsState.HasUnalignedStorageBuffer || _oldSpecState.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryViewportTransformDisable() public bool QueryViewportTransformDisable()
{ {

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 3747; private const uint CodeGenVersion = 3868;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";
@@ -379,7 +379,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (context.Capabilities.Api == TargetApi.Vulkan) if (context.Capabilities.Api == TargetApi.Vulkan)
{ {
ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode, isCompute); ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode);
hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo); hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo);
} }

View File

@@ -636,6 +636,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
List<ShaderProgram> translatedStages = new List<ShaderProgram>(); List<ShaderProgram> translatedStages = new List<ShaderProgram>();
TranslatorContext previousStage = null;
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
{ {
TranslatorContext currentStage = translatorContexts[stageIndex + 1]; TranslatorContext currentStage = translatorContexts[stageIndex + 1];
@@ -668,6 +670,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{ {
translatedStages.Add(program); translatedStages.Add(program);
} }
previousStage = currentStage;
}
else if (
previousStage != null &&
previousStage.LayerOutputWritten &&
stageIndex == 3 &&
!_context.Capabilities.SupportsLayerVertexTessellation)
{
translatedStages.Add(previousStage.GenerateGeometryPassthrough());
} }
} }

View File

@@ -1,5 +1,7 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -12,8 +14,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
using MemoryStream output = new MemoryStream(); using MemoryStream output = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(output); using BinaryWriter writer = new BinaryWriter(output);
writer.Write(sources.Length);
for (int i = 0; i < sources.Length; i++) for (int i = 0; i < sources.Length; i++)
{ {
writer.Write((int)sources[i].Stage);
writer.Write(sources[i].BinaryCode.Length); writer.Write(sources[i].BinaryCode.Length);
writer.Write(sources[i].BinaryCode); writer.Write(sources[i].BinaryCode);
} }
@@ -21,29 +26,40 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return output.ToArray(); return output.ToArray();
} }
public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code, bool compute) public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code)
{ {
using MemoryStream input = new MemoryStream(code); using MemoryStream input = new MemoryStream(code);
using BinaryReader reader = new BinaryReader(input); using BinaryReader reader = new BinaryReader(input);
List<ShaderSource> output = new List<ShaderSource>(); List<ShaderSource> output = new List<ShaderSource>();
for (int i = compute ? 0 : 1; i < stages.Length; i++) int count = reader.ReadInt32();
for (int i = 0; i < count; i++)
{ {
CachedShaderStage stage = stages[i]; ShaderStage stage = (ShaderStage)reader.ReadInt32();
if (stage == null)
{
continue;
}
int binaryCodeLength = reader.ReadInt32(); int binaryCodeLength = reader.ReadInt32();
byte[] binaryCode = reader.ReadBytes(binaryCodeLength); byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
output.Add(new ShaderSource(binaryCode, ShaderCache.GetBindings(stage.Info), stage.Info.Stage, TargetLanguage.Spirv)); output.Add(new ShaderSource(binaryCode, GetBindings(stages, stage), stage, TargetLanguage.Spirv));
} }
return output.ToArray(); return output.ToArray();
} }
private static ShaderBindings GetBindings(CachedShaderStage[] stages, ShaderStage stage)
{
for (int i = 0; i < stages.Length; i++)
{
CachedShaderStage currentStage = stages[i];
if (currentStage != null && currentStage.Info.Stage == stage && currentStage.Info != null)
{
return ShaderCache.GetBindings(currentStage.Info);
}
}
return new ShaderBindings(Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
}
} }
} }

View File

@@ -145,6 +145,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasConstantBufferDrawParameters; return _state.GraphicsState.HasConstantBufferDrawParameters;
} }
/// <inheritdoc/>
public bool QueryHasUnalignedStorageBuffer()
{
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/> /// <inheritdoc/>
public InputTopology QueryPrimitiveTopology() public InputTopology QueryPrimitiveTopology()
{ {

View File

@@ -128,6 +128,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted; public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
public bool QueryHostSupportsLayerVertexTessellation() => _context.Capabilities.SupportsLayerVertexTessellation;
public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset; public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;

View File

@@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
public readonly int SharedMemorySize; public readonly int SharedMemorySize;
/// <summary>
/// Indicates that any storage buffer use is unaligned.
/// </summary>
public readonly bool HasUnalignedStorageBuffer;
/// <summary> /// <summary>
/// Creates a new GPU compute state. /// Creates a new GPU compute state.
/// </summary> /// </summary>
@@ -40,18 +45,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="localSizeZ">Local group size Z of the compute shader</param> /// <param name="localSizeZ">Local group size Z of the compute shader</param>
/// <param name="localMemorySize">Local memory size of the compute shader</param> /// <param name="localMemorySize">Local memory size of the compute shader</param>
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param> /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
public GpuChannelComputeState( public GpuChannelComputeState(
int localSizeX, int localSizeX,
int localSizeY, int localSizeY,
int localSizeZ, int localSizeZ,
int localMemorySize, int localMemorySize,
int sharedMemorySize) int sharedMemorySize,
bool hasUnalignedStorageBuffer)
{ {
LocalSizeX = localSizeX; LocalSizeX = localSizeX;
LocalSizeY = localSizeY; LocalSizeY = localSizeY;
LocalSizeZ = localSizeZ; LocalSizeZ = localSizeZ;
LocalMemorySize = localMemorySize; LocalMemorySize = localMemorySize;
SharedMemorySize = sharedMemorySize; SharedMemorySize = sharedMemorySize;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
} }
} }
} }

View File

@@ -82,6 +82,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
public readonly bool HasConstantBufferDrawParameters; public readonly bool HasConstantBufferDrawParameters;
/// <summary>
/// Indicates that any storage buffer use is unaligned.
/// </summary>
public readonly bool HasUnalignedStorageBuffer;
/// <summary> /// <summary>
/// Creates a new GPU graphics state. /// Creates a new GPU graphics state.
/// </summary> /// </summary>
@@ -99,6 +104,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param> /// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param> /// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param> /// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
public GpuChannelGraphicsState( public GpuChannelGraphicsState(
bool earlyZForce, bool earlyZForce,
PrimitiveTopology topology, PrimitiveTopology topology,
@@ -113,7 +119,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
CompareOp alphaTestCompare, CompareOp alphaTestCompare,
float alphaTestReference, float alphaTestReference,
ref Array32<AttributeType> attributeTypes, ref Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters) bool hasConstantBufferDrawParameters,
bool hasUnalignedStorageBuffer)
{ {
EarlyZForce = earlyZForce; EarlyZForce = earlyZForce;
Topology = topology; Topology = topology;
@@ -129,6 +136,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
AlphaTestReference = alphaTestReference; AlphaTestReference = alphaTestReference;
AttributeTypes = attributeTypes; AttributeTypes = attributeTypes;
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters; HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
} }
} }
} }

View File

@@ -203,12 +203,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuChannelComputeState computeState, GpuChannelComputeState computeState,
ulong gpuVa) ulong gpuVa)
{ {
if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, cpShader, gpuVa)) if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
{ {
return cpShader; return cpShader;
} }
if (_computeShaderCache.TryFind(channel, poolState, gpuVa, out cpShader, out byte[] cachedGuestCode)) if (_computeShaderCache.TryFind(channel, poolState, computeState, gpuVa, out cpShader, out byte[] cachedGuestCode))
{ {
_cpPrograms[gpuVa] = cpShader; _cpPrograms[gpuVa] = cpShader;
return cpShader; return cpShader;
@@ -356,6 +356,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
List<ShaderSource> shaderSources = new List<ShaderSource>(); List<ShaderSource> shaderSources = new List<ShaderSource>();
TranslatorContext previousStage = null;
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
{ {
TranslatorContext currentStage = translatorContexts[stageIndex + 1]; TranslatorContext currentStage = translatorContexts[stageIndex + 1];
@@ -392,6 +394,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
shaderSources.Add(CreateShaderSource(program)); shaderSources.Add(CreateShaderSource(program));
} }
previousStage = currentStage;
}
else if (
previousStage != null &&
previousStage.LayerOutputWritten &&
stageIndex == 3 &&
!_context.Capabilities.SupportsLayerVertexTessellation)
{
shaderSources.Add(CreateShaderSource(previousStage.GenerateGeometryPassthrough()));
} }
} }
@@ -473,18 +485,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel using the shader</param> /// <param name="channel">GPU channel using the shader</param>
/// <param name="poolState">GPU channel state to verify shader compatibility</param> /// <param name="poolState">GPU channel state to verify shader compatibility</param>
/// <param name="computeState">GPU channel compute state to verify shader compatibility</param>
/// <param name="cpShader">Cached compute shader</param> /// <param name="cpShader">Cached compute shader</param>
/// <param name="gpuVa">GPU virtual address of the shader code in memory</param> /// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
/// <returns>True if the code is different, false otherwise</returns> /// <returns>True if the code is different, false otherwise</returns>
private static bool IsShaderEqual( private static bool IsShaderEqual(
GpuChannel channel, GpuChannel channel,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelComputeState computeState,
CachedShaderProgram cpShader, CachedShaderProgram cpShader,
ulong gpuVa) ulong gpuVa)
{ {
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa)) if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
{ {
return cpShader.SpecializationState.MatchesCompute(channel, poolState, true); return cpShader.SpecializationState.MatchesCompute(channel, poolState, computeState, true);
} }
return false; return false;

View File

@@ -53,13 +53,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="computeState">Compute state</param>
/// <param name="program">Cached program, if found</param> /// <param name="program">Cached program, if found</param>
/// <returns>True if a compatible program is found, false otherwise</returns> /// <returns>True if a compatible program is found, false otherwise</returns>
public bool TryFindForCompute(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program) public bool TryFindForCompute(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelComputeState computeState, out CachedShaderProgram program)
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesCompute(channel, poolState, true)) if (entry.SpecializationState.MatchesCompute(channel, poolState, computeState, true))
{ {
program = entry; program = entry;
return true; return true;

View File

@@ -531,6 +531,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
return false; return false;
} }
if (graphicsState.HasUnalignedStorageBuffer != GraphicsState.HasUnalignedStorageBuffer)
{
return false;
}
return Matches(channel, poolState, checkTextures, isCompute: false); return Matches(channel, poolState, checkTextures, isCompute: false);
} }
@@ -539,10 +544,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="computeState">Compute state</param>
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param> /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <returns>True if the state matches, false otherwise</returns> /// <returns>True if the state matches, false otherwise</returns>
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures) public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelComputeState computeState, bool checkTextures)
{ {
if (computeState.HasUnalignedStorageBuffer != ComputeState.HasUnalignedStorageBuffer)
{
return false;
}
return Matches(channel, poolState, checkTextures, isCompute: true); return Matches(channel, poolState, checkTextures, isCompute: true);
} }

View File

@@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
short dqv = dq[0]; short dqv = dq[0];
ReadOnlySpan<byte> cat6Prob = (xd.Bd == 12) ReadOnlySpan<byte> cat6Prob = (xd.Bd == 12)
? Luts.Vp9Cat6ProbHigh12 ? Luts.Vp9Cat6ProbHigh12
: (xd.Bd == 10) ? new ReadOnlySpan<byte>(Luts.Vp9Cat6ProbHigh12).Slice(2) : Luts.Vp9Cat6Prob; : (xd.Bd == 10) ? Luts.Vp9Cat6ProbHigh12.Slice(2) : Luts.Vp9Cat6Prob;
int cat6Bits = (xd.Bd == 12) ? 18 : (xd.Bd == 10) ? 16 : 14; int cat6Bits = (xd.Bd == 12) ? 18 : (xd.Bd == 10) ? 16 : 14;
// Keep value, range, and count as locals. The compiler produces better // Keep value, range, and count as locals. The compiler produces better
// results with the locals than using r directly. // results with the locals than using r directly.

View File

@@ -1,14 +1,12 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Nvdec.Vp9.Types; using Ryujinx.Graphics.Nvdec.Vp9.Types;
using System;
namespace Ryujinx.Graphics.Nvdec.Vp9 namespace Ryujinx.Graphics.Nvdec.Vp9
{ {
internal static class Luts internal static class Luts
{ {
public static readonly byte[] SizeGroupLookup = new byte[] public static ReadOnlySpan<byte> SizeGroupLookup => new byte[] { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3 };
{
0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3
};
public static readonly BlockSize[][] SubsizeLookup = new BlockSize[][] public static readonly BlockSize[][] SubsizeLookup = new BlockSize[][]
{ {
@@ -1070,18 +1068,18 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
-(sbyte)MvClassType.MvClass10, -(sbyte)MvClassType.MvClass10,
}; };
public static readonly sbyte[] Vp9MvFPTree = new sbyte[] { -0, 2, -1, 4, -2, -3 }; public static ReadOnlySpan<sbyte> Vp9MvFPTree => new sbyte[] { -0, 2, -1, 4, -2, -3 };
// Entropy // Entropy
public static readonly byte[] Vp9Cat1Prob = new byte[] { 159 }; public static ReadOnlySpan<byte> Vp9Cat1Prob => new byte[] { 159 };
public static readonly byte[] Vp9Cat2Prob = new byte[] { 165, 145 }; public static ReadOnlySpan<byte> Vp9Cat2Prob => new byte[] { 165, 145 };
public static readonly byte[] Vp9Cat3Prob = new byte[] { 173, 148, 140 }; public static ReadOnlySpan<byte> Vp9Cat3Prob => new byte[] { 173, 148, 140 };
public static readonly byte[] Vp9Cat4Prob = new byte[] { 176, 155, 140, 135 }; public static ReadOnlySpan<byte> Vp9Cat4Prob => new byte[] { 176, 155, 140, 135 };
public static readonly byte[] Vp9Cat5Prob = new byte[] { 180, 157, 141, 134, 130 }; public static ReadOnlySpan<byte> Vp9Cat5Prob => new byte[] { 180, 157, 141, 134, 130 };
public static readonly byte[] Vp9Cat6Prob = new byte[] { 254, 254, 254, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; public static ReadOnlySpan<byte> Vp9Cat6Prob => new byte[] { 254, 254, 254, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129 };
public static readonly byte[] Vp9Cat6ProbHigh12 = new byte[] public static ReadOnlySpan<byte> Vp9Cat6ProbHigh12 => new byte[]
{ {
255, 255, 255, 255, 254, 254, 54, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129 255, 255, 255, 255, 254, 254, 54, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129
}; };
@@ -1131,12 +1129,12 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
}; };
private static readonly byte[] Vp9CoefbandTrans4X4 = new byte[] private static ReadOnlySpan<byte> Vp9CoefbandTrans4X4 => new byte[]
{ {
0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5,
}; };
public static byte[] get_band_translate(TxSize txSize) public static ReadOnlySpan<byte> get_band_translate(TxSize txSize)
{ {
return txSize == TxSize.Tx4x4 ? Vp9CoefbandTrans4X4 : Vp9CoefbandTrans8X8Plus; return txSize == TxSize.Tx4x4 ? Vp9CoefbandTrans4X4 : Vp9CoefbandTrans8X8Plus;
} }

View File

@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.OpenGL
private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck); private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck);
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture")); private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot")); private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
private static readonly Lazy<bool> _supportsShaderViewportLayerArray = new Lazy<bool>(() => HasExtension("GL_ARB_shader_viewport_layer_array"));
private static readonly Lazy<bool> _supportsTextureCompressionBptc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_bptc")); private static readonly Lazy<bool> _supportsTextureCompressionBptc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_bptc"));
private static readonly Lazy<bool> _supportsTextureCompressionRgtc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_rgtc")); private static readonly Lazy<bool> _supportsTextureCompressionRgtc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_rgtc"));
private static readonly Lazy<bool> _supportsTextureCompressionS3tc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_s3tc")); private static readonly Lazy<bool> _supportsTextureCompressionS3tc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_s3tc"));
@@ -61,6 +62,7 @@ namespace Ryujinx.Graphics.OpenGL
public static bool SupportsQuads => _supportsQuads.Value; public static bool SupportsQuads => _supportsQuads.Value;
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value; public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsShaderBallot => _supportsShaderBallot.Value; public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
public static bool SupportsShaderViewportLayerArray => _supportsShaderViewportLayerArray.Value;
public static bool SupportsTextureCompressionBptc => _supportsTextureCompressionBptc.Value; public static bool SupportsTextureCompressionBptc => _supportsTextureCompressionBptc.Value;
public static bool SupportsTextureCompressionRgtc => _supportsTextureCompressionRgtc.Value; public static bool SupportsTextureCompressionRgtc => _supportsTextureCompressionRgtc.Value;
public static bool SupportsTextureCompressionS3tc => _supportsTextureCompressionS3tc.Value; public static bool SupportsTextureCompressionS3tc => _supportsTextureCompressionS3tc.Value;

View File

@@ -117,12 +117,13 @@ namespace Ryujinx.Graphics.OpenGL
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough, supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted, supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
supportsCubemapView: true, supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsViewportIndex: true, supportsViewportIndex: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver? maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?

View File

@@ -1296,9 +1296,9 @@ namespace Ryujinx.Graphics.OpenGL
_stencilFrontMask = stencilTest.FrontMask; _stencilFrontMask = stencilTest.FrontMask;
} }
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
SetBuffers(first, buffers, isStorage: true); SetBuffers(buffers, isStorage: true);
} }
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
@@ -1366,9 +1366,9 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
SetBuffers(first, buffers, isStorage: false); SetBuffers(buffers, isStorage: false);
} }
public void SetUserClipDistance(int index, bool enableClip) public void SetUserClipDistance(int index, bool enableClip)
@@ -1460,21 +1460,22 @@ namespace Ryujinx.Graphics.OpenGL
GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit);
} }
private void SetBuffers(int first, ReadOnlySpan<BufferRange> buffers, bool isStorage) private void SetBuffers(ReadOnlySpan<BufferAssignment> buffers, bool isStorage)
{ {
BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer; BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer;
for (int index = 0; index < buffers.Length; index++) for (int index = 0; index < buffers.Length; index++)
{ {
BufferRange buffer = buffers[index]; BufferAssignment assignment = buffers[index];
BufferRange buffer = assignment.Range;
if (buffer.Handle == BufferHandle.Null) if (buffer.Handle == BufferHandle.Null)
{ {
GL.BindBufferRange(target, first + index, 0, IntPtr.Zero, 0); GL.BindBufferRange(target, assignment.Binding, 0, IntPtr.Zero, 0);
continue; continue;
} }
GL.BindBufferRange(target, first + index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); GL.BindBufferRange(target, assignment.Binding, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
} }
} }

View File

@@ -54,4 +54,4 @@ namespace Ryujinx.Graphics.OpenGL.Queries
} }
} }
} }
} }

View File

@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.OpenGL
private const int TextureCount = 3; private const int TextureCount = 3;
private readonly OpenGLRenderer _renderer; private readonly OpenGLRenderer _renderer;
private bool _initialized;
private int _width; private int _width;
private int _height; private int _height;
private int _copyFramebufferHandle; private int _copyFramebufferHandle;
@@ -179,6 +181,7 @@ namespace Ryujinx.Graphics.OpenGL
public void InitializeBackgroundContext(IOpenGLContext baseContext) public void InitializeBackgroundContext(IOpenGLContext baseContext)
{ {
BackgroundContext = new BackgroundContextWorker(baseContext); BackgroundContext = new BackgroundContextWorker(baseContext);
_initialized = true;
} }
public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
@@ -193,6 +196,11 @@ namespace Ryujinx.Graphics.OpenGL
public void Dispose() public void Dispose()
{ {
if (!_initialized)
{
return;
}
BackgroundContext.Dispose(); BackgroundContext.Dispose();
if (_copyFramebufferHandle != 0) if (_copyFramebufferHandle != 0)
@@ -203,4 +211,4 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
} }
} }

View File

@@ -1829,7 +1829,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (texOp.Index < 2 || (type & SamplerType.Mask) == SamplerType.Texture3D) if (texOp.Index < 2 || (type & SamplerType.Mask) == SamplerType.Texture3D)
{ {
result = ScalingHelpers.ApplyUnscaling(context, texOp, result, isBindless, isIndexed); result = ScalingHelpers.ApplyUnscaling(context, texOp.WithType(type), result, isBindless, isIndexed);
} }
return new OperationResult(AggregateType.S32, result); return new OperationResult(AggregateType.S32, result);

View File

@@ -10,5 +10,7 @@ namespace Ryujinx.Graphics.Shader
public const int NvnBaseVertexByteOffset = 0x640; public const int NvnBaseVertexByteOffset = 0x640;
public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnBaseInstanceByteOffset = 0x644;
public const int NvnDrawIndexByteOffset = 0x648; public const int NvnDrawIndexByteOffset = 0x648;
public const int StorageAlignment = 16;
} }
} }

View File

@@ -177,6 +177,15 @@ namespace Ryujinx.Graphics.Shader
return false; return false;
} }
/// <summary>
/// Queries whenever the current draw uses unaligned storage buffer addresses.
/// </summary>
/// <returns>True if any storage buffer address is not aligned to 16 bytes, false otherwise</returns>
bool QueryHasUnalignedStorageBuffer()
{
return false;
}
/// <summary> /// <summary>
/// Queries host about the presence of the FrontFacing built-in variable bug. /// Queries host about the presence of the FrontFacing built-in variable bug.
/// </summary> /// </summary>
@@ -249,6 +258,15 @@ namespace Ryujinx.Graphics.Shader
return true; return true;
} }
/// <summary>
/// Queries host support for writes to Layer from vertex or tessellation shader stages.
/// </summary>
/// <returns>True if writes to layer from vertex or tessellation are supported, false otherwise</returns>
bool QueryHostSupportsLayerVertexTessellation()
{
return true;
}
/// <summary> /// <summary>
/// Queries host GPU non-constant texture offset support. /// Queries host GPU non-constant texture offset support.
/// </summary> /// </summary>

View File

@@ -278,13 +278,21 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput) private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput)
{ {
if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
if (attr == AttributeConsts.Layer && config.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess)
{ {
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, 0, isOutput); attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.Layer, 0, isOutput);
config.SetLayerOutputAttribute(attr);
}
else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr, isOutput);
} }
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd) else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{ {
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, 4, isOutput); attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 4, isOutput);
} }
return attr; return attr;

View File

@@ -27,5 +27,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
Handle = handle; Handle = handle;
} }
public AstTextureOperation WithType(SamplerType type)
{
return new AstTextureOperation(Inst, type, Format, Flags, CbufSlot, Handle, Index);
}
} }
} }

View File

@@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// we can guess which storage buffer it is accessing. // we can guess which storage buffer it is accessing.
// We can then replace the global memory access with a storage // We can then replace the global memory access with a storage
// buffer access. // buffer access.
node = ReplaceGlobalWithStorage(node, config, storageIndex); node = ReplaceGlobalWithStorage(block, node, config, storageIndex);
} }
else if (config.Stage == ShaderStage.Compute && operation.Inst == Instruction.LoadGlobal) else if (config.Stage == ShaderStage.Compute && operation.Inst == Instruction.LoadGlobal)
{ {
@@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
} }
} }
private static LinkedListNode<INode> ReplaceGlobalWithStorage(LinkedListNode<INode> node, ShaderConfig config, int storageIndex) private static LinkedListNode<INode> ReplaceGlobalWithStorage(BasicBlock block, LinkedListNode<INode> node, ShaderConfig config, int storageIndex)
{ {
Operation operation = (Operation)node.Value; Operation operation = (Operation)node.Value;
@@ -64,42 +64,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
config.SetUsedStorageBuffer(storageIndex, isWrite); config.SetUsedStorageBuffer(storageIndex, isWrite);
Operand GetStorageOffset()
{
Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = Cbuf(0, GetStorageCbOffset(config.Stage, storageIndex));
Operand baseAddrTrunc = Local();
Operand alignMask = Const(-config.GpuAccessor.QueryHostStorageBufferOffsetAlignment());
Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask);
node.List.AddBefore(node, andOp);
Operand byteOffset = Local();
Operation subOp = new Operation(Instruction.Subtract, byteOffset, addrLow, baseAddrTrunc);
node.List.AddBefore(node, subOp);
if (isStg16Or8)
{
return byteOffset;
}
Operand wordOffset = Local();
Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2));
node.List.AddBefore(node, shrOp);
return wordOffset;
}
Operand[] sources = new Operand[operation.SourcesCount]; Operand[] sources = new Operand[operation.SourcesCount];
sources[0] = Const(storageIndex); sources[0] = Const(storageIndex);
sources[1] = GetStorageOffset(); sources[1] = GetStorageOffset(block, node, config, storageIndex, operation.GetSource(0), isStg16Or8);
for (int index = 2; index < operation.SourcesCount; index++) for (int index = 2; index < operation.SourcesCount; index++)
{ {
@@ -144,6 +112,170 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return node; return node;
} }
private static Operand GetStorageOffset(
BasicBlock block,
LinkedListNode<INode> node,
ShaderConfig config,
int storageIndex,
Operand addrLow,
bool isStg16Or8)
{
int baseAddressCbOffset = GetStorageCbOffset(config.Stage, storageIndex);
bool storageAligned = !(config.GpuAccessor.QueryHasUnalignedStorageBuffer() || config.GpuAccessor.QueryHostStorageBufferOffsetAlignment() > Constants.StorageAlignment);
(Operand byteOffset, int constantOffset) = storageAligned ?
GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) :
(null, 0);
if (byteOffset == null)
{
Operand baseAddrLow = Cbuf(0, baseAddressCbOffset);
Operand baseAddrTrunc = Local();
Operand alignMask = Const(-config.GpuAccessor.QueryHostStorageBufferOffsetAlignment());
Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask);
node.List.AddBefore(node, andOp);
Operand offset = Local();
Operation subOp = new Operation(Instruction.Subtract, offset, addrLow, baseAddrTrunc);
node.List.AddBefore(node, subOp);
byteOffset = offset;
}
else if (constantOffset != 0)
{
Operand offset = Local();
Operation addOp = new Operation(Instruction.Add, offset, byteOffset, Const(constantOffset));
node.List.AddBefore(node, addOp);
byteOffset = offset;
}
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (isStg16Or8)
{
return byteOffset;
}
Operand wordOffset = Local();
Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2));
node.List.AddBefore(node, shrOp);
return wordOffset;
}
private static bool IsCb0Offset(Operand operand, int offset)
{
return operand.Type == OperandType.ConstantBuffer && operand.GetCbufSlot() == 0 && operand.GetCbufOffset() == offset;
}
private static void ReplaceAddressAlignment(LinkedList<INode> list, Operand address, Operand byteOffset, int constantOffset)
{
// When we emit 16/8-bit LDG, we add extra code to determine the address alignment.
// Eliminate the storage buffer base address from this too, leaving only the byte offset.
foreach (INode useNode in address.UseOps)
{
if (useNode is Operation op && op.Inst == Instruction.BitwiseAnd)
{
Operand src1 = op.GetSource(0);
Operand src2 = op.GetSource(1);
int addressIndex = -1;
if (src1 == address && src2.Type == OperandType.Constant && src2.Value == 3)
{
addressIndex = 0;
}
else if (src2 == address && src1.Type == OperandType.Constant && src1.Value == 3)
{
addressIndex = 1;
}
if (addressIndex != -1)
{
LinkedListNode<INode> node = list.Find(op);
// Add offset calculation before the use. Needs to be on the same block.
if (node != null)
{
Operand offset = Local();
Operation addOp = new Operation(Instruction.Add, offset, byteOffset, Const(constantOffset));
list.AddBefore(node, addOp);
op.SetSource(addressIndex, offset);
}
}
}
}
}
private static (Operand, int) GetStorageOffset(BasicBlock block, Operand address, int baseAddressCbOffset)
{
if (IsCb0Offset(address, baseAddressCbOffset))
{
// Direct offset: zero.
return (Const(0), 0);
}
(address, int constantOffset) = GetStorageConstantOffset(block, address);
address = Utils.FindLastOperation(address, block);
if (IsCb0Offset(address, baseAddressCbOffset))
{
// Only constant offset
return (Const(0), constantOffset);
}
if (!(address.AsgOp is Operation offsetAdd) || offsetAdd.Inst != Instruction.Add)
{
return (null, 0);
}
Operand src1 = offsetAdd.GetSource(0);
Operand src2 = Utils.FindLastOperation(offsetAdd.GetSource(1), block);
if (IsCb0Offset(src2, baseAddressCbOffset))
{
return (src1, constantOffset);
}
else if (IsCb0Offset(src1, baseAddressCbOffset))
{
return (src2, constantOffset);
}
return (null, 0);
}
private static (Operand, int) GetStorageConstantOffset(BasicBlock block, Operand address)
{
if (!(address.AsgOp is Operation offsetAdd) || offsetAdd.Inst != Instruction.Add)
{
return (address, 0);
}
Operand src1 = offsetAdd.GetSource(0);
Operand src2 = offsetAdd.GetSource(1);
if (src2.Type != OperandType.Constant)
{
return (address, 0);
}
return (src1, src2.Value);
}
private static LinkedListNode<INode> ReplaceLdgWithLdc(LinkedListNode<INode> node, ShaderConfig config, int storageIndex) private static LinkedListNode<INode> ReplaceLdgWithLdc(LinkedListNode<INode> node, ShaderConfig config, int storageIndex)
{ {
Operation operation = (Operation)node.Value; Operation operation = (Operation)node.Value;
@@ -165,7 +297,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand byteOffset = Local(); Operand byteOffset = Local();
Operand wordOffset = Local(); Operand wordOffset = Local();
Operation subOp = new Operation(Instruction.Subtract, byteOffset, addrLow, baseAddrTrunc); Operation subOp = new Operation(Instruction.Subtract, byteOffset, addrLow, baseAddrTrunc);
Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2)); Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2));
node.List.AddBefore(node, subOp); node.List.AddBefore(node, subOp);
@@ -260,7 +392,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{ {
if (operand.Type == OperandType.ConstantBuffer) if (operand.Type == OperandType.ConstantBuffer)
{ {
int slot = operand.GetCbufSlot(); int slot = operand.GetCbufSlot();
int offset = operand.GetCbufOffset(); int offset = operand.GetCbufOffset();
if (slot == 0 && offset >= sbStart && offset < sbEnd) if (slot == 0 && offset >= sbStart && offset < sbEnd)

View File

@@ -48,6 +48,9 @@ namespace Ryujinx.Graphics.Shader.Translation
public int Cb1DataSize { get; private set; } public int Cb1DataSize { get; private set; }
public bool LayerOutputWritten { get; private set; }
public int LayerOutputAttribute { get; private set; }
public bool NextUsesFixedFuncAttributes { get; private set; } public bool NextUsesFixedFuncAttributes { get; private set; }
public int UsedInputAttributes { get; private set; } public int UsedInputAttributes { get; private set; }
public int UsedOutputAttributes { get; private set; } public int UsedOutputAttributes { get; private set; }
@@ -131,6 +134,20 @@ namespace Ryujinx.Graphics.Shader.Translation
_usedImages = new Dictionary<TextureInfo, TextureMeta>(); _usedImages = new Dictionary<TextureInfo, TextureMeta>();
} }
public ShaderConfig(
ShaderStage stage,
OutputTopology outputTopology,
int maxOutputVertices,
IGpuAccessor gpuAccessor,
TranslationOptions options) : this(gpuAccessor, options)
{
Stage = stage;
ThreadsPerInputPrimitive = 1;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
}
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(gpuAccessor, options) public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(gpuAccessor, options)
{ {
Stage = header.Stage; Stage = header.Stage;
@@ -240,6 +257,12 @@ namespace Ryujinx.Graphics.Shader.Translation
} }
} }
public void SetLayerOutputAttribute(int attr)
{
LayerOutputWritten = true;
LayerOutputAttribute = attr;
}
public void SetInputUserAttributeFixedFunc(int index) public void SetInputUserAttributeFixedFunc(int index)
{ {
UsedInputAttributes |= 1 << index; UsedInputAttributes |= 1 << index;
@@ -694,5 +717,20 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
return FindDescriptorIndex(GetImageDescriptors(), texOp); return FindDescriptorIndex(GetImageDescriptors(), texOp);
} }
public ShaderProgramInfo CreateProgramInfo()
{
return new ShaderProgramInfo(
GetConstantBufferDescriptors(),
GetStorageBufferDescriptors(),
GetTextureDescriptors(),
GetImageDescriptors(),
Stage,
UsedFeatures.HasFlag(FeatureFlags.InstanceId),
UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
UsedFeatures.HasFlag(FeatureFlags.RtLayer),
ClipDistancesWritten,
OmapTargets);
}
} }
} }

View File

@@ -79,17 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation
var sInfo = StructuredProgram.MakeStructuredProgram(funcs, config); var sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
var info = new ShaderProgramInfo( var info = config.CreateProgramInfo();
config.GetConstantBufferDescriptors(),
config.GetStorageBufferDescriptors(),
config.GetTextureDescriptors(),
config.GetImageDescriptors(),
config.Stage,
config.UsedFeatures.HasFlag(FeatureFlags.InstanceId),
config.UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
config.UsedFeatures.HasFlag(FeatureFlags.RtLayer),
config.ClipDistancesWritten,
config.OmapTargets);
return config.Options.TargetLanguage switch return config.Options.TargetLanguage switch
{ {

View File

@@ -1,7 +1,12 @@
using Ryujinx.Graphics.Shader.Decoders; using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
using static Ryujinx.Graphics.Shader.Translation.Translator; using static Ryujinx.Graphics.Shader.Translation.Translator;
@@ -18,6 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderStage Stage => _config.Stage; public ShaderStage Stage => _config.Stage;
public int Size => _config.Size; public int Size => _config.Size;
public int Cb1DataSize => _config.Cb1DataSize; public int Cb1DataSize => _config.Cb1DataSize;
public bool LayerOutputWritten => _config.LayerOutputWritten;
public IGpuAccessor GpuAccessor => _config.GpuAccessor; public IGpuAccessor GpuAccessor => _config.GpuAccessor;
@@ -149,5 +155,94 @@ namespace Ryujinx.Graphics.Shader.Translation
return Translator.Translate(code, _config); return Translator.Translate(code, _config);
} }
public ShaderProgram GenerateGeometryPassthrough()
{
int outputAttributesMask = _config.UsedOutputAttributes;
int layerOutputAttr = _config.LayerOutputAttribute;
OutputTopology outputTopology;
int maxOutputVertices;
switch (GpuAccessor.QueryPrimitiveTopology())
{
case InputTopology.Points:
outputTopology = OutputTopology.PointList;
maxOutputVertices = 1;
break;
case InputTopology.Lines:
case InputTopology.LinesAdjacency:
outputTopology = OutputTopology.LineStrip;
maxOutputVertices = 2;
break;
default:
outputTopology = OutputTopology.TriangleStrip;
maxOutputVertices = 3;
break;
}
ShaderConfig config = new ShaderConfig(ShaderStage.Geometry, outputTopology, maxOutputVertices, GpuAccessor, _config.Options);
EmitterContext context = new EmitterContext(default, config, false);
for (int v = 0; v < maxOutputVertices; v++)
{
int outAttrsMask = outputAttributesMask;
while (outAttrsMask != 0)
{
int attrIndex = BitOperations.TrailingZeroCount(outAttrsMask);
outAttrsMask &= ~(1 << attrIndex);
for (int c = 0; c < 4; c++)
{
int attr = AttributeConsts.UserAttributeBase + attrIndex * 16 + c * 4;
Operand value = context.LoadAttribute(Const(attr), Const(0), Const(v));
if (attr == layerOutputAttr)
{
context.Copy(Attribute(AttributeConsts.Layer), value);
}
else
{
context.Copy(Attribute(attr), value);
config.SetOutputUserAttribute(attrIndex);
}
config.SetInputUserAttribute(attrIndex, c);
}
}
for (int c = 0; c < 4; c++)
{
int attr = AttributeConsts.PositionX + c * 4;
Operand value = context.LoadAttribute(Const(attr), Const(0), Const(v));
context.Copy(Attribute(attr), value);
}
context.EmitVertex();
}
context.EndPrimitive();
var operations = context.GetOperations();
var cfg = ControlFlowGraph.Create(operations);
var function = new Function(cfg.Blocks, "main", false, 0, 0);
var sInfo = StructuredProgram.MakeStructuredProgram(new[] { function }, config);
var info = config.CreateProgramInfo();
return config.Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, config)),
_ => throw new NotImplementedException(config.Options.TargetLanguage.ToString())
};
}
} }
} }

View File

@@ -210,7 +210,10 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
if (cbs != null && !(_buffer.HasCommandBufferDependency(cbs.Value) && _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize))) if (cbs != null &&
_gd.PipelineInternal.RenderPassActive &&
!(_buffer.HasCommandBufferDependency(cbs.Value) &&
_waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
{ {
// If the buffer hasn't been used on the command buffer yet, try to preload the data. // If the buffer hasn't been used on the command buffer yet, try to preload the data.
// This avoids ending and beginning render passes on each buffer data upload. // This avoids ending and beginning render passes on each buffer data upload.

View File

@@ -130,6 +130,12 @@ namespace Ryujinx.Graphics.Vulkan
1f)); 1f));
} }
public void Initialize()
{
Span<byte> dummyTextureData = stackalloc byte[4];
_dummyTexture.SetData(dummyTextureData);
}
public void SetProgram(ShaderCollection program) public void SetProgram(ShaderCollection program)
{ {
_program = program; _program = program;
@@ -157,12 +163,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Image); SignalDirty(DirtyFlags.Image);
} }
public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{ {
for (int i = 0; i < buffers.Length; i++) for (int i = 0; i < buffers.Length; i++)
{ {
var buffer = buffers[i]; var assignment = buffers[i];
int index = first + i; var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index]; ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
@@ -237,12 +244,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Texture); SignalDirty(DirtyFlags.Texture);
} }
public void SetUniformBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{ {
for (int i = 0; i < buffers.Length; i++) for (int i = 0; i < buffers.Length; i++)
{ {
var buffer = buffers[i]; var assignment = buffers[i];
int index = first + i; var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index]; ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];

View File

@@ -177,7 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, region); gd.BufferManager.SetData<float>(bufferHandle, 0, region);
_pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, RegionBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1]; Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -240,7 +240,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor); gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
_pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, ClearColorBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1]; Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -302,7 +302,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, region); gd.BufferManager.SetData<float>(bufferHandle, 0, region);
pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, RegionBufferSize) }); pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1]; Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -380,7 +380,7 @@ namespace Ryujinx.Graphics.Vulkan
_pipeline.SetCommandBuffer(cbs); _pipeline.SetCommandBuffer(cbs);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(bufferHandle, 0, ParamsBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2]; Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
@@ -571,7 +571,7 @@ namespace Ryujinx.Graphics.Vulkan
int conversionType = srcIsMs ? src.Info.BytesPerPixel : -src.Info.BytesPerPixel; int conversionType = srcIsMs ? src.Info.BytesPerPixel : -src.Info.BytesPerPixel;
_pipeline.Specialize(conversionType); _pipeline.Specialize(conversionType);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(bufferHandle, 0, ParamsBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
if (src.Info.Target == Target.Texture2DMultisampleArray || if (src.Info.Target == Target.Texture2DMultisampleArray ||
dst.Info.Target == Target.Texture2DMultisampleArray) dst.Info.Target == Target.Texture2DMultisampleArray)
@@ -776,7 +776,7 @@ namespace Ryujinx.Graphics.Vulkan
srcIndirectBufferOffset, srcIndirectBufferOffset,
indirectDataSize); indirectDataSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { drawCountBufferAligned }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) });
_pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() }); _pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndirectData); _pipeline.SetProgram(_programConvertIndirectData);
@@ -804,7 +804,7 @@ namespace Ryujinx.Graphics.Vulkan
0, 0,
convertedCount * outputIndexSize); convertedCount * outputIndexSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(patternBufferHandle, 0, ParamsBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternBufferHandle, 0, ParamsBufferSize)) });
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() }); _pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndexBuffer); _pipeline.SetProgram(_programConvertIndexBuffer);

View File

@@ -49,7 +49,6 @@ namespace Ryujinx.Graphics.Vulkan
private Auto<DisposableFramebuffer> _framebuffer; private Auto<DisposableFramebuffer> _framebuffer;
private Auto<DisposableRenderPass> _renderPass; private Auto<DisposableRenderPass> _renderPass;
private int _writtenAttachmentCount; private int _writtenAttachmentCount;
private bool _renderPassActive;
private readonly DescriptorSetUpdater _descriptorSetUpdater; private readonly DescriptorSetUpdater _descriptorSetUpdater;
@@ -73,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
private PipelineColorBlendAttachmentState[] _storedBlend; private PipelineColorBlendAttachmentState[] _storedBlend;
public ulong DrawCount { get; private set; } public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; }
public unsafe PipelineBase(VulkanRenderer gd, Device device) public unsafe PipelineBase(VulkanRenderer gd, Device device)
{ {
@@ -114,6 +114,8 @@ namespace Ryujinx.Graphics.Vulkan
public void Initialize() public void Initialize()
{ {
_descriptorSetUpdater.Initialize();
SupportBufferUpdater = new SupportBufferUpdater(Gd); SupportBufferUpdater = new SupportBufferUpdater(Gd);
SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount); SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount);
@@ -838,6 +840,11 @@ namespace Ryujinx.Graphics.Vulkan
stages.CopyTo(_newState.Stages.AsSpan().Slice(0, stages.Length)); stages.CopyTo(_newState.Stages.AsSpan().Slice(0, stages.Length));
SignalStateChange(); SignalStateChange();
if (_program.IsCompute)
{
EndRenderPass();
}
} }
public void Specialize<T>(in T data) where T : unmanaged public void Specialize<T>(in T data) where T : unmanaged
@@ -966,9 +973,9 @@ namespace Ryujinx.Graphics.Vulkan
SignalStateChange(); SignalStateChange();
} }
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers); _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers);
} }
public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers) public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
@@ -1006,9 +1013,9 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_descriptorSetUpdater.SetUniformBuffers(CommandBuffer, first, buffers); _descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers);
} }
public void SetUserClipDistance(int index, bool enableClip) public void SetUserClipDistance(int index, bool enableClip)
@@ -1451,7 +1458,7 @@ namespace Ryujinx.Graphics.Vulkan
private unsafe void BeginRenderPass() private unsafe void BeginRenderPass()
{ {
if (!_renderPassActive) if (!RenderPassActive)
{ {
var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height)); var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
var clearValue = new ClearValue(); var clearValue = new ClearValue();
@@ -1467,18 +1474,18 @@ namespace Ryujinx.Graphics.Vulkan
}; };
Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline); Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
_renderPassActive = true; RenderPassActive = true;
} }
} }
public void EndRenderPass() public void EndRenderPass()
{ {
if (_renderPassActive) if (RenderPassActive)
{ {
PauseTransformFeedbackInternal(); PauseTransformFeedbackInternal();
Gd.Api.CmdEndRenderPass(CommandBuffer); Gd.Api.CmdEndRenderPass(CommandBuffer);
SignalRenderPassEnd(); SignalRenderPassEnd();
_renderPassActive = false; RenderPassActive = false;
} }
} }

View File

@@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.Vulkan
public bool HasMinimalLayout { get; } public bool HasMinimalLayout { get; }
public bool UsePushDescriptors { get; } public bool UsePushDescriptors { get; }
public bool IsCompute { get; }
public uint Stages { get; } public uint Stages { get; }
@@ -47,7 +48,6 @@ namespace Ryujinx.Graphics.Vulkan
private VulkanRenderer _gd; private VulkanRenderer _gd;
private Device _device; private Device _device;
private bool _initialized; private bool _initialized;
private bool _isCompute;
private ProgramPipelineState _state; private ProgramPipelineState _state;
private DisposableRenderPass _dummyRenderPass; private DisposableRenderPass _dummyRenderPass;
@@ -91,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit) if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit)
{ {
_isCompute = true; IsCompute = true;
} }
internalShaders[i] = shader; internalShaders[i] = shader;
@@ -163,7 +163,7 @@ namespace Ryujinx.Graphics.Vulkan
try try
{ {
if (_isCompute) if (IsCompute)
{ {
CreateBackgroundComputePipeline(); CreateBackgroundComputePipeline();
} }

View File

@@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Device _device; private Device _device;
private WindowBase _window; private WindowBase _window;
private bool _initialized;
internal FormatCapabilities FormatCapabilities { get; private set; } internal FormatCapabilities FormatCapabilities { get; private set; }
internal HardwareCapabilities Capabilities; internal HardwareCapabilities Capabilities;
@@ -266,6 +268,8 @@ namespace Ryujinx.Graphics.Vulkan
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex); LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
_window = new Window(this, _surface, _physicalDevice, _device); _window = new Window(this, _surface, _physicalDevice, _device);
_initialized = true;
} }
public BufferHandle CreateBuffer(int size) public BufferHandle CreateBuffer(int size)
@@ -396,6 +400,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsFragmentShaderOrderingIntel: false, supportsFragmentShaderOrderingIntel: false,
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough, supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat, supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer,
supportsMismatchingViewFormat: true, supportsMismatchingViewFormat: true,
supportsCubemapView: !IsAmdGcn, supportsCubemapView: !IsAmdGcn,
supportsNonConstantTextureOffset: false, supportsNonConstantTextureOffset: false,
@@ -572,6 +577,11 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void Dispose() public unsafe void Dispose()
{ {
if (!_initialized)
{
return;
}
CommandBufferPool.Dispose(); CommandBufferPool.Dispose();
BackgroundResources.Dispose(); BackgroundResources.Dispose();
_counters.Dispose(); _counters.Dispose();
@@ -612,4 +622,4 @@ namespace Ryujinx.Graphics.Vulkan
Api.DestroyInstance(_instance, null); Api.DestroyInstance(_instance, null);
} }
} }
} }

View File

@@ -172,9 +172,11 @@ namespace Ryujinx.HLE.FileSystem
fsServerClient = horizon.CreatePrivilegedHorizonClient(); fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient); var fsServer = new FileSystemServer(fsServerClient);
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer); RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
// Use our own encrypted fs creator that always uses all-zero keys DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
// Use our own encrypted fs creator that doesn't actually do any encryption
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(); fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
GameCard = fsServerObjects.GameCard; GameCard = fsServerObjects.GameCard;
@@ -186,7 +188,8 @@ namespace Ryujinx.HLE.FileSystem
{ {
DeviceOperator = fsServerObjects.DeviceOperator, DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet, ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators FsCreators = fsServerObjects.FsCreators,
RandomGenerator = randomGenerator
}; };
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
@@ -9,6 +10,7 @@ using Ryujinx.Memory;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -78,13 +80,13 @@ namespace Ryujinx.HLE.HOS.Applets
var launchParams = _normalSession.Pop(); var launchParams = _normalSession.Pop();
var keyboardConfig = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop();
_isBackground = keyboardConfig.Length == Marshal.SizeOf<SoftwareKeyboardInitialize>(); _isBackground = keyboardConfig.Length == Unsafe.SizeOf<SoftwareKeyboardInitialize>();
if (_isBackground) if (_isBackground)
{ {
// Initialize the keyboard applet in background mode. // Initialize the keyboard applet in background mode.
_keyboardBackgroundInitialize = ReadStruct<SoftwareKeyboardInitialize>(keyboardConfig); _keyboardBackgroundInitialize = MemoryMarshal.Read<SoftwareKeyboardInitialize>(keyboardConfig);
_backgroundState = InlineKeyboardState.Uninitialized; _backgroundState = InlineKeyboardState.Uninitialized;
if (_device.UiHandler == null) if (_device.UiHandler == null)
@@ -342,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Applets
else else
{ {
int wordsCount = reader.ReadInt32(); int wordsCount = reader.ReadInt32();
int wordSize = Marshal.SizeOf<SoftwareKeyboardUserWord>(); int wordSize = Unsafe.SizeOf<SoftwareKeyboardUserWord>();
remaining = stream.Length - stream.Position; remaining = stream.Length - stream.Position;
if (wordsCount > MaxUserWords) if (wordsCount > MaxUserWords)
@@ -359,8 +361,7 @@ namespace Ryujinx.HLE.HOS.Applets
for (int word = 0; word < wordsCount; word++) for (int word = 0; word < wordsCount; word++)
{ {
byte[] wordData = reader.ReadBytes(wordSize); _keyboardBackgroundUserWords[word] = reader.ReadStruct<SoftwareKeyboardUserWord>();
_keyboardBackgroundUserWords[word] = ReadStruct<SoftwareKeyboardUserWord>(wordData);
} }
} }
} }
@@ -369,27 +370,25 @@ namespace Ryujinx.HLE.HOS.Applets
case InlineKeyboardRequest.SetCustomizeDic: case InlineKeyboardRequest.SetCustomizeDic:
// Read the custom dic data. // Read the custom dic data.
remaining = stream.Length - stream.Position; remaining = stream.Length - stream.Position;
if (remaining != Marshal.SizeOf<SoftwareKeyboardCustomizeDic>()) if (remaining != Unsafe.SizeOf<SoftwareKeyboardCustomizeDic>())
{ {
Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes"); Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes");
} }
else else
{ {
var keyboardDicData = reader.ReadBytes((int)remaining); _keyboardBackgroundDic = reader.ReadStruct<SoftwareKeyboardCustomizeDic>();
_keyboardBackgroundDic = ReadStruct<SoftwareKeyboardCustomizeDic>(keyboardDicData);
} }
break; break;
case InlineKeyboardRequest.SetCustomizedDictionaries: case InlineKeyboardRequest.SetCustomizedDictionaries:
// Read the custom dictionaries data. // Read the custom dictionaries data.
remaining = stream.Length - stream.Position; remaining = stream.Length - stream.Position;
if (remaining != Marshal.SizeOf<SoftwareKeyboardDictSet>()) if (remaining != Unsafe.SizeOf<SoftwareKeyboardDictSet>())
{ {
Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes"); Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes");
} }
else else
{ {
var keyboardDictData = reader.ReadBytes((int)remaining); _keyboardBackgroundDictSet = reader.ReadStruct<SoftwareKeyboardDictSet>();
_keyboardBackgroundDictSet = ReadStruct<SoftwareKeyboardDictSet>(keyboardDictData);
} }
break; break;
case InlineKeyboardRequest.Calc: case InlineKeyboardRequest.Calc:

View File

@@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary> /// <summary>
/// A structure used by SetCustomizeDic request to software keyboard. /// A structure used by SetCustomizeDic request to software keyboard.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Size = 0x70)]
struct SoftwareKeyboardCustomizeDic struct SoftwareKeyboardCustomizeDic
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 112)] // Unknown
public byte[] Unknown;
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
@@ -21,8 +22,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary> /// <summary>
/// Array of word entries in the buffer. /// Array of word entries in the buffer.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] public Array24<ulong> Entries;
public ulong[] Entries;
/// <summary> /// <summary>
/// Number of used entries in the Entries field. /// Number of used entries in the Entries field.

View File

@@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary> /// <summary>
/// A structure used by SetUserWordInfo request to the software keyboard. /// A structure used by SetUserWordInfo request to the software keyboard.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Size = 0x64)]
struct SoftwareKeyboardUserWord struct SoftwareKeyboardUserWord
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] // Unknown
public byte[] Unknown;
} }
} }

View File

@@ -850,7 +850,7 @@ namespace Ryujinx.HLE.HOS
for (int i = 0; i < programCount; i++) for (int i = 0; i < programCount; i++)
{ {
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
mapInfo[i].MainProgramId = new ProgramId(applicationId); mapInfo[i].MainProgramId = new ApplicationId(applicationId);
mapInfo[i].ProgramIndex = (byte)i; mapInfo[i].ProgramIndex = (byte)i;
} }

View File

@@ -479,7 +479,10 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose(); AudioRendererManager.Dispose();
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); if (LibHacHorizonManager.ApplicationClient != null)
{
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
}
KernelContext.Dispose(); KernelContext.Dispose();
} }

View File

@@ -60,8 +60,6 @@ namespace Ryujinx.HLE.HOS
virtualFileSystem.InitializeFsServer(Server, out var fsClient); virtualFileSystem.InitializeFsServer(Server, out var fsClient);
FsClient = fsClient; FsClient = fsClient;
CleanSdCardDirectory();
} }
public void InitializeSystemClients() public void InitializeSystemClients()
@@ -78,27 +76,6 @@ namespace Ryujinx.HLE.HOS
ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor); ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor);
} }
// This function was added to avoid errors that come from a user's keys or SD encryption seed changing.
// Catching these errors and recreating the file ended up not working because of the different ways
// applications respond to a file suddenly containing all zeros or having a length of zero.
// Clearing the SD card save directory was determined to be the best option for the moment since
// the saves on the SD card are meant as caches that can be deleted at any time.
private void CleanSdCardDirectory()
{
Result rc = RyujinxClient.Fs.MountSdCard("sdcard".ToU8Span());
if (rc.IsFailure()) return;
try
{
RyujinxClient.Fs.CleanDirectoryRecursively("sdcard:/Nintendo/save".ToU8Span()).IgnoreResult();
RyujinxClient.Fs.DeleteDirectoryRecursively("sdcard:/save".ToU8Span()).IgnoreResult();
}
finally
{
RyujinxClient.Fs.Unmount("sdcard".ToU8Span());
}
}
private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData | private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData |
AccessControlBits.Bits.GameCard | AccessControlBits.Bits.GameCard |
AccessControlBits.Bits.SaveDataMeta | AccessControlBits.Bits.SaveDataMeta |

View File

@@ -26,8 +26,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
{ {
class IApplicationFunctions : IpcService class IApplicationFunctions : IpcService
{ {
private ulong _defaultSaveDataSize = 200000000; private long _defaultSaveDataSize = 200000000;
private ulong _defaultJournalSaveDataSize = 200000000; private long _defaultJournalSaveDataSize = 200000000;
private KEvent _gpuErrorDetectedSystemEvent; private KEvent _gpuErrorDetectedSystemEvent;
private KEvent _friendInvitationStorageChannelEvent; private KEvent _friendInvitationStorageChannelEvent;
@@ -203,13 +203,13 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
} }
[CommandHipc(25)] // 3.0.0+ [CommandHipc(25)] // 3.0.0+
// ExtendSaveData(u8 save_data_type, nn::account::Uid, u64 save_size, u64 journal_size) -> u64 result_code // ExtendSaveData(u8 save_data_type, nn::account::Uid, s64 save_size, s64 journal_size) -> u64 result_code
public ResultCode ExtendSaveData(ServiceCtx context) public ResultCode ExtendSaveData(ServiceCtx context)
{ {
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
Uid userId = context.RequestData.ReadStruct<Uid>(); Uid userId = context.RequestData.ReadStruct<Uid>();
ulong saveDataSize = context.RequestData.ReadUInt64(); long saveDataSize = context.RequestData.ReadInt64();
ulong journalSize = context.RequestData.ReadUInt64(); long journalSize = context.RequestData.ReadInt64();
// NOTE: Service calls nn::fs::ExtendApplicationSaveData. // NOTE: Service calls nn::fs::ExtendApplicationSaveData.
// Since LibHac currently doesn't support this method, we can stub it for now. // Since LibHac currently doesn't support this method, we can stub it for now.
@@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
} }
[CommandHipc(26)] // 3.0.0+ [CommandHipc(26)] // 3.0.0+
// GetSaveDataSize(u8 save_data_type, nn::account::Uid) -> (u64 save_size, u64 journal_size) // GetSaveDataSize(u8 save_data_type, nn::account::Uid) -> (s64 save_size, s64 journal_size)
public ResultCode GetSaveDataSize(ServiceCtx context) public ResultCode GetSaveDataSize(ServiceCtx context)
{ {
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
@@ -268,6 +268,23 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(28)] // 11.0.0+
// GetSaveDataSizeMax() -> (s64 save_size_max, s64 journal_size_max)
public ResultCode GetSaveDataSizeMax(ServiceCtx context)
{
// NOTE: We are currently using a stub for GetSaveDataSize() which returns the default values.
// For this method we shouldn't return anything lower than that, but since we aren't interacting
// with fs to get the actual sizes, we return the default values here as well.
// This also helps in case ExtendSaveData() has been executed and the default values were modified.
context.ResponseData.Write(_defaultSaveDataSize);
context.ResponseData.Write(_defaultJournalSaveDataSize);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandHipc(30)] [CommandHipc(30)]
// BeginBlockingHomeButtonShortAndLongPressed() // BeginBlockingHomeButtonShortAndLongPressed()
public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context) public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context)

View File

@@ -1,9 +1,12 @@
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
{ {
[StructLayout(LayoutKind.Sequential, Pack = 0x8, CharSet = CharSet.Ansi)] [StructLayout(LayoutKind.Sequential, Pack = 0x8)]
struct UserPresence struct UserPresence
{ {
public UserId UserId; public UserId UserId;
@@ -13,15 +16,20 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool SamePresenceGroupApplication; public bool SamePresenceGroupApplication;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)] public Array3<byte> Unknown;
public char[] Unknown; private AppKeyValueStorageHolder _appKeyValueStorage;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC0)] public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size));
public char[] AppKeyValueStorage;
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)]
private struct AppKeyValueStorageHolder
{
public const int Size = 0xC0;
}
public override string ToString() public override string ToString()
{ {
return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {AppKeyValueStorage} }}"; return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {Encoding.ASCII.GetString(AppKeyValueStorage)} }}";
} }
} }
} }

View File

@@ -43,6 +43,16 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(1)]
// nn::friends::Cancel()
public ResultCode Cancel(ServiceCtx context)
{
// TODO: Original service sets an internal field to 1 here. Determine usage.
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
return ResultCode.Success;
}
[CommandHipc(10100)] [CommandHipc(10100)]
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) // nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
// -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa> // -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
@@ -226,23 +236,14 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
ulong position = context.Request.PtrBuff[0].Position; ulong position = context.Request.PtrBuff[0].Position;
ulong size = context.Request.PtrBuff[0].Size; ulong size = context.Request.PtrBuff[0].Size;
byte[] bufferContent = new byte[size]; ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size));
context.Memory.Read(position, bufferContent);
if (uuid.IsNull) if (uuid.IsNull)
{ {
return ResultCode.InvalidArgument; return ResultCode.InvalidArgument;
} }
int elementCount = bufferContent.Length / Marshal.SizeOf<UserPresence>(); Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() });
using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent)))
{
UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray<UserPresence>(elementCount);
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray });
}
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -1,15 +1,13 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
{ {
[StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x10)] [StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct NotificationInfo struct NotificationInfo
{ {
public NotificationEventType Type; public NotificationEventType Type;
private Array4<byte> _padding;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4)]
public char[] Padding;
public long NetworkUserIdPlaceholder; public long NetworkUserIdPlaceholder;
} }
} }

View File

@@ -1,6 +1,7 @@
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs;
using GameCardHandle = System.UInt32;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
@@ -41,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{ {
Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle); Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle);
context.ResponseData.Write(handle.Value); context.ResponseData.Write(handle);
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }

View File

@@ -19,6 +19,7 @@ using static Ryujinx.HLE.Utilities.StringUtils;
using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; using IFileSystem = LibHac.FsSrv.Sf.IFileSystem;
using IStorage = LibHac.FsSrv.Sf.IStorage; using IStorage = LibHac.FsSrv.Sf.IStorage;
using RightsId = LibHac.Fs.RightsId; using RightsId = LibHac.Fs.RightsId;
using GameCardHandle = System.UInt32;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
@@ -239,7 +240,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// OpenGameCardStorage(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IStorage> // OpenGameCardStorage(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IStorage>
public ResultCode OpenGameCardStorage(ServiceCtx context) public ResultCode OpenGameCardStorage(ServiceCtx context)
{ {
GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); GameCardHandle handle = context.RequestData.ReadUInt32();
GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32(); GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32();
using var storage = new SharedRef<IStorage>(); using var storage = new SharedRef<IStorage>();
@@ -255,7 +256,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IFileSystem> // OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IFileSystem>
public ResultCode OpenGameCardFileSystem(ServiceCtx context) public ResultCode OpenGameCardFileSystem(ServiceCtx context)
{ {
GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); GameCardHandle handle = context.RequestData.ReadUInt32();
GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32(); GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32();
using var fileSystem = new SharedRef<IFileSystem>(); using var fileSystem = new SharedRef<IFileSystem>();
@@ -315,6 +316,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs
return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value; return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value;
} }
[CommandHipc(37)] // 14.0.0+
// CreateSaveDataFileSystemWithCreationInfo2(buffer<nn::fs::SaveDataCreationInfo2, 25> creationInfo) -> ()
public ResultCode CreateSaveDataFileSystemWithCreationInfo2(ServiceCtx context)
{
byte[] creationInfoBuffer = new byte[context.Request.SendBuff[0].Size];
context.Memory.Read(context.Request.SendBuff[0].Position, creationInfoBuffer);
ref readonly SaveDataCreationInfo2 creationInfo = ref SpanHelpers.AsReadOnlyStruct<SaveDataCreationInfo2>(creationInfoBuffer);
return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo).Value;
}
[CommandHipc(51)] [CommandHipc(51)]
// OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> saveDataFs // OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> saveDataFs
public ResultCode OpenSaveDataFileSystem(ServiceCtx context) public ResultCode OpenSaveDataFileSystem(ServiceCtx context)

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
@@ -75,7 +76,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++) for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++)
{ {
MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Marshal.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value); MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Unsafe.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value);
} }
context.ResponseData.Write(filteredApplicationPlayStatistics.Count()); context.ResponseData.Write(filteredApplicationPlayStatistics.Count());

View File

@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.Clock namespace Ryujinx.HLE.HOS.Services.Time.Clock
@@ -16,14 +17,22 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public CalendarAdditionalInfo NetworkCalendarAdditionalTime; public CalendarAdditionalInfo NetworkCalendarAdditionalTime;
public SteadyClockTimePoint SteadyClockTimePoint; public SteadyClockTimePoint SteadyClockTimePoint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)] private LocationNameStorageHolder _locationName;
public char[] LocationName;
public Span<byte> LocationName => MemoryMarshal.Cast<LocationNameStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _locationName, LocationNameStorageHolder.Size));
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsAutomaticCorrectionEnabled; public bool IsAutomaticCorrectionEnabled;
public byte Type; public byte Type;
public ushort Unknown; public ushort Unknown;
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)]
private struct LocationNameStorageHolder
{
public const int Size = 0x24;
}
public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context) public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context)
{ {
currentTime = 0; currentTime = 0;

View File

@@ -8,7 +8,9 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time namespace Ryujinx.HLE.HOS.Services.Time
{ {
@@ -281,7 +283,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{ {
byte type = context.RequestData.ReadByte(); byte type = context.RequestData.ReadByte();
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ClockSnapshot>()); context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf<ClockSnapshot>());
context.RequestData.BaseStream.Position += 7; context.RequestData.BaseStream.Position += 7;
@@ -372,12 +374,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
return result; return result;
} }
char[] tzName = deviceLocationName.ToCharArray(); ReadOnlySpan<byte> tzName = Encoding.ASCII.GetBytes(deviceLocationName);
char[] locationName = new char[0x24];
Array.Copy(tzName, locationName, tzName.Length); tzName.CopyTo(clockSnapshot.LocationName);
clockSnapshot.LocationName = locationName;
result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext); result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
@@ -414,7 +413,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc) private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc)
{ {
Debug.Assert(ipcDesc.Size == (ulong)Marshal.SizeOf<ClockSnapshot>()); Debug.Assert(ipcDesc.Size == (ulong)Unsafe.SizeOf<ClockSnapshot>());
byte[] temp = new byte[ipcDesc.Size]; byte[] temp = new byte[ipcDesc.Size];

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.Utilities;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -890,14 +891,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
long streamLength = reader.BaseStream.Length; long streamLength = reader.BaseStream.Length;
if (streamLength < Marshal.SizeOf<TzifHeader>()) if (streamLength < Unsafe.SizeOf<TzifHeader>())
{ {
return false; return false;
} }
TzifHeader header = reader.ReadStruct<TzifHeader>(); TzifHeader header = reader.ReadStruct<TzifHeader>();
streamLength -= Marshal.SizeOf<TzifHeader>(); streamLength -= Unsafe.SizeOf<TzifHeader>();
int ttisGMTCount = Detzcode32(header.TtisGMTCount); int ttisGMTCount = Detzcode32(header.TtisGMTCount);
int ttisSTDCount = Detzcode32(header.TtisSTDCount); int ttisSTDCount = Detzcode32(header.TtisSTDCount);

View File

@@ -22,7 +22,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" /> <PackageReference Include="Concentus" Version="1.1.7" />
<PackageReference Include="LibHac" Version="0.16.1" /> <PackageReference Include="LibHac" Version="0.17.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" /> <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />

View File

@@ -40,20 +40,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan
return (IntPtr)surfaceHandle; return (IntPtr)surfaceHandle;
} }
// TODO: Fix this in SDL2-CS.
[DllImport("SDL2", EntryPoint = "SDL_Vulkan_GetInstanceExtensions", CallingConvention = CallingConvention.Cdecl)]
public static extern SDL_bool SDL_Vulkan_GetInstanceExtensions_Workaround(IntPtr window, out uint count, IntPtr names);
public unsafe string[] GetRequiredInstanceExtensions() public unsafe string[] GetRequiredInstanceExtensions()
{ {
if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE) if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE)
{ {
IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount]; IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount];
string[] extensions = new string[(int)extensionsCount]; string[] extensions = new string[(int)extensionsCount];
fixed (IntPtr* rawExtensionsPtr = rawExtensions) fixed (IntPtr* rawExtensionsPtr = rawExtensions)
{ {
if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE) if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE)
{ {
for (int i = 0; i < extensions.Length; i++) for (int i = 0; i < extensions.Length; i++)
{ {

View File

@@ -1,16 +1,15 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol namespace Ryujinx.Input.Motion.CemuHook.Protocol
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ControllerDataRequest struct ControllerDataRequest
{ {
public MessageType Type; public MessageType Type;
public SubscriberType SubscriberType; public SubscriberType SubscriberType;
public byte Slot; public byte Slot;
public Array6<byte> MacAddress;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] MacAddress;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -27,11 +26,8 @@ namespace Ryujinx.Input.Motion.CemuHook.Protocol
public uint DPadAnalog; public uint DPadAnalog;
public ulong MainButtonsAnalog; public ulong MainButtonsAnalog;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public Array6<byte> Touch1;
public byte[] Touch1; public Array6<byte> Touch2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Touch2;
public ulong MotionTimestamp; public ulong MotionTimestamp;
public float AccelerometerX; public float AccelerometerX;

View File

@@ -1,21 +1,20 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol namespace Ryujinx.Input.Motion.CemuHook.Protocol
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoResponse public struct ControllerInfoResponse
{ {
public SharedResponse Shared; public SharedResponse Shared;
private byte _zero; private byte _zero;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoRequest public struct ControllerInfoRequest
{ {
public MessageType Type; public MessageType Type;
public int PortsCount; public int PortsCount;
public Array4<byte> PortIndices;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] PortIndices;
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol namespace Ryujinx.Input.Motion.CemuHook.Protocol
{ {
@@ -11,8 +12,7 @@ namespace Ryujinx.Input.Motion.CemuHook.Protocol
public DeviceModelType ModelType; public DeviceModelType ModelType;
public ConnectionType ConnectionType; public ConnectionType ConnectionType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public Array6<byte> MacAddress;
public byte[] MacAddress;
public BatteryStatus BatteryStatus; public BatteryStatus BatteryStatus;
} }

View File

@@ -44,6 +44,11 @@ namespace Ryujinx.Memory.Tests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
throw new NotImplementedException();
}
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -136,6 +136,14 @@ namespace Ryujinx.Memory
} }
} }
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
Write(va, data);
return true;
}
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {

View File

@@ -58,6 +58,17 @@ namespace Ryujinx.Memory
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception> /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
void Write(ulong va, ReadOnlySpan<byte> data); void Write(ulong va, ReadOnlySpan<byte> data);
/// <summary>
/// Writes data to the application process, returning false if the data was not changed.
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
/// </summary>
/// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <returns>True if the data was changed, false otherwise</returns>
bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data);
void Fill(ulong va, ulong size, byte value) void Fill(ulong va, ulong size, byte value)
{ {
const int MaxChunkSize = 1 << 24; const int MaxChunkSize = 1 << 24;

View File

@@ -25,6 +25,7 @@ namespace Ryujinx.Memory.Tracking
private int _sequenceNumber; private int _sequenceNumber;
private BitMap _sequenceNumberBitmap; private BitMap _sequenceNumberBitmap;
private BitMap _dirtyCheckedBitmap;
private int _uncheckedHandles; private int _uncheckedHandles;
public bool Dirty { get; private set; } = true; public bool Dirty { get; private set; } = true;
@@ -36,6 +37,7 @@ namespace Ryujinx.Memory.Tracking
_dirtyBitmap = new ConcurrentBitmap(_handles.Length, true); _dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
_sequenceNumberBitmap = new BitMap(_handles.Length); _sequenceNumberBitmap = new BitMap(_handles.Length);
_dirtyCheckedBitmap = new BitMap(_handles.Length);
int i = 0; int i = 0;
@@ -246,16 +248,18 @@ namespace Ryujinx.Memory.Tracking
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction) private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, long[] checkMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
{ {
long seqMask = mask & ~seqMasks[index]; long seqMask = mask & ~seqMasks[index];
long checkMask = (~dirtyBits) & seqMask;
dirtyBits &= seqMask; dirtyBits &= seqMask;
while (dirtyBits != 0) while (dirtyBits != 0)
{ {
int bit = BitOperations.TrailingZeroCount(dirtyBits); int bit = BitOperations.TrailingZeroCount(dirtyBits);
long bitValue = 1L << bit;
dirtyBits &= ~(1L << bit); dirtyBits &= ~bitValue;
int handleIndex = baseBit + bit; int handleIndex = baseBit + bit;
@@ -273,11 +277,14 @@ namespace Ryujinx.Memory.Tracking
} }
rgSize += handle.Size; rgSize += handle.Size;
handle.Reprotect(); handle.Reprotect(false, (checkMasks[index] & bitValue) == 0);
checkMasks[index] &= ~bitValue;
prevHandle = handleIndex; prevHandle = handleIndex;
} }
checkMasks[index] |= checkMask;
seqMasks[index] |= mask; seqMasks[index] |= mask;
_uncheckedHandles -= BitOperations.PopCount((ulong)seqMask); _uncheckedHandles -= BitOperations.PopCount((ulong)seqMask);
@@ -328,6 +335,7 @@ namespace Ryujinx.Memory.Tracking
ulong rgSize = 0; ulong rgSize = 0;
long[] seqMasks = _sequenceNumberBitmap.Masks; long[] seqMasks = _sequenceNumberBitmap.Masks;
long[] checkedMasks = _dirtyCheckedBitmap.Masks;
long[] masks = _dirtyBitmap.Masks; long[] masks = _dirtyBitmap.Masks;
int startIndex = startHandle >> ConcurrentBitmap.IntShift; int startIndex = startHandle >> ConcurrentBitmap.IntShift;
@@ -345,20 +353,20 @@ namespace Ryujinx.Memory.Tracking
if (startIndex == endIndex) if (startIndex == endIndex)
{ {
ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
} }
else else
{ {
ParseDirtyBits(startValue, startMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); ParseDirtyBits(startValue, startMask, startIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
for (int i = startIndex + 1; i < endIndex; i++) for (int i = startIndex + 1; i < endIndex; i++)
{ {
ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
} }
long endValue = Volatile.Read(ref masks[endIndex]); long endValue = Volatile.Read(ref masks[endIndex]);
ParseDirtyBits(endValue, endMask, endIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); ParseDirtyBits(endValue, endMask, endIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
} }
if (rgSize != 0) if (rgSize != 0)

View File

@@ -263,15 +263,15 @@ namespace Ryujinx.Memory.Tracking
/// </summary> /// </summary>
public void ForceDirty() public void ForceDirty()
{ {
_checkCount++;
Dirty = true; Dirty = true;
} }
/// <summary> /// <summary>
/// Consume the dirty flag for this handle, and reprotect so it can be set on the next write. /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
/// </summary> /// </summary>
public void Reprotect(bool asDirty = false) /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param>
/// <param name="consecutiveCheck">True if this reprotect is the result of consecutive dirty checks</param>
public void Reprotect(bool asDirty, bool consecutiveCheck = false)
{ {
if (_volatile) return; if (_volatile) return;
@@ -296,7 +296,7 @@ namespace Ryujinx.Memory.Tracking
} }
else if (!asDirty) else if (!asDirty)
{ {
if (_checkCount > 0 && _checkCount < CheckCountForInfrequent) if (consecutiveCheck || (_checkCount > 0 && _checkCount < CheckCountForInfrequent))
{ {
if (++_volatileCount >= VolatileThreshold && _preAction == null) if (++_volatileCount >= VolatileThreshold && _preAction == null)
{ {
@@ -313,6 +313,15 @@ namespace Ryujinx.Memory.Tracking
} }
} }
/// <summary>
/// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
/// </summary>
/// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param>
public void Reprotect(bool asDirty = false)
{
Reprotect(asDirty, false);
}
/// <summary> /// <summary>
/// Register an action to perform when the tracked region is read or written. /// Register an action to perform when the tracked region is read or written.
/// The action is automatically removed after it runs. /// The action is automatically removed after it runs.

View File

@@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ryujinx.SDL2-CS" Version="2.0.22-build20" /> <PackageReference Include="Ryujinx.SDL2-CS" Version="2.24.2-build21" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

Some files were not shown because too many files have changed in this diff Show More