Compare commits

..

37 Commits

Author SHA1 Message Date
d2686e0a5b nuget: bump DiscordRichPresence from 1.0.175 to 1.1.3.18 (#3965)
Bumps [DiscordRichPresence](https://github.com/Lachee/discord-rpc-csharp) from 1.0.175 to 1.1.3.18.
- [Release notes](https://github.com/Lachee/discord-rpc-csharp/releases)
- [Commits](https://github.com/Lachee/discord-rpc-csharp/commits)

---
updated-dependencies:
- dependency-name: DiscordRichPresence
  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-30 21:58:45 +00:00
4905101df1 Remove shader dependency on SPV_KHR_shader_ballot and SPV_KHR_subgroup_vote extensions (#3943)
* Remove shader dependency on SPV_KHR_shader_ballot and SPV_KHR_subgroup_vote extensions

* Shader cache version bump
2022-11-30 18:24:15 -03:00
8750b90a7f Ensure that vertex attribute buffer index is valid on GPU (#3942)
* Ensure that vertex attribute buffer index is valid on GPU

* Remove vertex buffer validation code from OpenGL

* Remove some fields that are no longer necessary
2022-11-30 18:06:40 -03:00
af01100050 nuget: bump System.Management from 6.0.0 to 7.0.0 (#3949)
Bumps [System.Management](https://github.com/dotnet/runtime) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: System.Management
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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-29 23:30:33 +01:00
c0821fee1f Update README.MD (#3946)
Updates the readme to include usage of Metal via MoltenVK, updated game compatibility statistics.
2022-11-29 17:20:26 +01:00
a5c2aead67 ConcurrentBitmap: Use Interlocked Or/And (#3937) 2022-11-29 13:47:57 +00:00
d41c95dcff chore: Update OpenTK to 4.7.5 (#3944) 2022-11-29 13:32:40 +00:00
fbf2b09706 ava: Make dialogs using an overlay window work on Linux (#3938) 2022-11-29 06:33:46 +01:00
1fc0f569de GPU: Always draw polygon topology as triangle fan (#3932)
Polygon topology wasn't really supported and would only work on OpenGL on drivers that haven't removed it. As an alternative, this PR makes all cases of polygon topology use triangle fan. The topology type and transform feedback type have not been changed, as I don't think geo shader/tfb should be used with polygons.

The OpenGL spec states:
Only convex polygons are guaranteed to be drawn correctly by the GL.

For convex polygons, triangle fan is equivalent to polygon. I imagine this is probably how it works on device, as this get-out-of-jail-free card is too enticing to pass up.

This fixes the stat display in Pokemon S/V.
2022-11-28 19:18:22 -03:00
dff138229c amadeus: Fixes and initial 15.0.0 support (#3908)
* amadeus: Allow OOB read of GC-ADPCM coefficients

Fixes "Ninja Gaiden Sigma 2" and possibly "NINJA GAIDEN 3: Razor's Edge"

* amadeus: Fix wrong variable usage in delay effect

We should transform the delay line values, not the input.

* amadeus: Update GroupedBiquadFilterCommand documentation

* amadeus: Simplify PoolMapper alignment checks

* amadeus: Update Surround delay effect matrix to REV11

* amadeus: Add drop parameter support and use 32 bits integers for estimate time

Also implement accurate ExecuteAudioRendererRendering stub.

* Address gdkchan's comments

* Address gdkchan's other comments

* Address gdkchan's comment
2022-11-28 08:28:45 +01:00
472119c8da sfdnsres; Fix deserializer of AddrInfoSerialized when addresses are empty (#3924) 2022-11-28 02:53:57 +01:00
1865ea87e5 bsd: Fix eventfd broken logic (#3647)
* bsd: Fix eventfd broken logic

This commit fix eventfd logic being broken.

The following changes were made:
- EventFd IPC definition had argument inverted
- EventFd events weren't fired correctly
- Poll logic was wrong and unfinished for eventfd
- Reintroduce workaround from #3385 but in a safer way, and spawn 4
  threads.

* ipc: Rework a bit for multithreads

* Clean up debug logs

* Make server thread yield when managed lock isn't availaible

* Fix replyTargetHandle not being added in the proper locking scope

* Simplify some scopes

* Address gdkchan's comments

* Revert IPC workaround for now

* Reintroduce the EventFileDescriptor workaround
2022-11-27 20:18:05 +00:00
18b61aff59 Unbreak bug_report.md (#3915)
* Unbreak bug_report.md

* Update bug_report.md
2022-11-27 20:11:51 +00:00
cb22629ac1 HLE: fix small issue in IPsmSession (#3909) 2022-11-27 01:10:42 +00:00
6f0f99ee2b Avalonia: Fix OpenGL crashing on Linux (#3902)
* ava: Fix OpenGL crashing on Linux

Fixes a regression from #3901

* Fix formatting
2022-11-26 12:06:53 +01:00
70f2da8fdf ava: Fix invisible vulkan window on Linux (#3901)
Co-authored-by: emmauss <emmausssss@gmail.com>

Co-authored-by: emmauss <emmausssss@gmail.com>
2022-11-25 17:40:44 +00:00
5d3ef7761b ava: Refactor Title Update Manager window (#3898)
* ava: Refactor TitleUpdate Manager window

* Update locale
2022-11-25 17:55:08 +01:00
476b4683cf Fix CB0 alignment with addresses used for 8/16-bit LDG/STG (#3897)
This replacement is meant to be done with the original identified byteOffset, not the one assigned later on by the below conditionals (that already has the constant offset added, for instance).

This fixes videos being pixelated in Xenoblade 3, and other regressions that might have happened since #3847.
2022-11-25 14:39:03 +00:00
5fb5079730 chore: Update Avalonia related dependencies (#3891) 2022-11-25 13:27:41 +00:00
3fbacd0f49 ava: Rework DLC Manager, Add various fixes and cleanup (#3896)
* Fixes Everything Part.2

* Change sorting, fix remove and heading
2022-11-25 12:41:34 +01:00
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
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
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
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
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
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
a0c77f8d11 Fix NRE on Avalonia for error applets with unknown error message (#3888) 2022-11-24 09:31:00 +01:00
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
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
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
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
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
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
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
0fd47ff490 remove redundant logs (#3877) 2022-11-23 11:28:46 +01:00
f088c3d344 Do not update shader state for DrawTextures (#3876) 2022-11-21 18:16:00 +01:00
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
166 changed files with 2467 additions and 1688 deletions

View File

@ -1,6 +1,6 @@
---
name: Bug Report
about: Something doesn't work correctly in Ryujinx. Note that game-specific issues should be instead posted on the Game Compatibility List at https://github.com/Ryujinx/Ryujinx-Games-List, unless it is a provable regression.
about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
#assignees:
---

View File

@ -36,7 +36,7 @@
## Compatibility
As of October 2022, Ryujinx has been tested on approximately 3,700 titles; over 3,500 boot past menus and into gameplay, with roughly 3,000 of those being considered playable.
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
## Usage
@ -90,7 +90,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
- **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum) or Vulkan APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
- **Input**

View File

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.OpenAL" Version="4.7.2" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
</ItemGroup>
<ItemGroup>

View File

@ -1,4 +1,5 @@
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Common.Logging;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
@ -71,6 +72,19 @@ namespace Ryujinx.Audio.Renderer.Dsp
return (short)value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{
if ((uint)index > (uint)coefficients.Length)
{
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
}
return coefficients[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
{
@ -84,8 +98,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
short history0 = loopContext.History0;
short history1 = loopContext.History1;
short coefficient0 = coefficients[coefficientIndex * 2 + 0];
short coefficient1 = coefficients[coefficientIndex * 2 + 1];
short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
@ -109,8 +123,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
coefficientIndex = (byte)((predScale >> 4) & 0xF);
coefficient0 = coefficients[coefficientIndex * 2 + 0];
coefficient1 = coefficients[coefficientIndex * 2 + 1];
coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
nibbles += 2;

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; }
public uint SampleRate { get; }

View File

@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.AuxiliaryBuffer;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public uint InputBufferIndex { get; }
public uint OutputBufferIndex { get; }

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.BiquadFilter;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public Memory<BiquadFilterState> BiquadFilterState { get; }
public int InputBufferIndex { get; }

View File

@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.CaptureBuffer;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public uint InputBufferIndex { get; }

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.CircularBufferSink;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort[] Input { get; }
public uint InputCount { get; }

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.ClearMixBuffer;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ClearMixBufferCommand(int nodeId)
{

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.CopyMixBuffer;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType { get; }
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; }
public uint SampleRate { get; }

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Delay;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public DelayParameter Parameter => _parameter;
public Memory<DelayState> State { get; }
@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
}
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
{
const ushort channelCount = 1;
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
@ -70,7 +70,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float temp = input * inGain + delayLineValue * feedbackGain;
state.UpdateLowPassFilter(ref temp, 1);
state.UpdateLowPassFilter(ref temp, channelCount);
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
}
@ -104,7 +104,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Y = state.DelayLines[1].Read(),
};
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
@ -148,7 +148,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
W = state.DelayLines[3].Read()
};
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
@ -171,12 +171,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain,
0.0f, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f,
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackBaseGain, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain);
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
@ -200,7 +200,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
U = state.DelayLines[5].Read()
};
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DepopForMixBuffers;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public uint MixBufferOffset { get; }

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DepopPrepare;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public uint MixBufferCount { get; }

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DeviceSink;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public string DeviceName { get; }

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DownMixSurroundToStereo;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort[] InputBufferIndices { get; }
public ushort[] OutputBufferIndices { get; }

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.GroupedBiquadFilter;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
private BiquadFilterParameter[] _parameters;
private Memory<BiquadFilterState> _biquadFilterStates;
@ -47,9 +47,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
}
// NOTE: Nintendo also implements a hot path for double biquad filters, but no generic path when the command definition suggests it could be done.
// As such we currently only implement a generic path for simplicity.
// TODO: Implement double biquad filters fast path.
// NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done.
// As such we currently only implement a generic path for simplicity for double biquad.
if (_parameters.Length == 1)
{
BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType { get; }
public ulong EstimatedProcessingTime { get; }
public uint EstimatedProcessingTime { get; }
public void Process(CommandList context);

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.LimiterVersion1;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public LimiterParameter Parameter => _parameter;
public Memory<LimiterState> State { get; }

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.LimiterVersion2;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public LimiterParameter Parameter => _parameter;
public Memory<LimiterState> State { get; }

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Mix;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.MixRamp;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.MixRampGrouped;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public uint MixBufferCount { get; }

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; }
public uint SampleRate { get; }

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; }
public uint SampleRate { get; }

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Performance;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public PerformanceEntryAddresses PerformanceEntryAddresses { get; }

View File

@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Reverb3d;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }

View File

@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Reverb;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ReverbParameter Parameter => _parameter;
public Memory<ReverbState> State { get; }

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Upsample;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public uint BufferCount { get; }
public uint InputBufferIndex { get; }

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Volume;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.VolumeRamp;
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; }

View File

@ -28,6 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
private object _lock = new object();
private AudioRendererRenderingDevice _renderingDevice;
private AudioRendererExecutionMode _executionMode;
private IWritableEvent _systemEvent;
private ManualResetEvent _terminationEvent;
@ -63,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server
private uint _renderingTimeLimitPercent;
private bool _voiceDropEnabled;
private uint _voiceDropCount;
private float _voiceDropParameter;
private bool _isDspRunningBehind;
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
@ -95,6 +97,7 @@ namespace Ryujinx.Audio.Renderer.Server
_totalElapsedTicksUpdating = 0;
_sessionId = 0;
_voiceDropParameter = 1.0f;
}
public ResultCode Initialize(
@ -130,6 +133,7 @@ namespace Ryujinx.Audio.Renderer.Server
_upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
_appletResourceId = appletResourceId;
_memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
_renderingDevice = parameter.RenderingDevice;
_executionMode = parameter.ExecutionMode;
_sessionId = sessionId;
MemoryManager = memoryManager;
@ -337,6 +341,7 @@ namespace Ryujinx.Audio.Renderer.Server
_processHandle = processHandle;
_elapsedFrameCount = 0;
_voiceDropParameter = 1.0f;
switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
{
@ -515,7 +520,7 @@ namespace Ryujinx.Audio.Renderer.Server
return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
}
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp)
{
int i;
@ -584,7 +589,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
command.Enabled = false;
voicesEstimatedTime -= (long)command.EstimatedProcessingTime;
voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime);
}
}
}
@ -618,13 +623,13 @@ namespace Ryujinx.Audio.Renderer.Server
_voiceContext.Sort();
commandGenerator.GenerateVoices();
long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
commandGenerator.GenerateSubMixes();
commandGenerator.GenerateFinalMixes();
commandGenerator.GenerateSinks();
long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
if (_voiceDropEnabled)
{
@ -856,5 +861,26 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
}
public void SetVoiceDropParameter(float voiceDropParameter)
{
_voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f);
}
public float GetVoiceDropParameter()
{
return _voiceDropParameter;
}
public ResultCode ExecuteAudioRendererRendering()
{
if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu)
{
// NOTE: Here Nintendo aborts with this error code, we don't want that.
return ResultCode.InvalidExecutionContextOperation;
}
return ResultCode.UnsupportedOperation;
}
}
}

View File

@ -94,8 +94,9 @@ namespace Ryujinx.Audio.Renderer.Server
/// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
/// </summary>
/// <remarks>This was added in system update 14.0.0</remarks>
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
public const int Revision11 = 11 << 24;
/// <summary>

View File

@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// The estimated total processing time.
/// </summary>
public ulong EstimatedProcessingTime { get; set; }
public uint EstimatedProcessingTime { get; set; }
/// <summary>
/// The command list that is populated by the <see cref="CommandBuffer"/>.

View File

@ -263,12 +263,12 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
return UpdateResult.Success;
}
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0)
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0)
{
return UpdateResult.InvalidParameter;
}
if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0)
if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0)
{
return UpdateResult.InvalidParameter;
}

View File

@ -17,5 +17,6 @@ namespace Ryujinx.Audio
InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId,
}
}

View File

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

View File

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

View File

@ -410,6 +410,8 @@
"DlcManagerTableHeadingContainerPathLabel": "Container Path",
"DlcManagerTableHeadingFullPathLabel": "Full Path",
"DlcManagerRemoveAllButton": "Remove All",
"DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language",
"CommonSort": "Sort",
"CommonShowNames": "Show Names",
@ -562,12 +564,12 @@
"Writable": "Writable",
"SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files",
"UserProfileWindowTitle": "Manage User Profiles",
"CheatWindowTitle": "Manage Game Cheats",
"DlcWindowTitle": "Manage Game DLC",
"UpdateWindowTitle": "Manage Game Updates",
"UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Downloadable Content Manager",
"UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "DLC Available for {0} [{1}]",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",
"Save": "Save",
@ -575,7 +577,7 @@
"UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]",
"GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
"UserProfilesName": "Name:",

View File

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

View File

@ -23,19 +23,18 @@ namespace Ryujinx.Ava
{
internal class Program
{
public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
private const uint MB_ICONWARNING = 0x30;
private const int BaseDpi = 96;
private const int BaseDpi = 96;
public static void Main(string[] args)
{
@ -43,7 +42,7 @@ namespace Ryujinx.Ava
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
}
PreviewerDetached = true;
@ -64,16 +63,16 @@ namespace Ryujinx.Ava
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
EnableIme = true,
UseEGL = false,
UseGpu = false
EnableIme = true,
UseEGL = false,
UseGpu = false
})
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
UseWgl = false,
AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8f,
EnableMultitouch = true,
UseWgl = false,
AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8.0f,
})
.UseSkia()
.AfterSetup(_ =>
@ -122,12 +121,10 @@ namespace Ryujinx.Ava
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
if (!hasSystemProdKeys)
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
@ -143,7 +140,7 @@ namespace Ryujinx.Ava
public static void ReloadConfig()
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
// Now load the configuration as the other subsystems are now registered
@ -197,8 +194,7 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{

View File

@ -18,26 +18,26 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.15" />
<PackageReference Include="Avalonia.Svg" Version="0.10.14" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.14" />
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.9.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
<PackageReference Include="DynamicData" Version="7.12.8" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
<PackageReference Include="OpenTK.Core" Version="4.7.2" />
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<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.KHR" Version="2.10.1" />
<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" />
</ItemGroup>

View File

@ -57,14 +57,14 @@ namespace Ryujinx.Ava.Ui.Applet
bool opened = false;
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,
"",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
"",
LocaleManager.Instance["SettingsButtonClose"],
(int)Symbol.Important,
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,
"",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
"",
LocaleManager.Instance["SettingsButtonClose"],
(int)Symbol.Important,
deferEvent,
async (window) =>
{
@ -168,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Applet
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;
}

View File

@ -127,9 +127,16 @@ namespace Ryujinx.Ava.Ui.Controls
contentDialog.PrimaryButtonClick += deferCloseAction;
}
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
if (useOverlay)
{
await contentDialog.ShowAsync(overlay, ContentDialogPlacement.Popup);
overlay?.Close();
overlay!.Close();
}
else
{
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}
}
if (useOverlay)
@ -391,4 +398,4 @@ namespace Ryujinx.Ava.Ui.Controls
return string.Empty;
}
}
}
}

View File

@ -6,8 +6,8 @@ using SPB.Graphics;
using SPB.Platform;
using SPB.Platform.GLX;
using SPB.Platform.X11;
using SPB.Windowing;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
@ -15,12 +15,12 @@ using static Ryujinx.Ava.Ui.Controls.Win32NativeInterop;
namespace Ryujinx.Ava.Ui.Controls
{
public unsafe class EmbeddedWindow : NativeControlHost
public class EmbeddedWindow : NativeControlHost
{
private WindowProc _wndProcDelegate;
private string _className;
protected GLXWindow X11Window { get; private set; }
protected GLXWindow X11Window { get; set; }
protected IntPtr WindowHandle { get; set; }
protected IntPtr X11Display { get; set; }
@ -94,19 +94,17 @@ namespace Ryujinx.Ava.Ui.Controls
}
[SupportedOSPlatform("linux")]
IPlatformHandle CreateLinux(IPlatformHandle parent)
protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
{
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
return new PlatformHandle(WindowHandle, "X11");
}
[SupportedOSPlatform("windows")]
unsafe IPlatformHandle CreateWin32(IPlatformHandle parent)
IPlatformHandle CreateWin32(IPlatformHandle parent)
{
_className = "NativeWindow-" + Guid.NewGuid();
_wndProcDelegate = WndProc;
@ -142,7 +140,7 @@ namespace Ryujinx.Ava.Ui.Controls
}
[SupportedOSPlatform("windows")]
internal IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
{
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
var root = VisualRoot as Window;

View File

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

View File

@ -1,10 +1,13 @@
using Avalonia.Platform;
using Ryujinx.Ava.Ui.Controls;
using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using SPB.Platform.GLX;
using SPB.Platform.Win32;
using SPB.Platform.X11;
using SPB.Windowing;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Ava.Ui
{
@ -12,6 +15,18 @@ namespace Ryujinx.Ava.Ui
{
private NativeWindowBase _window;
[SupportedOSPlatform("linux")]
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
{
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
X11Window.Hide();
return new PlatformHandle(WindowHandle, "X11");
}
public SurfaceKHR CreateSurface(Instance instance)
{
if (OperatingSystem.IsWindows())
@ -20,7 +35,7 @@ namespace Ryujinx.Ava.Ui
}
else if (OperatingSystem.IsLinux())
{
_window = X11Window;
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
}
else
{

View File

@ -1,8 +1,22 @@
namespace Ryujinx.Ava.Ui.Models
using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Models
{
public class DownloadableContentModel
public class DownloadableContentModel : BaseModel
{
public bool Enabled { get; set; }
private bool _enabled;
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
OnPropertyChanged();
}
}
public string TitleId { get; }
public string ContainerPath { get; }
public string FullPath { get; }

View File

@ -8,6 +8,7 @@ using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -189,7 +190,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
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();
}
@ -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();
ParseAmiiboData();

View File

@ -460,8 +460,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
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)
{
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);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null)
{
if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id)))

View File

@ -19,6 +19,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
@ -47,6 +48,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _loadHeading;
private string _cacheLoadStatus;
private string _searchText;
private Timer _searchTimer;
private string _dockedStatusText;
private string _fifoStatusText;
private string _gameStatusText;
@ -115,10 +117,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
_searchText = value;
RefreshView();
_searchTimer?.Dispose();
_searchTimer = new Timer(TimerCallback, null, 1000, 0);
}
}
private void TimerCallback(object obj)
{
RefreshView();
_searchTimer.Dispose();
_searchTimer = null;
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
{
get => _appsObservableList;
@ -200,22 +212,19 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _showUikey = "F4";
private string _pauseKey = "F5";
private string _screenshotkey = "F8";
private float _volume;
private float _volume;
private string _backendText;
public ApplicationData SelectedApplication
{
get
{
switch (Glyph)
return Glyph switch
{
case Glyph.List:
return _owner.GameList.SelectedApplication;
case Glyph.Grid:
return _owner.GameGrid.SelectedApplication;
default:
return null;
}
Glyph.List => _owner.GameList.SelectedApplication,
Glyph.Grid => _owner.GameGrid.SelectedApplication,
_ => null,
};
}
}
@ -408,6 +417,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
_owner.AppHost.Device.SetVolume(_volume);
}
OnPropertyChanged(nameof(VolumeStatusText));
OnPropertyChanged(nameof(VolumeMuted));
OnPropertyChanged();
@ -477,38 +487,36 @@ namespace Ryujinx.Ava.Ui.ViewModels
internal void Sort(bool isAscending)
{
IsAscending = isAscending;
RefreshView();
}
internal void Sort(ApplicationSort sort)
{
SortMode = sort;
RefreshView();
}
private IComparer<ApplicationData> GetComparer()
{
switch (SortMode)
return SortMode switch
{
case ApplicationSort.LastPlayed:
return new Models.Generic.LastPlayedSortComparer(IsAscending);
case ApplicationSort.FileSize:
return new Models.Generic.FileSizeSortComparer(IsAscending);
case ApplicationSort.TotalTimePlayed:
return new Models.Generic.TimePlayedSortComparer(IsAscending);
case ApplicationSort.Title:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName);
case ApplicationSort.Favorite:
return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite);
case ApplicationSort.Developer:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer);
case ApplicationSort.FileType:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension);
case ApplicationSort.Path:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
default:
return null;
}
ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending),
ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending),
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
_ => null,
};
}
private void RefreshView()
@ -611,40 +619,31 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public string SortName
{
get
{
switch (SortMode)
return SortMode switch
{
case ApplicationSort.Title:
return LocaleManager.Instance["GameListHeaderApplication"];
case ApplicationSort.Developer:
return LocaleManager.Instance["GameListHeaderDeveloper"];
case ApplicationSort.LastPlayed:
return LocaleManager.Instance["GameListHeaderLastPlayed"];
case ApplicationSort.TotalTimePlayed:
return LocaleManager.Instance["GameListHeaderTimePlayed"];
case ApplicationSort.FileType:
return LocaleManager.Instance["GameListHeaderFileExtension"];
case ApplicationSort.FileSize:
return LocaleManager.Instance["GameListHeaderFileSize"];
case ApplicationSort.Path:
return LocaleManager.Instance["GameListHeaderPath"];
case ApplicationSort.Favorite:
return LocaleManager.Instance["CommonFavorite"];
}
return string.Empty;
ApplicationSort.Title => LocaleManager.Instance["GameListHeaderApplication"],
ApplicationSort.Developer => LocaleManager.Instance["GameListHeaderDeveloper"],
ApplicationSort.LastPlayed => LocaleManager.Instance["GameListHeaderLastPlayed"],
ApplicationSort.TotalTimePlayed => LocaleManager.Instance["GameListHeaderTimePlayed"],
ApplicationSort.FileType => LocaleManager.Instance["GameListHeaderFileExtension"],
ApplicationSort.FileSize => LocaleManager.Instance["GameListHeaderFileSize"],
ApplicationSort.Path => LocaleManager.Instance["GameListHeaderPath"],
ApplicationSort.Favorite => LocaleManager.Instance["CommonFavorite"],
_ => string.Empty,
};
}
}
@ -668,6 +667,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_showUikey); set
{
_showUikey = value.ToString();
OnPropertyChanged();
}
}
@ -677,6 +677,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_screenshotkey); set
{
_screenshotkey = value.ToString();
OnPropertyChanged();
}
}
@ -686,14 +687,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_pauseKey); set
{
_pauseKey = value.ToString();
OnPropertyChanged();
}
}
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public int GridSizeScale
{
@ -728,14 +730,14 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
await window.ShowDialog(_owner);
if (window.IsScanned)
{
_showAll = window.ViewModel.ShowAllAmiibo;
_showAll = window.ViewModel.ShowAllAmiibo;
_lastScannedAmiiboId = window.ScannedAmiibo.GetId();
_owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
@ -766,8 +768,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{
StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressMaximum = e.NumAppsFound;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
Dispatcher.UIThread.Post(() =>
@ -792,9 +795,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
await Dispatcher.UIThread.InvokeAsync(() =>
{
Applications.Clear();
_owner.LoadProgressBar.IsVisible = true;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
});
@ -842,12 +847,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
});
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
string[] files = await dialog.ShowAsync(_owner);
@ -878,10 +883,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
@ -941,9 +948,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_lastFullscreenToggle = Environment.TickCount64;
WindowState state = _owner.WindowState;
if (state == WindowState.FullScreen)
if (_owner.WindowState == WindowState.FullScreen)
{
_owner.WindowState = WindowState.Normal;
@ -971,8 +976,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
if (IsGameRunning)
{
ConfigurationState.Instance.System.EnableDockedMode.Value =
!ConfigurationState.Instance.System.EnableDockedMode.Value;
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
}
@ -985,6 +989,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
else if (IsGameRunning)
{
await Task.Delay(100);
_owner.AppHost?.ShowExitPrompt();
}
}
@ -994,6 +999,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
await _owner.SettingsWindow.ShowDialog(_owner);
LoadConfigurableHotKeys();
}
@ -1004,9 +1010,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenAboutWindow()
{
AboutWindow window = new();
await window.ShowDialog(_owner);
await new AboutWindow().ShowDialog(_owner);
}
public void ChangeLanguage(object obj)
@ -1020,7 +1024,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
try
{
ProgressMaximum = total;
ProgressValue = current;
ProgressValue = current;
switch (state)
{
@ -1030,13 +1034,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
case PtcLoadingState.Start:
case PtcLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
IsLoadingIndeterminate = false;
break;
case PtcLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
CacheLoadStatus = "";
break;
}
break;
@ -1046,13 +1050,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
case ShaderCacheLoadingState.Start:
case ShaderCacheLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingShaders"];
LoadHeading = LocaleManager.Instance["CompilingShaders"];
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
CacheLoadStatus = "";
break;
}
break;
@ -1065,14 +1069,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenUserSaveDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
@ -1082,8 +1084,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
return;
}
var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
UserId userId = new((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
});
}
@ -1091,8 +1093,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void ToggleFavorite()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
selection.Favorite = !selection.Favorite;
@ -1108,11 +1109,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenModsDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath);
@ -1121,12 +1121,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenSdModsDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
@ -1134,13 +1134,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenPtcDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string backupPath = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir))
@ -1156,16 +1154,18 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgePtcCache()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new();
@ -1198,8 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenShaderCacheDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
@ -1220,18 +1219,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgeShaderCache()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
List<FileInfo> newCacheFiles = new List<FileInfo>();
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
{
@ -1279,38 +1280,28 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenTitleUpdateManager()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
TitleUpdateWindow titleUpdateManager =
new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
await titleUpdateManager.ShowDialog(_owner);
await new TitleUpdateWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
}
}
public async void OpenDownloadableContentManager()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName);
await downloadableContentManager.ShowDialog(_owner);
await new DownloadableContentManagerWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
}
}
public async void OpenCheatManager()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
await cheatManager.ShowDialog(_owner);
await new CheatWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner);
}
}
@ -1321,13 +1312,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
return;
}
var application = _owner.AppHost.Device.Application;
ApplicationLoader application = _owner.AppHost.Device.Application;
if (application != null)
{
CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName);
await cheatManager.ShowDialog(_owner);
await new CheatWindow(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(_owner);
_owner.AppHost.Device.EnableCheats();
}
@ -1335,14 +1323,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenDeviceSaveDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
@ -1360,14 +1346,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenBcatSaveDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
@ -1420,12 +1404,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.Close();
}
private async Task HandleFirmwareInstallation(string path)
private async Task HandleFirmwareInstallation(string filename)
{
try
{
string filename = path;
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
if (firmwareVersion == null)
@ -1437,7 +1419,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
@ -1480,11 +1461,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists)
{
@ -1514,8 +1496,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
catch (LibHac.Common.Keys.MissingKeyException ex)
{
Logger.Error?.Print(LogClass.Application, ex.ToString());
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
}
catch (Exception ex)
{
@ -1527,8 +1509,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
OpenFileDialog dialog = new() { AllowMultiple = false };
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
string[] file = await dialog.ShowAsync(_owner);

View File

@ -3,89 +3,128 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="800"
Height="500"
MinWidth="800"
MinHeight="500"
MaxWidth="800"
MaxHeight="500"
SizeToContent="Height"
Width="600" MinHeight="500" Height="500"
WindowStartupLocation="CenterOwner"
MinWidth="600"
mc:Ignorable="d">
<Grid Name="DownloadableContentGrid" Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Name="Heading"
Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18"
TextWrapping="Wrap"
Text="{Binding Heading}"
TextAlignment="Center" />
<Border
TextAlignment="Center"
TextWrapping="Wrap" />
<DockPanel
Grid.Row="2"
Margin="0"
HorizontalAlignment="Left">
<Button
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</DockPanel>
<Border
Grid.Row="3"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="Gray"
BorderThickness="1">
<DataGrid
MinHeight="200"
HorizontalAlignment="Stretch"
<ScrollViewer
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
Items="{Binding DownloadableContents}"
VerticalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTemplateColumn Width="90">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Width="50"
MinWidth="40"
HorizontalAlignment="Right"
IsChecked="{Binding Enabled}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="190"
Binding="{Binding TitleId}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn
Width="*"
Binding="{Binding ContainerPath}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn
Width="*"
Binding="{Binding FullPath}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid
Name="DlcDataGrid"
MinHeight="200"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="True"
HorizontalScrollBarVisibility="Auto"
Items="{Binding _downloadableContents}"
SelectionMode="Extended"
VerticalScrollBarVisibility="Auto">
<DataGrid.Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(1)">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</Styles>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Width="90">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Width="50"
MinWidth="40"
HorizontalAlignment="Center"
IsChecked="{Binding Enabled}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding ContainerPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Border>
<DockPanel
Grid.Row="3"
Grid.Row="4"
Margin="0"
HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left">

View File

@ -18,6 +18,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Path = System.IO.Path;
@ -27,14 +28,13 @@ namespace Ryujinx.Ava.Ui.Windows
public partial class DownloadableContentManagerWindow : StyleableWindow
{
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath;
private readonly string _downloadableContentJsonPath;
public VirtualFileSystem VirtualFileSystem { get; }
public AvaloniaList<DownloadableContentModel> DownloadableContents { get; set; } = new AvaloniaList<DownloadableContentModel>();
public ulong TitleId { get; }
public string TitleName { get; }
private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16"));
private ulong _titleId { get; }
private string _titleName { get; }
public DownloadableContentManagerWindow()
{
@ -42,14 +42,16 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
}
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
VirtualFileSystem = virtualFileSystem;
TitleId = titleId;
TitleName = titleName;
_virtualFileSystem = virtualFileSystem;
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
@ -66,9 +68,24 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
RemoveButton.IsEnabled = false;
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadDownloadableContents();
PrintHeading();
}
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
}
private void LoadDownloadableContents()
@ -79,23 +96,23 @@ namespace Ryujinx.Ava.Ui.Windows
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
VirtualFileSystem.ImportTickets(pfs);
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
using var ncaFile = new UniqueRef<IFile>();
using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled));
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled));
}
}
}
@ -105,11 +122,11 @@ namespace Ryujinx.Ava.Ui.Windows
Save();
}
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(VirtualFileSystem.KeySet, ncaStorage);
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
@ -124,61 +141,73 @@ namespace Ryujinx.Ava.Ui.Windows
private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
return;
}
using (FileStream containerFile = File.OpenRead(path))
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
bool containsDownloadableContent = false;
using var ncaFile = new UniqueRef<IFile>();
VirtualFileSystem.ImportTickets(pfs);
partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId)
{
break;
}
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
continue;
}
if (!containsDownloadableContent)
if (nca.Header.ContentType == NcaContentType.PublicData)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
}
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
}
}
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
{
if (removeSelectedOnly)
{
DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList());
AvaloniaList<DownloadableContentModel> removedItems = new();
foreach (var item in DlcDataGrid.SelectedItems)
{
removedItems.Add(item as DownloadableContentModel);
}
DlcDataGrid.SelectedItems.Clear();
foreach (var item in removedItems)
{
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
}
}
else
{
DownloadableContents.Clear();
_downloadableContents.Clear();
}
PrintHeading();
}
public void RemoveSelected()
@ -191,6 +220,22 @@ namespace Ryujinx.Ava.Ui.Windows
RemoveDownloadableContents();
}
public void EnableAll()
{
foreach(var item in _downloadableContents)
{
item.Enabled = true;
}
}
public void DisableAll()
{
foreach (var item in _downloadableContents)
{
item.Enabled = false;
}
}
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
@ -214,6 +259,8 @@ namespace Ryujinx.Ava.Ui.Windows
await AddDownloadableContent(file);
}
}
PrintHeading();
}
public void Save()
@ -222,7 +269,7 @@ namespace Ryujinx.Ava.Ui.Windows
DownloadableContentContainer container = default;
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
foreach (DownloadableContentModel downloadableContent in _downloadableContents)
{
if (container.ContainerPath != downloadableContent.ContainerPath)
{

View File

@ -90,8 +90,8 @@ namespace Ryujinx.Ava.Ui.Windows
Title = $"Ryujinx {Program.Version}";
Height = Height / Program.WindowScaleFactor;
Width = Width / Program.WindowScaleFactor;
Height /= Program.WindowScaleFactor;
Width /= Program.WindowScaleFactor;
if (Program.PreviewerDetached)
{
@ -251,24 +251,29 @@ namespace Ryujinx.Ava.Ui.Windows
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.TitleName = string.IsNullOrWhiteSpace(titleName) ? 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;
SwitchToGameControl(startFullscreen);
SwitchToGameControl(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new Thread(InitializeGame)
{
Name = "GUI.WindowThread"
};
gameThread.Start();
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame)
{
Name = "GUI.WindowThread"
};
gameThread.Start();
});
}
private void InitializeGame()
@ -518,23 +523,20 @@ namespace Ryujinx.Ava.Ui.Windows
public static void UpdateGraphicsConfig()
{
int resScale = ConfigurationState.Instance.Graphics.ResScale;
float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
}
public void LoadHotKeys()
{
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
}
public static void SaveConfig()
@ -546,10 +548,12 @@ namespace Ryujinx.Ava.Ui.Windows
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
});
}

View File

@ -75,7 +75,7 @@
Spacing="10">
<ListBox
Name="GameList"
MinHeight="150"
MinHeight="250"
Items="{Binding GameDirectories}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>

View File

@ -16,7 +16,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows
@ -31,7 +30,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
DataContext = ViewModel;
InitializeComponent();
@ -48,7 +47,7 @@ namespace Ryujinx.Ava.Ui.Windows
public SettingsWindow()
{
ViewModel = new SettingsViewModel();
ViewModel = new SettingsViewModel();
DataContext = ViewModel;
InitializeComponent();
@ -79,7 +78,7 @@ namespace Ryujinx.Ava.Ui.Windows
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner);
@ -92,6 +91,7 @@ namespace Ryujinx.Ava.Ui.Windows
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
@ -122,36 +122,19 @@ namespace Ryujinx.Ava.Ui.Windows
{
if (e.SelectedItem is NavigationViewItem navitem)
{
switch (navitem.Tag.ToString())
NavPanel.Content = navitem.Tag.ToString() switch
{
case "UiPage":
NavPanel.Content = UiPage;
break;
case "InputPage":
NavPanel.Content = InputPage;
break;
case "HotkeysPage":
NavPanel.Content = HotkeysPage;
break;
case "SystemPage":
NavPanel.Content = SystemPage;
break;
case "CpuPage":
NavPanel.Content = CpuPage;
break;
case "GraphicsPage":
NavPanel.Content = GraphicsPage;
break;
case "AudioPage":
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":
NavPanel.Content = LoggingPage;
break;
}
"UiPage" => UiPage,
"InputPage" => InputPage,
"HotkeysPage" => HotkeysPage,
"SystemPage" => SystemPage,
"CpuPage" => CpuPage,
"GraphicsPage" => GraphicsPage,
"AudioPage" => AudioPage,
"NetworkPage" => NetworkPage,
"LoggingPage" => LoggingPage,
_ => throw new NotImplementedException()
};
}
}
@ -178,13 +161,18 @@ namespace Ryujinx.Ava.Ui.Windows
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{
List<string> selected = new(GameList.SelectedItems.Cast<string>());
int oldIndex = GameList.SelectedIndex;
foreach (string path in selected)
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
{
ViewModel.GameDirectories.Remove(path);
ViewModel.DirectoryChanged = true;
}
if (GameList.ItemCount > 0)
{
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
}
}
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
@ -214,7 +202,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
SaveSettings();
Close();
}
@ -232,7 +219,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveSettings()
{
ViewModel.SaveSettings();
ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
@ -246,8 +232,10 @@ namespace Ryujinx.Ava.Ui.Windows
protected override void OnClosed(EventArgs e)
{
ControllerSettings.Dispose();
_currentAssigner?.Cancel();
_currentAssigner = null;
base.OnClosed(e);
}
}

View File

@ -3,13 +3,17 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
SizeToContent="Height"
Width="600" MinHeight="500" Height="500"
WindowStartupLocation="CenterOwner"
Width="600"
Height="400"
MinWidth="600"
MinHeight="400"
MaxWidth="600"
MaxHeight="400"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid Margin="15">
<Grid.RowDefinitions>
@ -19,15 +23,15 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Name="Heading"
Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18"
TextWrapping="Wrap"
Text="{Binding Heading}"
TextAlignment="Center" />
TextAlignment="Center"
TextWrapping="Wrap" />
<Border
Grid.Row="2"
Margin="5"
@ -36,8 +40,6 @@
BorderBrush="Gray"
BorderThickness="1">
<ScrollViewer
Width="550"
MinHeight="200"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
@ -45,11 +47,19 @@
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Items="{Binding TitleUpdates}">
Items="{Binding _titleUpdates}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}">
<Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" />
<RadioButton
Padding="8,0"
VerticalContentAlignment="Center"
GroupName="Update"
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
<Label
Margin="0"
VerticalAlignment="Center"
Content="{Binding Label}"
FontSize="12" />
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>

View File

@ -30,13 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows
private readonly string _titleUpdateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; }
private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>();
public string TitleId { get; }
public string TitleName { get; }
public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper());
private ulong _titleId { get; }
private string _titleName { get; }
public TitleUpdateWindow()
{
@ -44,16 +42,18 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
}
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName)
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
VirtualFileSystem = virtualFileSystem;
TitleId = titleId;
TitleName = titleName;
_virtualFileSystem = virtualFileSystem;
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
_titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
{
@ -64,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData = new TitleUpdateMetadata
{
Selected = "",
Paths = new List<string>()
Paths = new List<string>()
};
}
@ -72,14 +72,20 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadUpdates();
PrintHeading();
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
}
private void LoadUpdates()
{
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
foreach (string path in _titleUpdateWindowData.Paths)
{
@ -88,12 +94,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (_titleUpdateWindowData.Selected == "")
{
TitleUpdates[0].IsEnabled = true;
_titleUpdates[0].IsEnabled = true;
}
else
{
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList();
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
foreach (TitleUpdateModel update in enabled)
{
@ -111,43 +117,47 @@ namespace Ryujinx.Ava.Ui.Windows
private void AddUpdate(string path)
{
if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path))
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
{
using (FileStream file = new(path, FileMode.Open, FileAccess.Read))
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
try
{
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
try
if (controlNca != null && patchNca != null)
{
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0);
ApplicationControlProperty controlData = new();
if (controlNca != null && patchNca != null)
using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
_titleUpdates.Add(new TitleUpdateModel(controlData, path));
foreach (var update in _titleUpdates)
{
ApplicationControlProperty controlData = new ApplicationControlProperty();
using var nacpFile = new UniqueRef<IFile>();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
});
update.IsEnabled = false;
}
_titleUpdates.Last().IsEnabled = true;
}
catch (Exception ex)
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
});
}
}
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
});
}
}
}
@ -155,16 +165,17 @@ namespace Ryujinx.Ava.Ui.Windows
{
if (removeSelectedOnly)
{
TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
}
else
{
TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList());
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
}
TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
SortUpdates();
PrintHeading();
}
public void RemoveSelected()
@ -179,7 +190,7 @@ namespace Ryujinx.Ava.Ui.Windows
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
AllowMultiple = true
@ -202,11 +213,12 @@ namespace Ryujinx.Ava.Ui.Windows
}
SortUpdates();
PrintHeading();
}
private void SortUpdates()
{
var list = TitleUpdates.ToList();
var list = _titleUpdates.ToList();
list.Sort((first, second) =>
{
@ -222,8 +234,8 @@ namespace Ryujinx.Ava.Ui.Windows
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
});
TitleUpdates.Clear();
TitleUpdates.AddRange(list);
_titleUpdates.Clear();
_titleUpdates.AddRange(list);
}
public void Save()
@ -232,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates)
foreach (TitleUpdateModel update in _titleUpdates)
{
_titleUpdateWindowData.Paths.Add(update.Path);

View File

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

View File

@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" />
<PackageReference Include="System.Management" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
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>
/// Writes data to CPU mapped memory.
/// </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/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{

View File

@ -1,6 +1,7 @@
using Ryujinx.Memory;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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>();
byte[] data = new byte[size];
memory.Read(position, data);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
return MemoryMarshal.Cast<byte, T>(memory.GetSpan(position, Unsafe.SizeOf<T>()))[0];
}
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>();
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
memory.Write(position, data);
return (ulong)size;
return (ulong)data.Length;
}
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

@ -86,12 +86,12 @@ namespace Ryujinx.Graphics.GAL
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 SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetUserClipDistance(int index, bool enableClip);

View File

@ -142,6 +142,30 @@ namespace Ryujinx.Graphics.GAL.Multithreading
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)
{
// 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
{
public CommandType CommandType => CommandType.SetStorageBuffers;
private int _first;
private SpanRef<BufferRange> _buffers;
private SpanRef<BufferAssignment> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers)
public void Set(SpanRef<BufferAssignment> buffers)
{
_first = first;
_buffers = buffers;
}
public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
Span<BufferRange> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetStorageBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers));
Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetStorageBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded);
}
}

View File

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

View File

@ -275,9 +275,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_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();
}
@ -293,9 +293,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_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();
}

View File

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

View File

@ -51,16 +51,35 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
/// </summary>
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>
/// Fetch the command buffer.
/// </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>
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)
{
Words = MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)).ToArray();
}
return Words ?? GetWords(memoryManager, flush);
}
}
@ -158,7 +177,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
{
commandBuffer.Fetch(processor.MemoryManager);
commandBuffer.Prefetch(processor.MemoryManager);
}
if (commandBuffer.Type == CommandBufferType.NoPrefetch)
@ -199,7 +218,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
}
_currentCommandBuffer = entry;
_currentCommandBuffer.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
ReadOnlySpan<int> words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
// If we are changing the current channel,
// 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.Process(entry.EntryAddress, _currentCommandBuffer.Words);
entry.Processor.Process(entry.EntryAddress, words);
}
_interrupt = false;

View File

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

View File

@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private bool _vsUsesDrawParameters;
private bool _vtgWritesRtLayer;
private byte _vsClipDistancesWritten;
private uint _vbEnableMask;
private bool _prevDrawIndexed;
private bool _prevDrawIndirect;
@ -76,6 +77,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.VertexBufferState),
nameof(ThreedClassState.VertexBufferEndAddress)),
// Must be done after vertex buffer updates.
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
new StateUpdateCallbackEntry(UpdateBlendState,
@ -852,12 +854,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateVertexAttribState()
{
uint vbEnableMask = _vbEnableMask;
Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs];
for (int index = 0; index < Constants.TotalVertexAttribs; index++)
{
var vertexAttrib = _state.State.VertexAttribState[index];
int bufferIndex = vertexAttrib.UnpackBufferIndex();
if ((vbEnableMask & (1u << bufferIndex)) == 0)
{
// Using a vertex buffer that doesn't exist is invalid, so let's use a dummy attribute for those cases.
vertexAttribs[index] = new VertexAttribDescriptor(0, 0, true, Format.R32G32B32A32Float);
continue;
}
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
{
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
@ -866,7 +879,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
vertexAttribs[index] = new VertexAttribDescriptor(
vertexAttrib.UnpackBufferIndex(),
bufferIndex,
vertexAttrib.UnpackOffset(),
vertexAttrib.UnpackIsConstant(),
format);
@ -954,6 +967,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool drawIndexed = _drawState.DrawIndexed;
bool drawIndirect = _drawState.DrawIndirect;
uint vbEnableMask = 0;
for (int index = 0; index < Constants.TotalVertexBuffers; index++)
{
@ -971,6 +985,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong address = vertexBuffer.Address.Pack();
if (_channel.MemoryManager.IsMapped(address))
{
vbEnableMask |= 1u << index;
}
int stride = vertexBuffer.UnpackStride();
bool instanced = _state.State.VertexBufferInstanced[index];
@ -1017,6 +1036,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
}
if (_vbEnableMask != vbEnableMask)
{
_vbEnableMask = vbEnableMask;
UpdateVertexAttribState();
}
}
/// <summary>

View File

@ -22,6 +22,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context;
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 Buffer[] _bufferOverlaps;
@ -200,12 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size)
{
int overlapsCount;
lock (_buffers)
{
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
}
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
if (overlapsCount != 0)
{
@ -410,10 +409,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0)
{
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, size);
}
buffer = _buffers.FindFirstOverlap(address, size);
buffer.SynchronizeMemory(address, size);
@ -424,10 +420,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, 1);
}
buffer = _buffers.FindFirstOverlap(address, 1);
}
return buffer;
@ -442,12 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (size != 0)
{
Buffer buffer;
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, size);
}
Buffer buffer = _buffers.FindFirstOverlap(address, size);
buffer.SynchronizeMemory(address, size);
}

View File

@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures;
private readonly BufferRange[] _ranges;
private readonly BufferAssignment[] _ranges;
/// <summary>
/// Holds shader stage buffer state and binding information.
@ -134,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures = new List<BufferTextureBinding>();
_ranges = new BufferRange[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
}
@ -618,10 +618,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
{
int rangesFirst = 0;
int rangesCount = 0;
Span<BufferRange> ranges = _ranges;
Span<BufferAssignment> ranges = _ranges;
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
@ -640,25 +639,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0)
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
}
}
if (rangesCount != 0)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
SetHostBuffers(ranges, rangesCount, isStorage);
}
}
@ -671,10 +659,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
{
int rangesFirst = 0;
int rangesCount = 0;
Span<BufferRange> ranges = _ranges;
Span<BufferAssignment> ranges = _ranges;
for (int index = 0; index < buffers.Count; index++)
{
@ -689,24 +676,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0)
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
}
if (rangesCount != 0)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
SetHostBuffers(ranges, rangesCount, isStorage);
}
}
@ -718,15 +694,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="count">Number of bindings</param>
/// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param>
[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)
{
_context.Renderer.Pipeline.SetStorageBuffers(first, ranges.Slice(0, count));
_context.Renderer.Pipeline.SetStorageBuffers(ranges.Slice(0, count));
}
else
{
_context.Renderer.Pipeline.SetUniformBuffers(first, ranges.Slice(0, count));
_context.Renderer.Pipeline.SetUniformBuffers(ranges.Slice(0, count));
}
}

View File

@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
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);
/// <summary>

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 3868;
private const uint CodeGenVersion = 3943;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -331,7 +331,7 @@ namespace Ryujinx.Graphics.OpenGL
case PrimitiveTopology.QuadStrip:
return PrimitiveType.QuadStrip;
case PrimitiveTopology.Polygon:
return PrimitiveType.Polygon;
return PrimitiveType.TriangleFan;
case PrimitiveTopology.LinesAdjacency:
return PrimitiveType.LinesAdjacency;
case PrimitiveTopology.LineStripAdjacency:

View File

@ -1296,9 +1296,9 @@ namespace Ryujinx.Graphics.OpenGL
_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)
@ -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)
@ -1460,21 +1460,22 @@ namespace Ryujinx.Graphics.OpenGL
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;
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)
{
GL.BindBufferRange(target, first + index, 0, IntPtr.Zero, 0);
GL.BindBufferRange(target, assignment.Binding, 0, IntPtr.Zero, 0);
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

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
</ItemGroup>
<ItemGroup>

View File

@ -10,13 +10,9 @@ namespace Ryujinx.Graphics.OpenGL
{
public int Handle { get; private set; }
private bool _needsAttribsUpdate;
private readonly VertexAttribDescriptor[] _vertexAttribs;
private readonly VertexBufferDescriptor[] _vertexBuffers;
private int _vertexAttribsCount;
private int _vertexBuffersCount;
private int _minVertexCount;
private uint _vertexAttribsInUse;
@ -76,9 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexBuffers[bindingIndex] = vb;
}
_vertexBuffersCount = bindingIndex;
_minVertexCount = minVertexCount;
_needsAttribsUpdate = true;
}
public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
@ -131,8 +125,6 @@ namespace Ryujinx.Graphics.OpenGL
_vertexAttribs[index] = attrib;
}
_vertexAttribsCount = index;
for (; index < Constants.MaxVertexAttribs; index++)
{
DisableVertexAttrib(index);
@ -160,13 +152,11 @@ namespace Ryujinx.Graphics.OpenGL
public void PreDraw(int vertexCount)
{
LimitVertexBuffers(vertexCount);
Validate();
}
public void PreDrawVbUnbounded()
{
UnlimitVertexBuffers();
Validate();
}
public void LimitVertexBuffers(int vertexCount)
@ -252,36 +242,6 @@ namespace Ryujinx.Graphics.OpenGL
_vertexBuffersLimited = 0;
}
public void Validate()
{
for (int attribIndex = 0; attribIndex < _vertexAttribsCount; attribIndex++)
{
VertexAttribDescriptor attrib = _vertexAttribs[attribIndex];
if (!attrib.IsZero)
{
if ((uint)attrib.BufferIndex >= _vertexBuffersCount)
{
DisableVertexAttrib(attribIndex);
continue;
}
if (_vertexBuffers[attrib.BufferIndex].Buffer.Handle == BufferHandle.Null)
{
DisableVertexAttrib(attribIndex);
continue;
}
if (_needsAttribsUpdate)
{
EnableVertexAttrib(attribIndex);
}
}
}
_needsAttribsUpdate = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnableVertexAttrib(int index)
{

View File

@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.OpenGL
private const int TextureCount = 3;
private readonly OpenGLRenderer _renderer;
private bool _initialized;
private int _width;
private int _height;
private int _copyFramebufferHandle;
@ -179,6 +181,7 @@ namespace Ryujinx.Graphics.OpenGL
public void InitializeBackgroundContext(IOpenGLContext baseContext)
{
BackgroundContext = new BackgroundContextWorker(baseContext);
_initialized = true;
}
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()
{
if (!_initialized)
{
return;
}
BackgroundContext.Dispose();
if (_copyFramebufferHandle != 0)
@ -203,4 +211,4 @@ namespace Ryujinx.Graphics.OpenGL
}
}
}
}
}

View File

@ -234,7 +234,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var source = operation.GetSource(0);
var uvec4Type = context.TypeVector(context.TypeU32(), 4);
var execution = context.Constant(context.TypeU32(), 3); // Subgroup
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
var maskVector = context.GroupNonUniformBallot(uvec4Type, execution, context.Get(AggregateType.Bool, source));
var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)0);
@ -1233,7 +1233,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
var srcThreadId = context.BitwiseOr(context.TypeU32(), indexNotSegMask, minThreadId);
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
var result = context.Select(context.TypeFP32(), valid, value, x);
var validLocal = (AstOperand)operation.GetSource(3);
@ -1263,7 +1263,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
var srcThreadId = context.IAdd(context.TypeU32(), threadId, index);
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
var result = context.Select(context.TypeFP32(), valid, value, x);
var validLocal = (AstOperand)operation.GetSource(3);
@ -1289,7 +1289,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
var valid = context.SGreaterThanEqual(context.TypeBool(), srcThreadId, minThreadId);
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
var result = context.Select(context.TypeFP32(), valid, value, x);
var validLocal = (AstOperand)operation.GetSource(3);
@ -1319,7 +1319,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
var srcThreadId = context.BitwiseXor(context.TypeU32(), threadId, index);
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
var result = context.Select(context.TypeFP32(), valid, value, x);
var validLocal = (AstOperand)operation.GetSource(3);
@ -1861,19 +1861,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateVoteAll(CodeGenContext context, AstOperation operation)
{
var result = context.SubgroupAllKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
var result = context.GroupNonUniformAll(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0)));
return new OperationResult(AggregateType.Bool, result);
}
private static OperationResult GenerateVoteAllEqual(CodeGenContext context, AstOperation operation)
{
var result = context.SubgroupAllEqualKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
var result = context.GroupNonUniformAllEqual(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0)));
return new OperationResult(AggregateType.Bool, result);
}
private static OperationResult GenerateVoteAny(CodeGenContext context, AstOperation operation)
{
var result = context.SubgroupAnyKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
var result = context.GroupNonUniformAny(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0)));
return new OperationResult(AggregateType.Bool, result);
}

View File

@ -50,12 +50,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
CodeGenContext context = new CodeGenContext(info, config, instPool, integerPool);
context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle);
context.AddCapability(Capability.GroupNonUniformVote);
context.AddCapability(Capability.ImageBuffer);
context.AddCapability(Capability.ImageGatherExtended);
context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer);
context.AddCapability(Capability.SubgroupBallotKHR);
context.AddCapability(Capability.SubgroupVoteKHR);
if (config.TransformFeedbackEnabled && config.LastInVertexPipeline)
{
@ -94,9 +94,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.DrawParameters);
}
context.AddExtension("SPV_KHR_shader_ballot");
context.AddExtension("SPV_KHR_subgroup_vote");
Declarations.DeclareAll(context, info);
if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)

View File

@ -128,6 +128,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) :
(null, 0);
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (byteOffset == null)
{
Operand baseAddrLow = Cbuf(0, baseAddressCbOffset);
@ -156,11 +161,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
byteOffset = offset;
}
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (isStg16Or8)
{
return byteOffset;

View File

@ -163,12 +163,13 @@ namespace Ryujinx.Graphics.Vulkan
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++)
{
var buffer = buffers[i];
int index = first + i;
var assignment = buffers[i];
var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
@ -243,12 +244,13 @@ namespace Ryujinx.Graphics.Vulkan
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++)
{
var buffer = buffers[i];
int index = first + i;
var assignment = buffers[i];
var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];

View File

@ -180,6 +180,7 @@ namespace Ryujinx.Graphics.Vulkan
GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
GAL.PrimitiveTopology.Polygon => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan,
GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."),
GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."),
_ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)

View File

@ -177,7 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
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];
@ -240,7 +240,7 @@ namespace Ryujinx.Graphics.Vulkan
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];
@ -302,7 +302,7 @@ namespace Ryujinx.Graphics.Vulkan
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];
@ -380,7 +380,7 @@ namespace Ryujinx.Graphics.Vulkan
_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];
@ -571,7 +571,7 @@ namespace Ryujinx.Graphics.Vulkan
int conversionType = srcIsMs ? src.Info.BytesPerPixel : -src.Info.BytesPerPixel;
_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 ||
dst.Info.Target == Target.Texture2DMultisampleArray)
@ -776,7 +776,7 @@ namespace Ryujinx.Graphics.Vulkan
srcIndirectBufferOffset,
indirectDataSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { drawCountBufferAligned });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) });
_pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndirectData);
@ -804,7 +804,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
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.SetProgram(_programConvertIndexBuffer);

View File

@ -328,7 +328,8 @@ namespace Ryujinx.Graphics.Vulkan
IndexBufferPattern pattern = _topology switch
{
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern,
GAL.PrimitiveTopology.TriangleFan or
GAL.PrimitiveTopology.Polygon => TriFanToTrisPattern,
_ => throw new NotSupportedException($"Unsupported topology: {_topology}")
};
@ -359,7 +360,8 @@ namespace Ryujinx.Graphics.Vulkan
pattern = _topology switch
{
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern,
GAL.PrimitiveTopology.TriangleFan or
GAL.PrimitiveTopology.Polygon => TriFanToTrisPattern,
_ => throw new NotSupportedException($"Unsupported topology: {_topology}")
};
}
@ -973,9 +975,9 @@ namespace Ryujinx.Graphics.Vulkan
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)
@ -1013,9 +1015,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)

View File

@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.2" />
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
<PackageReference Include="shaderc.net" Version="0.1.0" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />

View File

@ -37,7 +37,6 @@ namespace Ryujinx.Graphics.Vulkan
public static string[] RequiredExtensions { get; } = new string[]
{
KhrSwapchain.ExtensionName,
"VK_EXT_shader_subgroup_vote",
ExtTransformFeedback.ExtensionName
};

View File

@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Device _device;
private WindowBase _window;
private bool _initialized;
internal FormatCapabilities FormatCapabilities { get; private set; }
internal HardwareCapabilities Capabilities;
@ -266,6 +268,8 @@ namespace Ryujinx.Graphics.Vulkan
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
_window = new Window(this, _surface, _physicalDevice, _device);
_initialized = true;
}
public BufferHandle CreateBuffer(int size)
@ -573,6 +577,11 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void Dispose()
{
if (!_initialized)
{
return;
}
CommandBufferPool.Dispose();
BackgroundResources.Dispose();
_counters.Dispose();
@ -613,4 +622,4 @@ namespace Ryujinx.Graphics.Vulkan
Api.DestroyInstance(_instance, null);
}
}
}
}

View File

@ -172,9 +172,11 @@ namespace Ryujinx.HLE.FileSystem
fsServerClient = horizon.CreatePrivilegedHorizonClient();
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();
GameCard = fsServerObjects.GameCard;
@ -186,7 +188,8 @@ namespace Ryujinx.HLE.FileSystem
{
DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators
FsCreators = fsServerObjects.FsCreators,
RandomGenerator = randomGenerator
};
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.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
@ -9,6 +10,7 @@ using Ryujinx.Memory;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@ -78,13 +80,13 @@ namespace Ryujinx.HLE.HOS.Applets
var launchParams = _normalSession.Pop();
var keyboardConfig = _normalSession.Pop();
_isBackground = keyboardConfig.Length == Marshal.SizeOf<SoftwareKeyboardInitialize>();
_isBackground = keyboardConfig.Length == Unsafe.SizeOf<SoftwareKeyboardInitialize>();
if (_isBackground)
{
// Initialize the keyboard applet in background mode.
_keyboardBackgroundInitialize = ReadStruct<SoftwareKeyboardInitialize>(keyboardConfig);
_keyboardBackgroundInitialize = MemoryMarshal.Read<SoftwareKeyboardInitialize>(keyboardConfig);
_backgroundState = InlineKeyboardState.Uninitialized;
if (_device.UiHandler == null)
@ -342,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Applets
else
{
int wordsCount = reader.ReadInt32();
int wordSize = Marshal.SizeOf<SoftwareKeyboardUserWord>();
int wordSize = Unsafe.SizeOf<SoftwareKeyboardUserWord>();
remaining = stream.Length - stream.Position;
if (wordsCount > MaxUserWords)
@ -359,8 +361,7 @@ namespace Ryujinx.HLE.HOS.Applets
for (int word = 0; word < wordsCount; word++)
{
byte[] wordData = reader.ReadBytes(wordSize);
_keyboardBackgroundUserWords[word] = ReadStruct<SoftwareKeyboardUserWord>(wordData);
_keyboardBackgroundUserWords[word] = reader.ReadStruct<SoftwareKeyboardUserWord>();
}
}
}
@ -369,27 +370,25 @@ namespace Ryujinx.HLE.HOS.Applets
case InlineKeyboardRequest.SetCustomizeDic:
// Read the custom dic data.
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");
}
else
{
var keyboardDicData = reader.ReadBytes((int)remaining);
_keyboardBackgroundDic = ReadStruct<SoftwareKeyboardCustomizeDic>(keyboardDicData);
_keyboardBackgroundDic = reader.ReadStruct<SoftwareKeyboardCustomizeDic>();
}
break;
case InlineKeyboardRequest.SetCustomizedDictionaries:
// Read the custom dictionaries data.
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");
}
else
{
var keyboardDictData = reader.ReadBytes((int)remaining);
_keyboardBackgroundDictSet = ReadStruct<SoftwareKeyboardDictSet>(keyboardDictData);
_keyboardBackgroundDictSet = reader.ReadStruct<SoftwareKeyboardDictSet>();
}
break;
case InlineKeyboardRequest.Calc:

View File

@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary>
/// A structure used by SetCustomizeDic request to software keyboard.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
[StructLayout(LayoutKind.Sequential, Size = 0x70)]
struct SoftwareKeyboardCustomizeDic
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 112)]
public byte[] Unknown;
// 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
{
@ -21,8 +22,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary>
/// Array of word entries in the buffer.
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
public ulong[] Entries;
public Array24<ulong> Entries;
/// <summary>
/// Number of used entries in the Entries field.

View File

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

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