Compare commits

...

13 Commits

Author SHA1 Message Date
41b104d0fb Fix audio renderer compressor effect (#5742)
* Delete DecibelToLinearExtended and fix Log10 function

* Fix CopyBuffer and ClearBuffer

* Change effect states from class to struct + formatting

* Formatting

* Make UpdateLowPassFilter readonly

* More compressor fixes
2023-09-29 10:48:49 +00:00
bc44b85b0b nuget: bump FluentAvaloniaUI from 2.0.1 to 2.0.4 (#5729)
* nuget: bump FluentAvaloniaUI from 2.0.1 to 2.0.4

Bumps [FluentAvaloniaUI](https://github.com/amwx/FluentAvalonia) from 2.0.1 to 2.0.4.
- [Commits](https://github.com/amwx/FluentAvalonia/commits)

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

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

* Update Directory.Packages.props

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-09-28 00:15:45 +02:00
01c2b8097c Implement NGC service (#5681)
* Implement NGC service

* Use raw byte arrays instead of string for _wordSeparators

* Silence IDE0230 for _wordSeparators

* Try to silence warning about _rangeValuesCount not being read on SparseSet

* Make AcType enum private

* Add abstract methods and one TODO that I forgot

* PR feedback

* More PR feedback

* More PR feedback
2023-09-27 19:21:26 +02:00
4bd2ca3f0d nuget: bump System.IdentityModel.Tokens.Jwt from 6.31.0 to 7.0.0 (#5730)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.31.0 to 7.0.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.31.0...7.0.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 19:03:41 +02:00
e63157cc33 GPU: Don't create tracking handles for buffer textures (#5727)
* GPU: Don't create tracking handles for buffer textures

Buffer texture memory is handled by the buffer cache - the texture shouldn't create any tracking handles as they aren't used. This change simply makes them create and iterate 0 tracking handles, while keeping the rest of the texture group around.

This prevents a possible issue where many buffer textures are created as views of overlapping buffer ranges, and virtual regions have many dependant textures that don't actually contribute anything to handle state.

Should improve performance in Mortal Kombat 1, possibly certain UE4 games when FIFO raises to 100%.

* Fix interval tree bug

* Don't check view compatibility for buffer textures
2023-09-26 12:37:10 -03:00
7f2fb049f5 Ava: Fix regressions by rewriting CheckLaunchState (#5728) 2023-09-26 07:17:55 +02:00
4744bde0e5 Reduce the amount of descriptor pool allocations on Vulkan (#5673)
* Reduce the amount of descriptor pool allocations on Vulkan

* Formatting

* Slice can be simplified

* Make GetDescriptorPoolSizes static

* Adjust CanFit calculation so that TryAllocateDescriptorSets never fails

* Remove unused field
2023-09-26 02:00:02 +02:00
4a835bb2b9 Make Vulkan memory allocator actually thread safe (#5575)
* Make Vulkan memory allocator actually thread safe

* Make free thread safe too

* PR feedback
2023-09-26 01:50:06 +02:00
ddc9ae2a83 Add VTimer as alternative interrupt method on Apple Hypervisor (#5663)
* Add VTimer as alternative interrupt method on Apple Hypervisor

* Fix naming violations on TimeApi

* Fix timer interval (was 16us rather than 16ms)

* Fix delta ticks calculation

* Missing ThrowOnError call

* Add SupportedOSPlatform attribute on AppleHv classes
2023-09-26 01:18:32 +02:00
d6d3cdd573 Ava UI: Refactor async usage (#5516)
* Remove `async void`

* Async LoadApplications

* Formatting and such

* Remove async from InstallUpdate

* Update src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs

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

* Cleanup LoadApplications()

* Cleanup

* Formatting

* Revert some stuff

* Cleanup

* Update src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Ack suggestions

* Whitespace

* Fix Peri suggestion

* Add missing trailing commas

* Remove redundant method override

* Remove Dispatcher.UIThread.InvokeAsync/Post where possible

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-09-26 00:04:58 +02:00
53bd4c9f60 Add ldn:u implementation, INetworkClient interface and DisabledLdnClient (#5652)
* Impl first attempt to LDN

* Make this work.

- Endianness swap on all IPs.
- Use local network IP for connections, rather than 127.0.0.1. This is to be changed when tunnelling or whatever.
- Mac addresses are now randomly assigned on the server. (fixes joining lobbies)
- Fixed the "connected" handler for stations to actually find a
- Added info retrieval when connected to a station.
- Users that disconnect are now removed from rooms they were in. (still need to broadcast tho)
- The communication service does a bit better with being closed now.
- Some locking around the game instance dictionary.

* We may just be "initialized". Ignore this for now.

* Lots of WIP

* Add Disconnect packet

* Improve signalling of internal events.

* Fix scan.

* Fix some more stupid things.

* Enable NoDelay on all sockets.

* Add station accept policy, disconnect function.

* Limit max number of games.

* Split out networking stuff from HLE, so it can be swapped.

* Update logging calls.

* Missed a spot.

* Call SignalDisconnect instead of SetState

* Add comment to GetNetworkInfo

* Update configuration + UI

Now has its own tab, more options.

* Refactoring IUserLocalCommunicationService

( Expected new issues :'( )

* some cleanup

* More fix

* Correctly handle errors when connecting.

* Disable *Private call and clean symbols

* Structs cleanup

* Big cleanup

* Fix InvalidHandle (in MK8D and other games)

* Add Reject and Private Network support (v1)

RyuLdn Version bumped to 1.

* Add Initialize Packet

Allows users to keep Mac Addresses assigned by the server.

* Add SetWirelessControllerRestriction and some cleanup

* LDN-2 Initial Rebase

Make this work.

- Endianness swap on all IPs.
- Use local network IP for connections, rather than 127.0.0.1. This is to be changed when tunnelling or whatever.
- Fixed the "connected" handler for stations to actually find a
- The communication service does a bit better with being closed now.
- Some locking around the game instance dictionary.

We may just be "initialized". Ignore this for now.

Lots of WIP

Implement scan filter.

Improve signalling of internal events.

Fix scan.

Fix 0 width data, scan reply end delay removed.

Fix some more stupid things.

Enable NoDelay on all sockets.

Add station accept policy, disconnect function.

Limit max number of games.

Split out networking stuff from HLE, so it can be swapped.

Update logging calls.

Missed a spot.

SetAdvertiseData when open, don't return games that have accept policy 1

Update configuration + UI

Now has its own tab, more options.

Don't Keepalive, it causes problems.

Refactoring IUserLocalCommunicationService

( Expected new issues :'( )

some cleanup

More fix

Correctly handle errors when connecting.

Disable *Private call and clean symbols

Structs cleanup

Big cleanup

Fix InvalidHandle (in MK8D and other games)

Add Reject and Private Network support (v1)

Disable TcpNoDelay option on linux.

Add SetWirelessControllerRestriction and some cleanup

Misc cleanup, implement broadcast flag.

* Misc Changes

* Fix GetNetworkInfo

* Fix some small issues

* Implement GetNetworkInfoLatestUpdate

* Hotfix when LocalCommunicationId = 0xFFFFFFFFFFFFFFFF

* Fix ARMS Scan (and other games using wrong LocalCommunicationId

* Fix latest update when host leaves

* Revert "Fix ARMS Scan (and other games using wrong LocalCommunicationId"

This reverts commit 519c283d3993e2fdfafb8ac6b4e0a98231f6fb75.

* Fix the localCommunicationId = -1

* Don't set Connect flag for nodes already in the room before joining.

* Make IUserLocalCommunicationService disposable

* Don't dispose if there's no client.

* LDN-2-2 Rebase

Make this work.

- Endianness swap on all IPs.
- Use local network IP for connections, rather than 127.0.0.1. This is to be changed when tunnelling or whatever.
- Fixed the "connected" handler for stations to actually find a
- The communication service does a bit better with being closed now.
- Some locking around the game instance dictionary.

We may just be "initialized". Ignore this for now.

Put sockets behind an interface, so that they can be swapped for something proxyable

Lots of WIP

Implement scan filter.

Improve signalling of internal events.

Fix scan.

Fix 0 width data, scan reply end delay removed.

Fix some more stupid things.

Enable NoDelay on all sockets.

Add station accept policy, disconnect function.

Limit max number of games.

Split out networking stuff from HLE, so it can be swapped.

Update logging calls.

Missed a spot.

SetAdvertiseData when open, don't return games that have accept policy 1

Update configuration + UI

Now has its own tab, more options.

Don't Keepalive, it causes problems.

Refactoring IUserLocalCommunicationService

( Expected new issues :'( )

some cleanup

More fix

Correctly handle errors when connecting.

Disable *Private call and clean symbols

Structs cleanup

Big cleanup

Fix InvalidHandle (in MK8D and other games)

Add Reject and Private Network support (v1)

Disable TcpNoDelay option on linux.

Add SetWirelessControllerRestriction and some cleanup

Misc cleanup, implement broadcast flag.

Misc Changes

Fix GetNetworkInfo

Fix some small issues

Disable LAN by default til the config is added.

Fix Splatoon 2

- Stub nfp IUser::StartDetection / IUser::StopDetection.
- Stub ntc IEnsureNetworkClockAvailabilityService and needed calls.

Cleanup previous fixes

Stub IAudioInManager/IAudioIn for Splatoon 2 LAN

Add LAN settings to multiplayer tab

LAN Play > LAN Mode

Implement GetNetworkInfoLatestUpdate

Hotfix when LocalCommunicationId = 0xFFFFFFFFFFFFFFFF

Fix ARMS Scan (and other games using wrong LocalCommunicationId

Fix latest update when host leaves

Revert "Fix ARMS Scan (and other games using wrong LocalCommunicationId"

This reverts commit 519c283d3993e2fdfafb8ac6b4e0a98231f6fb75.

Fix the localCommunicationId = -1

Don't set Connect flag for nodes already in the room before joining.

Make IUserLocalCommunicationService disposable

Fix crash when using LAN mode on linux.

Actually use that call

Don't dispose if there's no client.

Fix the settings window crash

Fix configurationFileUpdated

* Make LDN compatible with Ryujinx/Ryujinx#3805

* Ava: Add Ldn options to SettingsNetworkTab

* Ava: Add update events for multiplayer options

* Apply formatting

* Remove LdnHelper

* ldn: Fix hardcoded /24 subnet mask

* Fix naming rule violations

* Add missing summary doc tag

* Remove NetCoreServer dependency

* Address code style issues and typos

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

* Call CloseStation/CloseAccessPoint to reduce code duplication

* Fix typo

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

* Fix missing trailing commas

* Extract AddressList from AddressEntry

* Use AcceptPolicy as a type for LdnNetworkInfo.StationAcceptPolicy

* Add Flags attribute to ScanFilterFlag

* Rename struct members for LdnNetworkInfo

* Remove extra line

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Extract NetworkErrorMessage from NetworkError

* Fix missing trailing commas

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: riperiperi <rhy3756547@hotmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-09-25 23:50:43 +02:00
eca8808649 Headless: Add support for Scaling Filters, Anti-aliasing and Exclusive Fullscreen (#5412)
* Headless: Added support for fullscreen option

* Headless: cleanup of fullscreen support

* Headless: fullscreen support : implemented proposed changes

* Headless: fullscreen support: cleanup

* Headless: exclusive fullscreen support: wip

* Headless: exclusive fullscreen support: add. windows scale interop

* Headless: exclusive fullscreen support: cleanup

* Headless: exclusive fullscreen support: cleanup

* Headless: fullscreen support: fix for OpenGL scaling

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: add. Vulkan fix

* Headless: fullscreen support: add. macOS fullscreen fix

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: exclusive fullscreen support: add. display selection logic

* Headless: exclusive fullscreen support: add. anti-aliasing + scaling-filter logic

* Headless: exclusive fullscreen support: upd. options to be case-insensitive

* Headless: exclusive fullscreen support: force default values for scaling + anti-aliasing options

* Headless: upd. OpenGL --fullscreen window size logic

* Headless: upd. fullscreen logic

* Headless: cleanup

* Headless: refac. DisplayId option naming

* Headless: refac. scaling + anti-aliasing option handling

* Headless: refac. namespace handling

* Headless: upd. imports ordering

* Apply suggestions from code review

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

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-09-25 23:40:16 +02:00
f6c3f1cdfd GPU: Discard data when getting texture before full clear (#5719)
* GPU: Discard data when getting texture before full clear

* Fix rules and order of clear checks

* Fix formatting
2023-09-25 23:07:03 +02:00
162 changed files with 7756 additions and 614 deletions

View File

@ -3,18 +3,18 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.3" /> <PackageVersion Include="Avalonia" Version="11.0.4" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.3" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.3" /> <PackageVersion Include="Avalonia.Desktop" Version="11.0.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.3" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.0.4" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.3" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.4" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0" /> <PackageVersion Include="Avalonia.Svg" Version="11.0.0.2" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.2" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="7.14.2" /> <PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.1" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.0.4" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" /> <PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
@ -44,7 +44,7 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" /> <PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" /> <PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.31.0" /> <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.2" /> <PackageVersion Include="System.Management" Version="7.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />

View File

@ -189,7 +189,7 @@ namespace ARMeilleure.Translation
{ {
if (start.CompareTo(node.End) < 0) if (start.CompareTo(node.End) < 0)
{ {
if (overlaps.Length >= overlapCount) if (overlaps.Length <= overlapCount)
{ {
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
} }

View File

@ -31,9 +31,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public bool IsEffectEnabled { get; } public bool IsEffectEnabled { get; }
public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, public AuxiliaryBufferCommand(
ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax, uint bufferOffset,
CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) byte inputBufferOffset,
byte outputBufferOffset,
ref AuxiliaryBufferAddresses sendBufferInfo,
bool isEnabled,
uint countMax,
CpuAddress outputBuffer,
CpuAddress inputBuffer,
uint updateCount,
uint writeOffset,
int nodeId)
{ {
Enabled = true; Enabled = true;
NodeId = nodeId; NodeId = nodeId;

View File

@ -21,7 +21,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private BiquadFilterParameter _parameter; private BiquadFilterParameter _parameter;
public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) public BiquadFilterCommand(
int baseIndex,
ref BiquadFilterParameter filter,
Memory<BiquadFilterState> biquadFilterStateMemory,
int inputBufferOffset,
int outputBufferOffset,
bool needInitialization,
int nodeId)
{ {
_parameter = filter; _parameter = filter;
BiquadFilterState = biquadFilterStateMemory; BiquadFilterState = biquadFilterStateMemory;

View File

@ -77,7 +77,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ClearBuffer(int index) public unsafe void ClearBuffer(int index)
{ {
Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount); Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount * sizeof(float));
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex) public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex)
{ {
Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount); Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount * sizeof(float));
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -94,18 +94,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain); float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f; float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 0.0f; float z = 1.0f;
bool unknown10OutOfRange = false; bool unknown10OutOfRange = y >= state.Unknown10;
if (newMean < 1.0e-10f) if (newMean < 1.0e-10f)
{ {
z = 1.0f; y = -100.0f;
unknown10OutOfRange = state.Unknown10 < -100.0f; unknown10OutOfRange = state.Unknown10 <= -100.0f;
} }
if (y >= state.Unknown10 || unknown10OutOfRange) if (unknown10OutOfRange)
{ {
float tmpGain; float tmpGain;
@ -118,7 +118,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction); tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
} }
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain); z = FloatingPointHelper.DecibelToLinear(tmpGain);
} }
float unknown4New = z; float unknown4New = z;

View File

@ -88,7 +88,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix2x2 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, Matrix2x2 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackBaseGain); delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
@ -125,9 +125,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix4x4 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, Matrix4x4 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain); 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
@ -172,11 +172,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f, Matrix6x6 delayFeedback = new(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain); 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {

View File

@ -28,7 +28,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private LimiterParameter _parameter; private LimiterParameter _parameter;
public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> resultState, bool isEnabled, ulong workBuffer, int nodeId) public LimiterCommandVersion2(
uint bufferOffset,
LimiterParameter parameter,
Memory<LimiterState> state,
Memory<EffectResultState> resultState,
bool isEnabled,
ulong workBuffer,
int nodeId)
{ {
Enabled = true; Enabled = true;
NodeId = nodeId; NodeId = nodeId;

View File

@ -79,53 +79,57 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount) private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{ {
ProcessReverbGeneric(ref state, ProcessReverbGeneric(
outputBuffers, ref state,
inputBuffers, outputBuffers,
sampleCount, inputBuffers,
_outputEarlyIndicesTableMono, sampleCount,
_targetEarlyDelayLineIndicesTableMono, _outputEarlyIndicesTableMono,
_targetOutputFeedbackIndicesTableMono, _targetEarlyDelayLineIndicesTableMono,
_outputIndicesTableMono); _targetOutputFeedbackIndicesTableMono,
_outputIndicesTableMono);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount) private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{ {
ProcessReverbGeneric(ref state, ProcessReverbGeneric(
outputBuffers, ref state,
inputBuffers, outputBuffers,
sampleCount, inputBuffers,
_outputEarlyIndicesTableStereo, sampleCount,
_targetEarlyDelayLineIndicesTableStereo, _outputEarlyIndicesTableStereo,
_targetOutputFeedbackIndicesTableStereo, _targetEarlyDelayLineIndicesTableStereo,
_outputIndicesTableStereo); _targetOutputFeedbackIndicesTableStereo,
_outputIndicesTableStereo);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount) private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{ {
ProcessReverbGeneric(ref state, ProcessReverbGeneric(
outputBuffers, ref state,
inputBuffers, outputBuffers,
sampleCount, inputBuffers,
_outputEarlyIndicesTableQuadraphonic, sampleCount,
_targetEarlyDelayLineIndicesTableQuadraphonic, _outputEarlyIndicesTableQuadraphonic,
_targetOutputFeedbackIndicesTableQuadraphonic, _targetEarlyDelayLineIndicesTableQuadraphonic,
_outputIndicesTableQuadraphonic); _targetOutputFeedbackIndicesTableQuadraphonic,
_outputIndicesTableQuadraphonic);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount) private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{ {
ProcessReverbGeneric(ref state, ProcessReverbGeneric(
outputBuffers, ref state,
inputBuffers, outputBuffers,
sampleCount, inputBuffers,
_outputEarlyIndicesTableSurround, sampleCount,
_targetEarlyDelayLineIndicesTableSurround, _outputEarlyIndicesTableSurround,
_targetOutputFeedbackIndicesTableSurround, _targetEarlyDelayLineIndicesTableSurround,
_outputIndicesTableSurround); _targetOutputFeedbackIndicesTableSurround,
_outputIndicesTableSurround);
} }
private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable) private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)

View File

@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
{ {
// NOTE: Nintendo uses an approximation of log10, we don't. // NOTE: Nintendo uses an approximation of log10, we don't.
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours. // As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
return MathF.Pow(10, MathF.Max(x, 1.0e-10f)); return MathF.Log10(MathF.Max(x, 1.0e-10f));
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -62,7 +62,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
foreach (float input in inputs) foreach (float input in inputs)
{ {
res += (input * input); float normInput = input * (1f / 32768f);
res += normInput * normInput;
} }
res /= inputs.Length; res /= inputs.Length;
@ -81,19 +82,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10.0f, db / 20.0f); return MathF.Pow(10.0f, db / 20.0f);
} }
/// <summary>
/// Map decibel to linear in [0, 2] range.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value in [0, 2] range</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinearExtended(float db)
{
float tmp = MathF.Log2(DecibelToLinear(db));
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees) public static float DegreesToRadians(float degrees)
{ {

View File

@ -3,7 +3,7 @@ using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.State namespace Ryujinx.Audio.Renderer.Dsp.State
{ {
public class CompressorState public struct CompressorState
{ {
public ExponentialMovingAverage InputMovingAverage; public ExponentialMovingAverage InputMovingAverage;
public float Unknown4; public float Unknown4;
@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax; CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
Unknown10 = threshold - 1.5f; Unknown10 = threshold - 1.5f;
Unknown14 = threshold + 1.5f; Unknown14 = threshold + 1.5f;
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain); OutputGain = FloatingPointHelper.DecibelToLinear(parameter.OutputGain + makeupGain);
} }
} }
} }

View File

@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.State namespace Ryujinx.Audio.Renderer.Dsp.State
{ {
public class DelayState public struct DelayState
{ {
public DelayLine[] DelayLines { get; } public DelayLine[] DelayLines { get; }
public float[] LowPassZ { get; set; } public float[] LowPassZ { get; set; }
@ -53,7 +53,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
LowPassBaseGain = 1.0f - LowPassFeedbackGain; LowPassBaseGain = 1.0f - LowPassFeedbackGain;
} }
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount) public readonly void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
{ {
for (int i = 0; i < channelCount; i++) for (int i = 0; i < channelCount; i++)
{ {

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State namespace Ryujinx.Audio.Renderer.Dsp.State
{ {
public class LimiterState public struct LimiterState
{ {
public ExponentialMovingAverage[] DetectorAverage; public ExponentialMovingAverage[] DetectorAverage;
public ExponentialMovingAverage[] CompressionGainAverage; public ExponentialMovingAverage[] CompressionGainAverage;

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State namespace Ryujinx.Audio.Renderer.Dsp.State
{ {
public class Reverb3dState public struct Reverb3dState
{ {
private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f }; private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f };
private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f }; private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f };

View File

@ -5,7 +5,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State namespace Ryujinx.Audio.Renderer.Dsp.State
{ {
public class ReverbState public struct ReverbState
{ {
private static readonly float[] _fdnDelayTimes = new float[20] private static readonly float[] _fdnDelayTimes = new float[20]
{ {

View File

@ -104,7 +104,7 @@ namespace Ryujinx.Ava
{ {
"Light" => ThemeVariant.Light, "Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark, "Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default _ => ThemeVariant.Default,
}; };
if (enableCustomTheme) if (enableCustomTheme)

View File

@ -3,7 +3,6 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.Dummy;
@ -21,6 +20,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop; using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
@ -191,6 +191,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false); _gpuDoneEvent = new ManualResetEvent(false);
@ -412,6 +413,11 @@ namespace Ryujinx.Ava
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
} }
private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs<MultiplayerMode> e)
{
Device.Configuration.MultiplayerMode = e.NewValue;
}
public void Stop() public void Stop()
{ {
_isActive = false; _isActive = false;
@ -782,7 +788,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AspectRatio, ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor, ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
ConfigurationState.Instance.Multiplayer.Mode);
Device = new Switch(configuration); Device = new Switch(configuration);
} }

View File

@ -652,5 +652,8 @@
"NetworkInterfaceDefault": "Default", "NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders", "PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "View Changelog on GitHub", "AboutChangelogButton": "View Changelog on GitHub",
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.",
"SettingsTabNetworkMultiplayer": "Multiplayer",
"MultiplayerMode": "Mode:",
"MultiplayerModeTooltip": "Change multiplayer mode"
} }

View File

@ -15,7 +15,6 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
@ -36,11 +35,9 @@ namespace Ryujinx.Ava.Common
private static HorizonClient _horizonClient; private static HorizonClient _horizonClient;
private static AccountManager _accountManager; private static AccountManager _accountManager;
private static VirtualFileSystem _virtualFileSystem; private static VirtualFileSystem _virtualFileSystem;
private static StyleableWindow _owner;
public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, StyleableWindow owner) public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient)
{ {
_owner = owner;
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_horizonClient = horizonClient; _horizonClient = horizonClient;
_accountManager = accountManager; _accountManager = accountManager;
@ -148,7 +145,7 @@ namespace Ryujinx.Ava.Common
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{ {
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false AllowMultiple = false,
}); });
if (result.Count == 0) if (result.Count == 0)

View File

@ -1,6 +1,5 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;

View File

@ -82,12 +82,9 @@ namespace Ryujinx.Modules
{ {
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateWarningDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
await ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
_running = false; _running = false;
@ -114,10 +111,9 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateUpdaterInfoDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); "");
});
} }
_running = false; _running = false;
@ -134,10 +130,9 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateUpdaterInfoDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); "");
});
} }
_running = false; _running = false;
@ -149,10 +144,8 @@ namespace Ryujinx.Modules
{ {
Logger.Error?.Print(LogClass.Application, exception.Message); Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateErrorDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
});
_running = false; _running = false;
@ -167,12 +160,9 @@ namespace Ryujinx.Modules
{ {
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateWarningDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
await ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
_running = false; _running = false;
@ -183,10 +173,9 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateUpdaterInfoDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); "");
});
} }
_running = false; _running = false;
@ -212,7 +201,7 @@ namespace Ryujinx.Modules
_buildSize = -1; _buildSize = -1;
} }
Dispatcher.UIThread.Post(async () => await Dispatcher.UIThread.InvokeAsync(async () =>
{ {
// Show a message asking the user if they want to update // Show a message asking the user if they want to update
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
@ -222,7 +211,7 @@ namespace Ryujinx.Modules
if (shouldUpdate) if (shouldUpdate)
{ {
UpdateRyujinx(mainWindow, _buildUrl); await UpdateRyujinx(mainWindow, _buildUrl);
} }
else else
{ {
@ -241,7 +230,7 @@ namespace Ryujinx.Modules
return result; return result;
} }
private static async void UpdateRyujinx(Window parent, string downloadUrl) private static async Task UpdateRyujinx(Window parent, string downloadUrl)
{ {
_updateSuccessful = false; _updateSuccessful = false;
@ -579,27 +568,24 @@ namespace Ryujinx.Modules
} }
} }
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile) private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
{ {
// Extract Update // Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
await Task.Run(() => if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{ {
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
{ }
ExtractTarGzipFile(taskDialog, updateFile, _updateDir); else if (OperatingSystem.IsWindows())
} {
else if (OperatingSystem.IsWindows()) ExtractZipFile(taskDialog, updateFile, _updateDir);
{ }
ExtractZipFile(taskDialog, updateFile, _updateDir); else
} {
else throw new NotSupportedException();
{ }
throw new NotSupportedException();
}
});
// Delete downloaded zip // Delete downloaded zip
File.Delete(updateFile); File.Delete(updateFile);
@ -613,36 +599,33 @@ namespace Ryujinx.Modules
if (!OperatingSystem.IsMacOS()) if (!OperatingSystem.IsMacOS())
{ {
// Replace old files // Replace old files
await Task.Run(() => double count = 0;
foreach (string file in allFiles)
{ {
double count = 0; count++;
foreach (string file in allFiles) try
{ {
count++; File.Move(file, file + ".ryuold");
try
{
File.Move(file, file + ".ryuold");
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
catch
{ {
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
} });
} }
catch
Dispatcher.UIThread.Post(() =>
{ {
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); }
}); }
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
}); });
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
Directory.Delete(_updateDir, true); Directory.Delete(_updateDir, true);
} }
@ -658,12 +641,11 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() =>
{ ContentDialogHelper.CreateWarningDialog(
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]); LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage])
}); );
} }
return false; return false;
@ -673,12 +655,11 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() =>
{ ContentDialogHelper.CreateWarningDialog(
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]); LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
}); );
} }
return false; return false;
@ -688,12 +669,11 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() =>
{ ContentDialogHelper.CreateWarningDialog(
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]); LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
}); );
} }
return false; return false;
@ -705,21 +685,19 @@ namespace Ryujinx.Modules
{ {
if (ReleaseInformation.IsFlatHubBuild()) if (ReleaseInformation.IsFlatHubBuild())
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() =>
{ ContentDialogHelper.CreateWarningDialog(
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]); LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
}); );
} }
else else
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() =>
{ ContentDialogHelper.CreateWarningDialog(
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]); LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
}); );
} }
} }

View File

@ -106,7 +106,7 @@ namespace Ryujinx.Ava.UI.Applet
bool error = false; bool error = false;
string inputText = args.InitialText ?? ""; string inputText = args.InitialText ?? "";
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
try try
{ {
@ -149,7 +149,7 @@ namespace Ryujinx.Ava.UI.Applet
bool showDetails = false; bool showDetails = false;
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
try try
{ {

View File

@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Controls
{ {
if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel viewModel })
{ {
OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
} }
} }
@ -62,14 +62,14 @@ namespace Ryujinx.Ava.UI.Controls
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Device, userId: default); OpenSaveDirectory(viewModel, SaveDataType.Device, default);
} }
public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Bcat, userId: default); OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
} }
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
@ -158,11 +158,12 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null) if (viewModel?.SelectedApplication != null)
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName), LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -205,11 +206,12 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null) if (viewModel?.SelectedApplication != null)
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName), LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -335,13 +337,13 @@ namespace Ryujinx.Ava.UI.Controls
} }
} }
public void RunApplication_Click(object sender, RoutedEventArgs args) public async void RunApplication_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null) if (viewModel?.SelectedApplication != null)
{ {
viewModel.LoadApplication(viewModel.SelectedApplication.Path); await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
} }
} }
} }

View File

@ -212,9 +212,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
Patterns = new[] { "*.nsp" }, Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" } MimeTypes = new[] { "application/x-nx-nsp" },
} },
} },
}); });
foreach (var file in result) foreach (var file in result)

View File

@ -26,6 +26,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Ui; using Ryujinx.HLE.Ui;
using Ryujinx.Input.HLE;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common; using Ryujinx.Ui.Common;
@ -39,7 +40,6 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image; using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException; using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
@ -1068,9 +1068,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
Logger.Error?.Print(LogClass.Application, ex.ToString()); Logger.Error?.Print(LogClass.Application, ex.ToString());
static async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
Dispatcher.UIThread.Post(Action);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -1163,16 +1161,13 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost?.DisposeContext(); AppHost?.DisposeContext();
} }
private void HandleRelaunch() private async Task HandleRelaunch()
{ {
if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart) if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart)
{ {
UserChannelPersistence.ShouldRestart = false; UserChannelPersistence.ShouldRestart = false;
Dispatcher.UIThread.Post(() => await LoadApplication(_currentEmulatedGamePath);
{
LoadApplication(_currentEmulatedGamePath);
});
} }
else else
{ {
@ -1191,7 +1186,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Application.Current.Styles.TryGetResource(args.VSyncEnabled Application.Current.Styles.TryGetResource(args.VSyncEnabled
? "VsyncEnabled" ? "VsyncEnabled"
: "VsyncDisabled", : "VsyncDisabled",
Avalonia.Application.Current.ActualThemeVariant, Application.Current.ActualThemeVariant,
out object color); out object color);
if (color is not null) if (color is not null)
@ -1283,7 +1278,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Glyph = Glyph.Grid; Glyph = Glyph.Grid;
} }
public async void InstallFirmwareFromFile() public async Task InstallFirmwareFromFile()
{ {
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
@ -1294,21 +1289,21 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
Patterns = new[] { "*.xci", "*.zip" }, Patterns = new[] { "*.xci", "*.zip" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" },
MimeTypes = new[] { "application/x-nx-xci", "application/zip" } MimeTypes = new[] { "application/x-nx-xci", "application/zip" },
}, },
new("XCI") new("XCI")
{ {
Patterns = new[] { "*.xci" }, Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" } MimeTypes = new[] { "application/x-nx-xci" },
}, },
new("ZIP") new("ZIP")
{ {
Patterns = new[] { "*.zip" }, Patterns = new[] { "*.zip" },
AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, AppleUniformTypeIdentifiers = new[] { "public.zip-archive" },
MimeTypes = new[] { "application/zip" } MimeTypes = new[] { "application/zip" },
}, },
} },
}); });
if (result.Count > 0) if (result.Count > 0)
@ -1317,11 +1312,11 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public async void InstallFirmwareFromFolder() public async Task InstallFirmwareFromFolder()
{ {
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{ {
AllowMultiple = false AllowMultiple = false,
}); });
if (result.Count > 0) if (result.Count > 0)
@ -1352,7 +1347,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public async void ExitCurrentState() public async Task ExitCurrentState()
{ {
if (WindowState == WindowState.FullScreen) if (WindowState == WindowState.FullScreen)
{ {
@ -1377,7 +1372,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public async void ManageProfiles() public async Task ManageProfiles()
{ {
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
} }
@ -1387,7 +1382,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost.Device.System.SimulateWakeUpMessage(); AppHost.Device.System.SimulateWakeUpMessage();
} }
public async void OpenFile() public async Task OpenFile()
{ {
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
@ -1404,7 +1399,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"com.ryujinx.xci", "com.ryujinx.xci",
"com.ryujinx.nca", "com.ryujinx.nca",
"com.ryujinx.nro", "com.ryujinx.nro",
"com.ryujinx.nso" "com.ryujinx.nso",
}, },
MimeTypes = new[] MimeTypes = new[]
{ {
@ -1412,63 +1407,63 @@ namespace Ryujinx.Ava.UI.ViewModels
"application/x-nx-xci", "application/x-nx-xci",
"application/x-nx-nca", "application/x-nx-nca",
"application/x-nx-nro", "application/x-nx-nro",
"application/x-nx-nso" "application/x-nx-nso",
} },
}, },
new("NSP") new("NSP")
{ {
Patterns = new[] { "*.nsp" }, Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" } MimeTypes = new[] { "application/x-nx-nsp" },
}, },
new("XCI") new("XCI")
{ {
Patterns = new[] { "*.xci" }, Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" } MimeTypes = new[] { "application/x-nx-xci" },
}, },
new("NCA") new("NCA")
{ {
Patterns = new[] { "*.nca" }, Patterns = new[] { "*.nca" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" },
MimeTypes = new[] { "application/x-nx-nca" } MimeTypes = new[] { "application/x-nx-nca" },
}, },
new("NRO") new("NRO")
{ {
Patterns = new[] { "*.nro" }, Patterns = new[] { "*.nro" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" },
MimeTypes = new[] { "application/x-nx-nro" } MimeTypes = new[] { "application/x-nx-nro" },
}, },
new("NSO") new("NSO")
{ {
Patterns = new[] { "*.nso" }, Patterns = new[] { "*.nso" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" },
MimeTypes = new[] { "application/x-nx-nso" } MimeTypes = new[] { "application/x-nx-nso" },
}, },
} },
}); });
if (result.Count > 0) if (result.Count > 0)
{ {
LoadApplication(result[0].Path.LocalPath); await LoadApplication(result[0].Path.LocalPath);
} }
} }
public async void OpenFolder() public async Task OpenFolder()
{ {
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{ {
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle], Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
AllowMultiple = false AllowMultiple = false,
}); });
if (result.Count > 0) if (result.Count > 0)
{ {
LoadApplication(result[0].Path.LocalPath); await LoadApplication(result[0].Path.LocalPath);
} }
} }
public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "") public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
{ {
if (AppHost != null) if (AppHost != null)
{ {
@ -1505,35 +1500,30 @@ namespace Ryujinx.Ava.UI.ViewModels
this, this,
TopLevel); TopLevel);
async void Action() if (!await AppHost.LoadGuestApplication())
{ {
if (!await AppHost.LoadGuestApplication()) AppHost.DisposeContext();
{ AppHost = null;
AppHost.DisposeContext();
AppHost = null;
return; return;
}
CanUpdate = false;
LoadHeading = TitleName = titleName;
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
} }
Dispatcher.UIThread.Post(Action); CanUpdate = false;
LoadHeading = TitleName = titleName;
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
} }
public void SwitchToRenderer(bool startFullscreen) public void SwitchToRenderer(bool startFullscreen)
@ -1596,7 +1586,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsGameRunning = false; IsGameRunning = false;
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
ShowMenuAndStatusBar = true; ShowMenuAndStatusBar = true;
ShowContent = true; ShowContent = true;
@ -1609,7 +1599,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost = null; AppHost = null;
HandleRelaunch(); await HandleRelaunch();
}); });
RendererHostControl.WindowCreated -= RendererHost_Created; RendererHostControl.WindowCreated -= RendererHost_Created;

View File

@ -10,6 +10,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Vulkan; using Ryujinx.Graphics.Vulkan;
@ -54,6 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow; public event Action CloseWindow;
public event Action SaveSettingsEvent; public event Action SaveSettingsEvent;
private int _networkInterfaceIndex; private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
public int ResolutionScale public int ResolutionScale
{ {
@ -76,14 +78,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() =>
{ ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
"", "",
"", "",
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]); LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle])
}); );
} }
OnPropertyChanged(); OnPropertyChanged();
@ -251,6 +252,11 @@ namespace Ryujinx.Ava.UI.ViewModels
get => new(_networkInterfaces.Keys); get => new(_networkInterfaces.Keys);
} }
public AvaloniaList<string> MultiplayerModes
{
get => new(Enum.GetNames<MultiplayerMode>());
}
public KeyboardHotkeys KeyboardHotkeys public KeyboardHotkeys KeyboardHotkeys
{ {
get => _keyboardHotkeys; get => _keyboardHotkeys;
@ -272,6 +278,16 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public int MultiplayerModeIndex
{
get => _multiplayerModeIndex;
set
{
_multiplayerModeIndex = value;
ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex;
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
@ -478,6 +494,8 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog; EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
} }
public void SaveSettings() public void SaveSettings()
@ -579,6 +597,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]]; config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath); config.ToFileFormat().SaveConfig(Program.ConfigurationPath);

View File

@ -22,6 +22,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Path = System.IO.Path; using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers; using SpanHelpers = LibHac.Common.SpanHelpers;
@ -184,18 +185,12 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
else else
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
});
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)));
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
});
} }
} }
} }
@ -207,7 +202,7 @@ namespace Ryujinx.Ava.UI.ViewModels
SortUpdates(); SortUpdates();
} }
public async void Add() public async Task Add()
{ {
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
@ -218,9 +213,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
Patterns = new[] { "*.nsp" }, Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" } MimeTypes = new[] { "application/x-nx-nsp" },
} },
} },
}); });
foreach (var file in result) foreach (var file in result)

View File

@ -17,7 +17,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Main namespace Ryujinx.Ava.UI.Views.Main
{ {
@ -107,20 +106,14 @@ namespace Ryujinx.Ava.UI.Views.Main
await Window.ViewModel.AppHost?.ShowExitPrompt(); await Window.ViewModel.AppHost?.ShowExitPrompt();
} }
private async void PauseEmulation_Click(object sender, RoutedEventArgs e) private void PauseEmulation_Click(object sender, RoutedEventArgs e)
{ {
await Task.Run(() => Window.ViewModel.AppHost?.Pause();
{
Window.ViewModel.AppHost?.Pause();
});
} }
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e) private void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{ {
await Task.Run(() => Window.ViewModel.AppHost?.Resume();
{
Window.ViewModel.AppHost?.Resume();
});
} }
public async void OpenSettings(object sender, RoutedEventArgs e) public async void OpenSettings(object sender, RoutedEventArgs e)
@ -132,13 +125,13 @@ namespace Ryujinx.Ava.UI.Views.Main
ViewModel.LoadConfigurableHotKeys(); ViewModel.LoadConfigurableHotKeys();
} }
public void OpenMiiApplet(object sender, RoutedEventArgs e) public async void OpenMiiApplet(object sender, RoutedEventArgs e)
{ {
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath)) if (!string.IsNullOrEmpty(contentPath))
{ {
ViewModel.LoadApplication(contentPath, false, "Mii Applet"); await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
} }
} }
@ -196,8 +189,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
if (FileAssociationHelper.Install()) if (FileAssociationHelper.Install())
{ {
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
} }
else else
{ {
@ -209,8 +201,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
if (FileAssociationHelper.Uninstall()) if (FileAssociationHelper.Uninstall())
{ {
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
} }
else else
{ {

View File

@ -23,21 +23,34 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Orientation="Vertical" Orientation="Vertical"
Spacing="10"> Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkMultiplayer}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale MultiplayerMode}"
ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}"
Width="200" />
<ComboBox SelectedIndex="{Binding MultiplayerModeIndex}"
ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}"
HorizontalContentAlignment="Left"
ItemsSource="{Binding MultiplayerModes}"
Width="250" />
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" /> <TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" />
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}"> <CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}" <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" /> ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox> </CheckBox>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabNetworkInterface}" Text="{locale:Locale SettingsTabNetworkInterface}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
Width="200" /> Width="200" />
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}" <ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
ItemsSource="{Binding NetworkInterfaceList}" ItemsSource="{Binding NetworkInterfaceList}"
Width="250" /> Width="250" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
{ {
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{ {
AllowMultiple = false AllowMultiple = false,
}); });
if (result.Count > 0) if (result.Count > 0)
@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.Settings
{ {
Patterns = new[] { "*.xaml" }, Patterns = new[] { "*.xaml" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" },
MimeTypes = new[] { "application/xaml+xml" } MimeTypes = new[] { "application/xaml+xml" },
} },
} },
}); });
if (result.Count > 0) if (result.Count > 0)

View File

@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" }, Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" },
AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" }, AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" },
MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" } MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" },
} },
} },
}); });
if (result.Count > 0) if (result.Count > 0)

View File

@ -15,6 +15,7 @@ using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2; using Ryujinx.Input.SDL2;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
@ -24,8 +25,8 @@ using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
@ -79,35 +80,11 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
Initialize();
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ViewModel.RefreshFirmwareStatus();
LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged); this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
this.ScalingChanged += OnScalingChanged; this.ScalingChanged += OnScalingChanged;
} }
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
} }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@ -122,36 +99,17 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.IsActive = obj; ViewModel.IsActive = obj;
} }
public void LoadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
LoadApplications();
_isLoading = false;
}
private void OnScalingChanged(object sender, EventArgs e) private void OnScalingChanged(object sender, EventArgs e)
{ {
Program.DesktopScaleFactor = this.RenderScaling; Program.DesktopScaleFactor = this.RenderScaling;
} }
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Add(applicationData);
});
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e) private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{ {
AddApplication(e.AppData); Dispatcher.UIThread.Post(() =>
{
ViewModel.Applications.Add(e.AppData);
});
} }
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
@ -183,7 +141,7 @@ namespace Ryujinx.Ava.UI.Windows
string path = new FileInfo(args.Application.Path).FullName; string path = new FileInfo(args.Application.Path).FullName;
ViewModel.LoadApplication(path); ViewModel.LoadApplication(path).Wait();
} }
args.Handled = true; args.Handled = true;
@ -202,13 +160,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.ShowContent = true; ViewModel.ShowContent = true;
ViewModel.IsLoadingIndeterminate = false; ViewModel.IsLoadingIndeterminate = false;
Dispatcher.UIThread.InvokeAsync(() => if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{ {
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) ViewModel.ToggleFullscreen();
{ }
ViewModel.ToggleFullscreen();
}
});
} }
public void ShowLoading(bool startFullscreen = false) public void ShowLoading(bool startFullscreen = false)
@ -217,13 +172,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.ShowLoadProgress = true; ViewModel.ShowLoadProgress = true;
ViewModel.IsLoadingIndeterminate = true; ViewModel.IsLoadingIndeterminate = true;
Dispatcher.UIThread.InvokeAsync(() => if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{ {
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) ViewModel.ToggleFullscreen();
{ }
ViewModel.ToggleFullscreen();
}
});
} }
private void Initialize() private void Initialize()
@ -251,11 +203,11 @@ namespace Ryujinx.Ava.UI.Windows
VirtualFileSystem.ReloadKeySet(); VirtualFileSystem.ReloadKeySet();
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this); ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient);
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountWarning() private static async Task ShowVmMaxMapCountWarning()
{ {
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount); LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
@ -267,7 +219,7 @@ namespace Ryujinx.Ava.UI.Windows
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountDialog() private static async Task ShowVmMaxMapCountDialog()
{ {
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
LinuxHelper.RecommendedVmMaxMapCount); LinuxHelper.RecommendedVmMaxMapCount);
@ -313,33 +265,46 @@ namespace Ryujinx.Ava.UI.Windows
private void CheckLaunchState() private void CheckLaunchState()
{ {
if (ShowKeyErrorOnLoad)
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{ {
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})"); Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null) if (LinuxHelper.PkExecPath is not null)
{ {
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog); Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountDialog();
}
});
} }
else else
{ {
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning); Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountWarning();
}
});
} }
} }
if (_deferLoad) if (!ShowKeyErrorOnLoad)
{ {
_deferLoad = false; if (_deferLoad)
{
_deferLoad = false;
ViewModel.LoadApplication(_launchPath, _startFullscreen); ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait();
}
}
else
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
} }
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
@ -372,7 +337,7 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor; ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor; ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal; ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(savedPoint)) if (CheckScreenBounds(savedPoint))
{ {
@ -415,6 +380,30 @@ namespace Ryujinx.Ava.UI.Windows
{ {
base.OnOpened(e); base.OnOpened(e);
Initialize();
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.RefreshFirmwareStatus();
LoadApplications();
CheckLaunchState(); CheckLaunchState();
} }
@ -514,18 +503,15 @@ namespace Ryujinx.Ava.UI.Windows
}); });
} }
public async void LoadApplications() public void LoadApplications()
{ {
await Dispatcher.UIThread.InvokeAsync(() => ViewModel.Applications.Clear();
{
ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true; StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0; ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0; ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
ReloadGameList(); ReloadGameList();
} }
@ -558,9 +544,17 @@ namespace Ryujinx.Ava.UI.Windows
_isLoading = true; _isLoading = true;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language); Thread applicationLibraryThread = new(() =>
{
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language);
_isLoading = false; _isLoading = false;
})
{
Name = "GUI.ApplicationLibraryThread",
IsBackground = true,
};
applicationLibraryThread.Start();
} }
} }
} }

View File

@ -3,7 +3,6 @@ using Avalonia.Controls.Primitives;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using System;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -25,11 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
IconImage = new Bitmap(stream); IconImage = new Bitmap(stream);
} }
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);

View File

@ -192,7 +192,7 @@ namespace Ryujinx.Common.Collections
{ {
if (start.CompareTo(overlap.End) < 0) if (start.CompareTo(overlap.End) < 0)
{ {
if (overlaps.Length >= overlapCount) if (overlaps.Length <= overlapCount)
{ {
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
} }

View File

@ -0,0 +1,7 @@
namespace Ryujinx.Common.Configuration.Multiplayer
{
public enum MultiplayerMode
{
Disabled,
}
}

View File

@ -791,5 +791,34 @@ namespace Ryujinx.Common.Memory
[Pure] [Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
} }
public struct Array140<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array64<T> _other2;
Array11<T> _other3;
public readonly int Length => 140;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array384<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array64<T> _other2;
Array64<T> _other3;
Array64<T> _other4;
Array64<T> _other5;
Array63<T> _other6;
public readonly int Length => 384;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
} }
#pragma warning restore CS0169, IDE0051 #pragma warning restore CS0169, IDE0051

View File

@ -1,4 +1,6 @@
using System.Net.NetworkInformation; using System.Buffers.Binary;
using System.Net;
using System.Net.NetworkInformation;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
{ {
@ -62,5 +64,15 @@ namespace Ryujinx.Common.Utilities
return (targetProperties, targetAddressInfo); return (targetProperties, targetAddressInfo);
} }
public static uint ConvertIpv4Address(IPAddress ipAddress)
{
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
}
public static uint ConvertIpv4Address(string ipAddress)
{
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
}
} }
} }

View File

@ -1,9 +1,11 @@
using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvAddressSpace : IDisposable class HvAddressSpace : IDisposable
{ {
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39)); private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));

View File

@ -2,10 +2,12 @@ using Ryujinx.Cpu.AppleHv.Arm;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvAddressSpaceRange : IDisposable class HvAddressSpaceRange : IDisposable
{ {
private const ulong AllocationGranule = 1UL << 14; private const ulong AllocationGranule = 1UL << 14;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
@ -12,10 +13,18 @@ namespace Ryujinx.Cpu.AppleHv
#pragma warning restore CS0649 #pragma warning restore CS0649
} }
enum HvExitReason : uint
{
Canceled,
Exception,
VTimerActivated,
Unknown,
}
struct HvVcpuExit struct HvVcpuExit
{ {
#pragma warning disable CS0649 // Field is never assigned to #pragma warning disable CS0649 // Field is never assigned to
public uint Reason; public HvExitReason Reason;
public HvVcpuExitException Exception; public HvVcpuExitException Exception;
#pragma warning restore CS0649 #pragma warning restore CS0649
} }
@ -255,6 +264,7 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
[SupportedOSPlatform("macos")]
static partial class HvApi static partial class HvApi
{ {
public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor"; public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor";

View File

@ -1,7 +1,9 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvCpuContext : ICpuContext class HvCpuContext : ICpuContext
{ {
private readonly ITickSource _tickSource; private readonly ITickSource _tickSource;

View File

@ -1,7 +1,9 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
public class HvEngine : ICpuEngine public class HvEngine : ICpuEngine
{ {
private readonly ITickSource _tickSource; private readonly ITickSource _tickSource;

View File

@ -2,9 +2,12 @@ using ARMeilleure.State;
using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvExecutionContext : IExecutionContext class HvExecutionContext : IExecutionContext
{ {
/// <inheritdoc/> /// <inheritdoc/>
@ -67,6 +70,8 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ExceptionCallbacks _exceptionCallbacks; private readonly ExceptionCallbacks _exceptionCallbacks;
private int _interruptRequested;
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks) public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
{ {
_counter = counter; _counter = counter;
@ -111,7 +116,15 @@ namespace Ryujinx.Cpu.AppleHv
/// <inheritdoc/> /// <inheritdoc/>
public void RequestInterrupt() public void RequestInterrupt()
{ {
_impl.RequestInterrupt(); if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
{
impl.RequestInterrupt();
}
}
private bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -131,9 +144,9 @@ namespace Ryujinx.Cpu.AppleHv
{ {
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
uint reason = vcpu.ExitInfo->Reason; HvExitReason reason = vcpu.ExitInfo->Reason;
if (reason == 1) if (reason == HvExitReason.Exception)
{ {
uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome; uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome;
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26); ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
@ -146,14 +159,22 @@ namespace Ryujinx.Cpu.AppleHv
address = SynchronousException(memoryManager, ref vcpu); address = SynchronousException(memoryManager, ref vcpu);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
} }
else if (reason == 0) else if (reason == HvExitReason.Canceled || reason == HvExitReason.VTimerActivated)
{ {
if (_impl.GetAndClearInterruptRequested()) if (GetAndClearInterruptRequested())
{ {
ReturnToPool(vcpu); ReturnToPool(vcpu);
InterruptHandler(); InterruptHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
} }
if (reason == HvExitReason.VTimerActivated)
{
vcpu.EnableAndUpdateVTimer();
// Unmask VTimer interrupts.
HvApi.hv_vcpu_set_vtimer_mask(vcpu.Handle, false).ThrowOnError();
}
} }
else else
{ {

View File

@ -46,14 +46,5 @@ namespace Ryujinx.Cpu.AppleHv
{ {
_v[index] = value; _v[index] = value;
} }
public void RequestInterrupt()
{
}
public bool GetAndClearInterruptRequested()
{
return false;
}
} }
} }

View File

@ -2,10 +2,11 @@ using ARMeilleure.State;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvExecutionContextVcpu : IHvExecutionContext class HvExecutionContextVcpu : IHvExecutionContext
{ {
private static readonly MemoryBlock _setSimdFpRegFuncMem; private static readonly MemoryBlock _setSimdFpRegFuncMem;
@ -135,7 +136,6 @@ namespace Ryujinx.Cpu.AppleHv
} }
private readonly ulong _vcpu; private readonly ulong _vcpu;
private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu) public HvExecutionContextVcpu(ulong vcpu)
{ {
@ -181,16 +181,8 @@ namespace Ryujinx.Cpu.AppleHv
public void RequestInterrupt() public void RequestInterrupt()
{ {
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0) ulong vcpu = _vcpu;
{ HvApi.hv_vcpus_exit(ref vcpu, 1);
ulong vcpu = _vcpu;
HvApi.hv_vcpus_exit(ref vcpu, 1);
}
}
public bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
} }
} }
} }

View File

@ -1,8 +1,10 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
readonly struct HvMemoryBlockAllocation : IDisposable readonly struct HvMemoryBlockAllocation : IDisposable
{ {
private readonly HvMemoryBlockAllocator _owner; private readonly HvMemoryBlockAllocator _owner;

View File

@ -1,7 +1,9 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block> class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block>
{ {
public class Block : PrivateMemoryAllocator.Block public class Block : PrivateMemoryAllocator.Block

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
@ -14,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
/// <summary> /// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary> /// </summary>
[SupportedOSPlatform("macos")]
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{ {
public const int PageBits = 12; public const int PageBits = 12;

View File

@ -1,7 +1,15 @@
using System.Diagnostics;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
unsafe class HvVcpu unsafe class HvVcpu
{ {
private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms
private static ulong _interruptTimeDeltaTicks = 0;
public readonly ulong Handle; public readonly ulong Handle;
public readonly HvVcpuExit* ExitInfo; public readonly HvVcpuExit* ExitInfo;
public readonly IHvExecutionContext ShadowContext; public readonly IHvExecutionContext ShadowContext;
@ -21,5 +29,28 @@ namespace Ryujinx.Cpu.AppleHv
NativeContext = nativeContext; NativeContext = nativeContext;
IsEphemeral = isEphemeral; IsEphemeral = isEphemeral;
} }
public void EnableAndUpdateVTimer()
{
// We need to ensure interrupts will be serviced,
// and for that we set up the VTime to trigger an interrupt at fixed intervals.
ulong deltaTicks = _interruptTimeDeltaTicks;
if (deltaTicks == 0)
{
// Calculate our time delta in ticks based on the current clock frequency.
int result = TimeApi.mach_timebase_info(out var timeBaseInfo);
Debug.Assert(result == 0);
deltaTicks = ((InterruptIntervalNs * timeBaseInfo.Denom) + (timeBaseInfo.Numer - 1)) / timeBaseInfo.Numer;
_interruptTimeDeltaTicks = deltaTicks;
}
HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CTL_EL0, 1).ThrowOnError();
HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CVAL_EL0, TimeApi.mach_absolute_time() + deltaTicks).ThrowOnError();
}
} }
} }

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvVcpuPool class HvVcpuPool
{ {
// Since there's a limit on the number of VCPUs we can create, // Since there's a limit on the number of VCPUs we can create,
@ -81,6 +83,8 @@ namespace Ryujinx.Cpu.AppleHv
HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral); HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral);
vcpu.EnableAndUpdateVTimer();
return vcpu; return vcpu;
} }

View File

@ -1,8 +1,10 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
static class HvVm static class HvVm
{ {
// This alignment allows us to use larger blocks on the page table. // This alignment allows us to use larger blocks on the page table.

View File

@ -2,7 +2,7 @@ using ARMeilleure.State;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
public interface IHvExecutionContext interface IHvExecutionContext
{ {
ulong Pc { get; set; } ulong Pc { get; set; }
ulong ElrEl1 { get; set; } ulong ElrEl1 { get; set; }
@ -39,8 +39,5 @@ namespace Ryujinx.Cpu.AppleHv
SetV(i, context.GetV(i)); SetV(i, context.GetV(i));
} }
} }
void RequestInterrupt();
bool GetAndClearInterruptRequested();
} }
} }

View File

@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
struct MachTimebaseInfo
{
public uint Numer;
public uint Denom;
}
[SupportedOSPlatform("macos")]
static partial class TimeApi
{
[LibraryImport("libc", SetLastError = true)]
public static partial ulong mach_absolute_time();
[LibraryImport("libc", SetLastError = true)]
public static partial int mach_timebase_info(out MachTimebaseInfo info);
}
}

View File

@ -335,6 +335,45 @@ namespace Ryujinx.Graphics.GAL
return 1; return 1;
} }
/// <summary>
/// Checks if the texture format is a depth or depth-stencil format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the format is a depth or depth-stencil format, false otherwise</returns>
public static bool HasDepth(this Format format)
{
switch (format)
{
case Format.D16Unorm:
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.D32Float:
case Format.D32FloatS8Uint:
return true;
}
return false;
}
/// <summary>
/// Checks if the texture format is a stencil or depth-stencil format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the format is a stencil or depth-stencil format, false otherwise</returns>
public static bool HasStencil(this Format format)
{
switch (format)
{
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.D32FloatS8Uint:
case Format.S8Uint:
return true;
}
return false;
}
/// <summary> /// <summary>
/// Checks if the texture format is valid to use as image format. /// Checks if the texture format is valid to use as image format.
/// </summary> /// </summary>

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw; using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using System; using System;
@ -806,25 +807,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
updateFlags |= RenderTargetUpdateFlags.Layered; updateFlags |= RenderTargetUpdateFlags.Layered;
} }
if (clearDepth || clearStencil) bool clearDS = clearDepth || clearStencil;
if (clearDS)
{ {
updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil; updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil;
} }
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
// If there is a mismatch on the host clip region and the one explicitly defined by the guest // If there is a mismatch on the host clip region and the one explicitly defined by the guest
// on the screen scissor state, then we need to force only one texture to be bound to avoid // on the screen scissor state, then we need to force only one texture to be bound to avoid
// host clipping. // host clipping.
var screenScissorState = _state.State.ScreenScissorState; var screenScissorState = _state.State.ScreenScissorState;
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
if (clearDS || componentMask == 15)
{
// A full clear if scissor is disabled, or it matches the screen scissor state.
bool fullClear = screenScissorState.X == 0 && screenScissorState.Y == 0;
if (fullClear && clearAffectedByScissor && _state.State.ScissorState[0].Enable)
{
ref var scissorState = ref _state.State.ScissorState[0];
fullClear = scissorState.X1 == screenScissorState.X &&
scissorState.Y1 == screenScissorState.Y &&
scissorState.X2 >= screenScissorState.X + screenScissorState.Width &&
scissorState.Y2 >= screenScissorState.Y + screenScissorState.Height;
}
if (fullClear && clearDS)
{
// Must clear all aspects of the depth-stencil format.
FormatInfo dsFormat = _state.State.RtDepthStencilState.Format.Convert();
bool hasDepth = dsFormat.Format.HasDepth();
bool hasStencil = dsFormat.Format.HasStencil();
if (hasStencil && (!clearStencil || (clearAffectedByStencilMask && _state.State.StencilTestState.FrontMask != 0xff)))
{
fullClear = false;
}
else if (hasDepth && !clearDepth)
{
fullClear = false;
}
}
if (fullClear)
{
updateFlags |= RenderTargetUpdateFlags.DiscardClip;
}
}
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
// Must happen after UpdateRenderTargetState to have up-to-date clip region values. // Must happen after UpdateRenderTargetState to have up-to-date clip region values.
bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 || bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 ||
screenScissorState.Width != _channel.TextureManager.ClipRegionWidth || screenScissorState.Width != _channel.TextureManager.ClipRegionWidth ||
screenScissorState.Height != _channel.TextureManager.ClipRegionHeight; screenScissorState.Height != _channel.TextureManager.ClipRegionHeight;
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
bool needsCustomScissor = !clearAffectedByScissor || clipMismatch; bool needsCustomScissor = !clearAffectedByScissor || clipMismatch;
// Scissor and rasterizer discard also affect clears. // Scissor and rasterizer discard also affect clears.

View File

@ -33,6 +33,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
UpdateDepthStencil = 1 << 3, UpdateDepthStencil = 1 << 3,
/// <summary>
/// Indicates that the data in the clip region can be discarded for the next use.
/// </summary>
DiscardClip = 1 << 4,
/// <summary> /// <summary>
/// Default update flags for draw. /// Default update flags for draw.
/// </summary> /// </summary>

View File

@ -447,6 +447,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl); bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl);
bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered); bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered);
bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor); bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor);
bool discard = updateFlags.HasFlag(RenderTargetUpdateFlags.DiscardClip);
int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
@ -486,6 +487,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
memoryManager, memoryManager,
colorState, colorState,
_vtgWritesRtLayer || layered, _vtgWritesRtLayer || layered,
discard,
samplesInX, samplesInX,
samplesInY, samplesInY,
sizeHint); sizeHint);
@ -525,6 +527,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
dsState, dsState,
dsSize, dsSize,
_vtgWritesRtLayer || layered, _vtgWritesRtLayer || layered,
discard,
samplesInX, samplesInX,
samplesInY, samplesInY,
sizeHint); sizeHint);

View File

@ -570,6 +570,18 @@ namespace Ryujinx.Graphics.Gpu.Image
return Group.CheckDirty(this, consume); return Group.CheckDirty(this, consume);
} }
/// <summary>
/// Discards all data for this texture.
/// This clears all dirty flags, modified flags, and pending copies from other textures.
/// It should be used if the texture data will be fully overwritten by the next use.
/// </summary>
public void DiscardData()
{
Group.DiscardData(this);
_dirty = false;
}
/// <summary> /// <summary>
/// Synchronizes guest and host memory. /// Synchronizes guest and host memory.
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU /// This will overwrite the texture data with the texture data on the guest memory, if a CPU

View File

@ -311,7 +311,7 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.NoCreate; flags |= TextureSearchFlags.NoCreate;
} }
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0); Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint: sizeHint);
texture?.SynchronizeMemory(); texture?.SynchronizeMemory();
@ -324,6 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
/// <param name="colorState">Color buffer texture to find or create</param> /// <param name="colorState">Color buffer texture to find or create</param>
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param> /// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
/// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param>
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
@ -332,6 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Image
MemoryManager memoryManager, MemoryManager memoryManager,
RtColorState colorState, RtColorState colorState,
bool layered, bool layered,
bool discard,
int samplesInX, int samplesInX,
int samplesInY, int samplesInY,
Size sizeHint) Size sizeHint)
@ -398,7 +400,14 @@ namespace Ryujinx.Graphics.Gpu.Image
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; int layerSize = !isLinear ? colorState.LayerSize * 4 : 0;
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize); var flags = TextureSearchFlags.WithUpscale;
if (discard)
{
flags |= TextureSearchFlags.DiscardData;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, layerSize, sizeHint: sizeHint);
texture?.SynchronizeMemory(); texture?.SynchronizeMemory();
@ -412,6 +421,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="dsState">Depth-stencil buffer texture to find or create</param> /// <param name="dsState">Depth-stencil buffer texture to find or create</param>
/// <param name="size">Size of the depth-stencil texture</param> /// <param name="size">Size of the depth-stencil texture</param>
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param> /// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
/// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param>
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
@ -421,6 +431,7 @@ namespace Ryujinx.Graphics.Gpu.Image
RtDepthStencilState dsState, RtDepthStencilState dsState,
Size3D size, Size3D size,
bool layered, bool layered,
bool discard,
int samplesInX, int samplesInX,
int samplesInY, int samplesInY,
Size sizeHint) Size sizeHint)
@ -465,7 +476,14 @@ namespace Ryujinx.Graphics.Gpu.Image
target, target,
formatInfo); formatInfo);
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4); var flags = TextureSearchFlags.WithUpscale;
if (discard)
{
flags |= TextureSearchFlags.DiscardData;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, dsState.LayerSize * 4, sizeHint: sizeHint);
texture?.SynchronizeMemory(); texture?.SynchronizeMemory();
@ -500,6 +518,37 @@ namespace Ryujinx.Graphics.Gpu.Image
return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned); return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned);
} }
/// <summary>
/// Determines if texture data should be fully discarded
/// based on the size hint region and whether it is set to be discarded.
/// </summary>
/// <param name="discard">Whether the size hint region should be discarded</param>
/// <param name="texture">The texture being discarded</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <returns>True if the data should be discarded, false otherwise</returns>
private static bool ShouldDiscard(bool discard, Texture texture, Size? sizeHint)
{
return discard &&
texture.Info.DepthOrLayers == 1 &&
sizeHint != null &&
texture.Width <= sizeHint.Value.Width &&
texture.Height <= sizeHint.Value.Height;
}
/// <summary>
/// Discards texture data if requested and possible.
/// </summary>
/// <param name="discard">Whether the size hint region should be discarded</param>
/// <param name="texture">The texture being discarded</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
private static void DiscardIfNeeded(bool discard, Texture texture, Size? sizeHint)
{
if (ShouldDiscard(discard, texture, sizeHint))
{
texture.DiscardData();
}
}
/// <summary> /// <summary>
/// Tries to find an existing texture, or create a new one if not found. /// Tries to find an existing texture, or create a new one if not found.
/// </summary> /// </summary>
@ -507,6 +556,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param>
/// <param name="info">Texture information of the texture to be found or created</param> /// <param name="info">Texture information of the texture to be found or created</param>
/// <param name="layerSize">Size in bytes of a single texture layer</param> /// <param name="layerSize">Size in bytes of a single texture layer</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <param name="range">Optional ranges of physical memory where the texture data is located</param> /// <param name="range">Optional ranges of physical memory where the texture data is located</param>
/// <returns>The texture</returns> /// <returns>The texture</returns>
public Texture FindOrCreateTexture( public Texture FindOrCreateTexture(
@ -514,9 +564,11 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureSearchFlags flags, TextureSearchFlags flags,
TextureInfo info, TextureInfo info,
int layerSize = 0, int layerSize = 0,
Size? sizeHint = null,
MultiRange? range = null) MultiRange? range = null)
{ {
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
bool discard = (flags & TextureSearchFlags.DiscardData) != 0;
TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0); TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0);
@ -612,6 +664,8 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture != null) if (texture != null)
{ {
DiscardIfNeeded(discard, texture, sizeHint);
texture.SynchronizeMemory(); texture.SynchronizeMemory();
return texture; return texture;
@ -642,11 +696,14 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
// Find view compatible matches. // Find view compatible matches.
int overlapsCount; int overlapsCount = 0;
lock (_textures) if (info.Target != Target.TextureBuffer)
{ {
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); lock (_textures)
{
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
} }
if (_overlapInfo.Length != _textureOverlaps.Length) if (_overlapInfo.Length != _textureOverlaps.Length)
@ -907,7 +964,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// We need to synchronize before copying the old view data to the texture, // We need to synchronize before copying the old view data to the texture,
// otherwise the copied data would be overwritten by a future synchronization. // otherwise the copied data would be overwritten by a future synchronization.
texture.InitializeData(false, setData); texture.InitializeData(false, setData && !ShouldDiscard(discard, texture, sizeHint));
texture.Group.InitializeOverlaps(); texture.Group.InitializeOverlaps();

View File

@ -79,6 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private int[] _allOffsets; private int[] _allOffsets;
private int[] _sliceSizes; private int[] _sliceSizes;
private readonly bool _is3D; private readonly bool _is3D;
private readonly bool _isBuffer;
private bool _hasMipViews; private bool _hasMipViews;
private bool _hasLayerViews; private bool _hasLayerViews;
private readonly int _layers; private readonly int _layers;
@ -118,6 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_physicalMemory = physicalMemory; _physicalMemory = physicalMemory;
_is3D = storage.Info.Target == Target.Texture3D; _is3D = storage.Info.Target == Target.Texture3D;
_isBuffer = storage.Info.Target == Target.TextureBuffer;
_layers = storage.Info.GetSlices(); _layers = storage.Info.GetSlices();
_levels = storage.Info.Levels; _levels = storage.Info.Levels;
@ -278,6 +280,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return dirty; return dirty;
} }
/// <summary>
/// Discards all data for a given texture.
/// This clears all dirty flags, modified flags, and pending copies from other textures.
/// </summary>
/// <param name="texture">The texture being discarded</param>
public void DiscardData(Texture texture)
{
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
{
for (int i = 0; i < regionCount; i++)
{
TextureGroupHandle group = _handles[baseHandle + i];
group.DiscardData();
}
});
}
/// <summary> /// <summary>
/// Synchronize memory for a given texture. /// Synchronize memory for a given texture.
/// If overlapping tracking handles are dirty, fully or partially synchronize the texture data. /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
@ -776,7 +796,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int targetLayerHandles = _hasLayerViews ? slices : 1; int targetLayerHandles = _hasLayerViews ? slices : 1;
int targetLevelHandles = _hasMipViews ? levels : 1; int targetLevelHandles = _hasMipViews ? levels : 1;
if (_is3D) if (_isBuffer)
{
return;
}
else if (_is3D)
{ {
// Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last. // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
@ -1309,7 +1333,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
TextureGroupHandle[] handles; TextureGroupHandle[] handles;
if (!(_hasMipViews || _hasLayerViews)) if (_isBuffer)
{
handles = Array.Empty<TextureGroupHandle>();
}
else if (!(_hasMipViews || _hasLayerViews))
{ {
// Single dirty region. // Single dirty region.
var cpuRegionHandles = new RegionHandle[TextureRange.Count]; var cpuRegionHandles = new RegionHandle[TextureRange.Count];

View File

@ -2,7 +2,6 @@
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
@ -155,6 +154,24 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Discards all data for this handle.
/// This clears all dirty flags, modified flags, and pending copies from other handles.
/// </summary>
public void DiscardData()
{
Modified = false;
DeferredCopy = null;
foreach (RegionHandle handle in Handles)
{
if (handle.Dirty)
{
handle.Reprotect();
}
}
}
/// <summary> /// <summary>
/// Calculate a list of which views overlap this handle. /// Calculate a list of which views overlap this handle.
/// </summary> /// </summary>

View File

@ -14,5 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Image
DepthAlias = 1 << 3, DepthAlias = 1 << 3,
WithUpscale = 1 << 4, WithUpscale = 1 << 4,
NoCreate = 1 << 5, NoCreate = 1 << 5,
DiscardData = 1 << 6,
} }
} }

View File

@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Gpu
{ {
pt.AcquireCallback(_context, pt.UserObj); pt.AcquireCallback(_context, pt.UserObj);
Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, pt.Range); Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, range: pt.Range);
pt.Cache.Tick(); pt.Cache.Tick();

View File

@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public bool InUse; public bool InUse;
public bool InConsumption; public bool InConsumption;
public int SubmissionCount;
public CommandBuffer CommandBuffer; public CommandBuffer CommandBuffer;
public FenceHolder Fence; public FenceHolder Fence;
public SemaphoreHolder Semaphore; public SemaphoreHolder Semaphore;
@ -193,6 +194,11 @@ namespace Ryujinx.Graphics.Vulkan
return _commandBuffers[cbIndex].Fence; return _commandBuffers[cbIndex].Fence;
} }
public int GetSubmissionCount(int cbIndex)
{
return _commandBuffers[cbIndex].SubmissionCount;
}
private int FreeConsumed(bool wait) private int FreeConsumed(bool wait)
{ {
int freeEntry = 0; int freeEntry = 0;
@ -282,6 +288,7 @@ namespace Ryujinx.Graphics.Vulkan
Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle); Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
entry.InUse = false; entry.InUse = false;
entry.InConsumption = true; entry.InConsumption = true;
entry.SubmissionCount++;
_inUseCount--; _inUseCount--;
var commandBuffer = entry.CommandBuffer; var commandBuffer = entry.CommandBuffer;

View File

@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class DescriptorSetManager : IDisposable class DescriptorSetManager : IDisposable
{ {
private const uint DescriptorPoolMultiplier = 16; public const uint MaxSets = 16;
public class DescriptorPoolHolder : IDisposable public class DescriptorPoolHolder : IDisposable
{ {
@ -14,36 +14,28 @@ namespace Ryujinx.Graphics.Vulkan
public Device Device { get; } public Device Device { get; }
private readonly DescriptorPool _pool; private readonly DescriptorPool _pool;
private readonly uint _capacity; private int _freeDescriptors;
private int _totalSets; private int _totalSets;
private int _setsInUse; private int _setsInUse;
private bool _done; private bool _done;
public unsafe DescriptorPoolHolder(Vk api, Device device) public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
{ {
Api = api; Api = api;
Device = device; Device = device;
var poolSizes = new[] foreach (var poolSize in poolSizes)
{ {
new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier), _freeDescriptors += (int)poolSize.DescriptorCount;
new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier), }
new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier),
};
uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
_capacity = maxSets;
fixed (DescriptorPoolSize* pPoolsSize = poolSizes) fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
{ {
var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
{ {
SType = StructureType.DescriptorPoolCreateInfo, SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = maxSets, Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
MaxSets = MaxSets,
PoolSizeCount = (uint)poolSizes.Length, PoolSizeCount = (uint)poolSizes.Length,
PPoolSizes = pPoolsSize, PPoolSizes = pPoolsSize,
}; };
@ -52,18 +44,22 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts) public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
{ {
TryAllocateDescriptorSets(layouts, isTry: false, out var dsc); TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
return dsc; return dsc;
} }
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc) public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
{ {
return TryAllocateDescriptorSets(layouts, isTry: true, out dsc); return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
} }
private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, bool isTry, out DescriptorSetCollection dsc) private unsafe bool TryAllocateDescriptorSets(
ReadOnlySpan<DescriptorSetLayout> layouts,
int consumedDescriptors,
bool isTry,
out DescriptorSetCollection dsc)
{ {
Debug.Assert(!_done); Debug.Assert(!_done);
@ -84,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets); var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
if (isTry && result == Result.ErrorOutOfPoolMemory) if (isTry && result == Result.ErrorOutOfPoolMemory)
{ {
_totalSets = (int)_capacity; _totalSets = (int)MaxSets;
_done = true; _done = true;
DestroyIfDone(); DestroyIfDone();
dsc = default; dsc = default;
@ -95,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
_freeDescriptors -= consumedDescriptors;
_totalSets += layouts.Length; _totalSets += layouts.Length;
_setsInUse += layouts.Length; _setsInUse += layouts.Length;
@ -109,9 +106,15 @@ namespace Ryujinx.Graphics.Vulkan
DestroyIfDone(); DestroyIfDone();
} }
public bool CanFit(int count) public bool CanFit(int setsCount, int descriptorsCount)
{ {
if (_totalSets + count <= _capacity) // Try to determine if an allocation with the given parameters will succeed.
// An allocation may fail if the sets count or descriptors count exceeds the available counts
// of the pool.
// Not getting that right is not fatal, it will just create a new pool and try again,
// but it is less efficient.
if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
{ {
return true; return true;
} }
@ -148,46 +151,74 @@ namespace Ryujinx.Graphics.Vulkan
} }
private readonly Device _device; private readonly Device _device;
private DescriptorPoolHolder _currentPool; private readonly DescriptorPoolHolder[] _currentPools;
public DescriptorSetManager(Device device) public DescriptorSetManager(Device device, int poolCount)
{ {
_device = device; _device = device;
_currentPools = new DescriptorPoolHolder[poolCount];
} }
public Auto<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout) public Auto<DescriptorSetCollection> AllocateDescriptorSet(
Vk api,
DescriptorSetLayout layout,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{ {
Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1]; Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
layouts[0] = layout; layouts[0] = layout;
return AllocateDescriptorSets(api, layouts); return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
} }
public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> layouts) public Auto<DescriptorSetCollection> AllocateDescriptorSets(
Vk api,
ReadOnlySpan<DescriptorSetLayout> layouts,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{ {
// If we fail the first time, just create a new pool and try again. // If we fail the first time, just create a new pool and try again.
if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
{ {
dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts); pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
} }
return new Auto<DescriptorSetCollection>(dsc); return new Auto<DescriptorSetCollection>(dsc);
} }
private DescriptorPoolHolder GetPool(Vk api, int requiredCount) private DescriptorPoolHolder GetPool(
Vk api,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int setsCount,
int descriptorsCount,
bool updateAfterBind)
{ {
if (_currentPool == null || !_currentPool.CanFit(requiredCount)) ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
{ {
_currentPool = new DescriptorPoolHolder(api, _device); currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
} }
return _currentPool; return currentPool;
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {
_currentPool?.Dispose(); for (int index = 0; index < _currentPools.Length; index++)
{
_currentPools[index]?.Dispose();
_currentPools[index] = null;
}
} }
} }

View File

@ -59,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
private BitMapStruct<Array2<long>> _uniformMirrored; private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored; private BitMapStruct<Array2<long>> _storageMirrored;
private bool _updateDescriptorCacheCbIndex;
[Flags] [Flags]
private enum DirtyFlags private enum DirtyFlags
{ {
@ -218,6 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetProgram(ShaderCollection program) public void SetProgram(ShaderCollection program)
{ {
_program = program; _program = program;
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
} }
@ -490,7 +493,13 @@ namespace Ryujinx.Graphics.Vulkan
var dummyBuffer = _dummyBuffer?.GetBuffer(); var dummyBuffer = _dummyBuffer?.GetBuffer();
var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs); if (_updateDescriptorCacheCbIndex)
{
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
if (!program.HasMinimalLayout) if (!program.HasMinimalLayout)
{ {
@ -697,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalCommandBufferChange() public void SignalCommandBufferChange()
{ {
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
_uniformSet.Clear(); _uniformSet.Clear();

View File

@ -1,6 +1,7 @@
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device; private readonly Device _device;
private readonly List<MemoryAllocatorBlockList> _blockLists; private readonly List<MemoryAllocatorBlockList> _blockLists;
private readonly int _blockAlignment; private readonly int _blockAlignment;
private readonly ReaderWriterLockSlim _lock;
public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device) public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device)
{ {
@ -21,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
_device = device; _device = device;
_blockLists = new List<MemoryAllocatorBlockList>(); _blockLists = new List<MemoryAllocatorBlockList>();
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount); _blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
_lock = new(LockRecursionPolicy.NoRecursion);
} }
public MemoryAllocation AllocateDeviceMemory( public MemoryAllocation AllocateDeviceMemory(
@ -40,21 +43,37 @@ namespace Ryujinx.Graphics.Vulkan
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer) private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer)
{ {
for (int i = 0; i < _blockLists.Count; i++) _lock.EnterReadLock();
try
{ {
var bl = _blockLists[i]; for (int i = 0; i < _blockLists.Count; i++)
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
{ {
lock (bl) var bl = _blockLists[i];
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
{ {
return bl.Allocate(size, alignment, map); return bl.Allocate(size, alignment, map);
} }
} }
} }
finally
{
_lock.ExitReadLock();
}
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer); _lock.EnterWriteLock();
_blockLists.Add(newBl);
return newBl.Allocate(size, alignment, map); try
{
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
_blockLists.Add(newBl);
return newBl.Allocate(size, alignment, map);
}
finally
{
_lock.ExitWriteLock();
}
} }
internal int FindSuitableMemoryTypeIndex( internal int FindSuitableMemoryTypeIndex(

View File

@ -3,6 +3,7 @@ using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@ -166,6 +167,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly int _blockAlignment; private readonly int _blockAlignment;
private readonly ReaderWriterLockSlim _lock;
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer) public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer)
{ {
_blocks = new List<Block>(); _blocks = new List<Block>();
@ -174,6 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
MemoryTypeIndex = memoryTypeIndex; MemoryTypeIndex = memoryTypeIndex;
ForBuffer = forBuffer; ForBuffer = forBuffer;
_blockAlignment = blockAlignment; _blockAlignment = blockAlignment;
_lock = new(LockRecursionPolicy.NoRecursion);
} }
public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map) public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map)
@ -184,19 +188,28 @@ namespace Ryujinx.Graphics.Vulkan
throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}."); throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
} }
for (int i = 0; i < _blocks.Count; i++) _lock.EnterReadLock();
{
var block = _blocks[i];
if (block.Mapped == map && block.Size >= size) try
{
for (int i = 0; i < _blocks.Count; i++)
{ {
ulong offset = block.Allocate(size, alignment); var block = _blocks[i];
if (offset != InvalidOffset)
if (block.Mapped == map && block.Size >= size)
{ {
return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size); ulong offset = block.Allocate(size, alignment);
if (offset != InvalidOffset)
{
return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size);
}
} }
} }
} }
finally
{
_lock.ExitReadLock();
}
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment); ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
@ -244,14 +257,23 @@ namespace Ryujinx.Graphics.Vulkan
if (block.IsTotallyFree()) if (block.IsTotallyFree())
{ {
for (int i = 0; i < _blocks.Count; i++) _lock.EnterWriteLock();
try
{ {
if (_blocks[i] == block) for (int i = 0; i < _blocks.Count; i++)
{ {
_blocks.RemoveAt(i); if (_blocks[i] == block)
break; {
_blocks.RemoveAt(i);
break;
}
} }
} }
finally
{
_lock.ExitWriteLock();
}
block.Destroy(_api, _device); block.Destroy(_api, _device);
} }
@ -259,13 +281,22 @@ namespace Ryujinx.Graphics.Vulkan
private void InsertBlock(Block block) private void InsertBlock(Block block)
{ {
int index = _blocks.BinarySearch(block); _lock.EnterWriteLock();
if (index < 0)
{
index = ~index;
}
_blocks.Insert(index, block); try
{
int index = _blocks.BinarySearch(block);
if (index < 0)
{
index = ~index;
}
_blocks.Insert(index, block);
}
finally
{
_lock.ExitWriteLock();
}
} }
public void Dispose() public void Dispose()

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -7,15 +8,28 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class PipelineLayoutCacheEntry class PipelineLayoutCacheEntry
{ {
// Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts.
// It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically.
private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets;
private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets;
private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
private const int MaxPoolSizesPerSet = 2;
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
public DescriptorSetLayout[] DescriptorSetLayouts { get; } public DescriptorSetLayout[] DescriptorSetLayouts { get; }
public PipelineLayout PipelineLayout { get; } public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet;
private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache; private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
private readonly int[] _dsCacheCursor; private readonly int[] _dsCacheCursor;
private int _dsLastCbIndex; private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{ {
@ -44,29 +58,55 @@ namespace Ryujinx.Graphics.Vulkan
bool usePushDescriptors) : this(gd, device, setDescriptors.Count) bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
{ {
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{
int count = 0;
foreach (var descriptor in setDescriptors[setIndex].Descriptors)
{
count += descriptor.Count;
}
_consumedDescriptorsPerSet[setIndex] = count;
}
} }
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection( public void UpdateCommandBufferIndex(int commandBufferIndex)
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
{ {
if (_dsLastCbIndex != commandBufferIndex) int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
{ {
_dsLastCbIndex = commandBufferIndex; _dsLastCbIndex = commandBufferIndex;
_dsLastSubmissionCount = submissionCount;
for (int i = 0; i < _dsCacheCursor.Length; i++) Array.Clear(_dsCacheCursor);
{
_dsCacheCursor[i] = 0;
}
} }
var list = _dsCache[commandBufferIndex][setIndex]; _currentDsCache = _dsCache[commandBufferIndex];
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
var list = _currentDsCache[setIndex];
int index = _dsCacheCursor[setIndex]++; int index = _dsCacheCursor[setIndex]++;
if (index == list.Count) if (index == list.Count)
{ {
var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]); Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex);
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api,
DescriptorSetLayouts[setIndex],
poolSizes,
setIndex,
consumedDescriptors,
false);
list.Add(dsc); list.Add(dsc);
isNew = true; isNew = true;
return dsc; return dsc;
@ -76,6 +116,33 @@ namespace Ryujinx.Graphics.Vulkan
return list[index]; return list[index];
} }
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
{
int count = 1;
switch (setIndex)
{
case PipelineBase.UniformSetIndex:
output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity);
break;
case PipelineBase.StorageSetIndex:
output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity);
break;
case PipelineBase.TextureSetIndex:
output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity);
output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity);
count = 2;
break;
case PipelineBase.ImageSetIndex:
output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity);
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
count = 2;
break;
}
return output[..count];
}
protected virtual unsafe void Dispose(bool disposing) protected virtual unsafe void Dispose(bool disposing)
{ {
if (disposing) if (disposing)

View File

@ -464,13 +464,14 @@ namespace Ryujinx.Graphics.Vulkan
return true; return true;
} }
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection( public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
{ {
return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew); _plce.UpdateCommandBufferIndex(commandBufferIndex);
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)

View File

@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device); DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();

View File

@ -1,6 +1,7 @@
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
@ -158,6 +159,11 @@ namespace Ryujinx.HLE
/// </summary> /// </summary>
public string MultiplayerLanInterfaceId { internal get; set; } public string MultiplayerLanInterfaceId { internal get; set; }
/// <summary>
/// Multiplayer Mode
/// </summary>
public MultiplayerMode MultiplayerMode { internal get; set; }
/// <summary> /// <summary>
/// An action called when HLE force a refresh of output after docked mode changed. /// An action called when HLE force a refresh of output after docked mode changed.
/// </summary> /// </summary>
@ -187,7 +193,8 @@ namespace Ryujinx.HLE
AspectRatio aspectRatio, AspectRatio aspectRatio,
float audioVolume, float audioVolume,
bool useHypervisor, bool useHypervisor,
string multiplayerLanInterfaceId) string multiplayerLanInterfaceId,
MultiplayerMode multiplayerMode)
{ {
VirtualFileSystem = virtualFileSystem; VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager; LibHacHorizonManager = libHacHorizonManager;
@ -214,6 +221,7 @@ namespace Ryujinx.HLE
AudioVolume = audioVolume; AudioVolume = audioVolume;
UseHypervisor = useHypervisor; UseHypervisor = useHypervisor;
MultiplayerLanInterfaceId = multiplayerLanInterfaceId; MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
MultiplayerMode = multiplayerMode;
} }
} }
} }

View File

@ -88,6 +88,7 @@ namespace Ryujinx.HLE.HOS
internal ServerBase ViServer { get; private set; } internal ServerBase ViServer { get; private set; }
internal ServerBase ViServerM { get; private set; } internal ServerBase ViServerM { get; private set; }
internal ServerBase ViServerS { get; private set; } internal ServerBase ViServerS { get; private set; }
internal ServerBase LdnServer { get; private set; }
internal KSharedMemory HidSharedMem { get; private set; } internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; } internal KSharedMemory FontSharedMem { get; private set; }
@ -319,14 +320,17 @@ namespace Ryujinx.HLE.HOS
ViServer = new ServerBase(KernelContext, "ViServerU"); ViServer = new ServerBase(KernelContext, "ViServerU");
ViServerM = new ServerBase(KernelContext, "ViServerM"); ViServerM = new ServerBase(KernelContext, "ViServerM");
ViServerS = new ServerBase(KernelContext, "ViServerS"); ViServerS = new ServerBase(KernelContext, "ViServerS");
LdnServer = new ServerBase(KernelContext, "LdnServer");
StartNewServices(); StartNewServices();
} }
private void StartNewServices() private void StartNewServices()
{ {
HorizonFsClient fsClient = new(this);
ServiceTable = new ServiceTable(); ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient)); var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient));
foreach (var service in services) foreach (var service in services)
{ {

View File

@ -0,0 +1,119 @@
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Horizon;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Fs;
using System;
using System.Collections.Concurrent;
using System.IO;
namespace Ryujinx.HLE.HOS
{
class HorizonFsClient : IFsClient
{
private readonly Horizon _system;
private readonly LibHac.Fs.FileSystemClient _fsClient;
private readonly ConcurrentDictionary<string, LocalStorage> _mountedStorages;
public HorizonFsClient(Horizon system)
{
_system = system;
_fsClient = _system.LibHacHorizonManager.FsClient.Fs;
_mountedStorages = new();
}
public void CloseFile(FileHandle handle)
{
_fsClient.CloseFile((LibHac.Fs.FileHandle)handle.Value);
}
public Result GetFileSize(out long size, FileHandle handle)
{
return _fsClient.GetFileSize(out size, (LibHac.Fs.FileHandle)handle.Value).ToHorizonResult();
}
public Result MountSystemData(string mountName, ulong dataId)
{
string contentPath = _system.ContentManager.GetInstalledContentPath(dataId, StorageId.BuiltInSystem, NcaContentType.PublicData);
string installPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath);
if (!string.IsNullOrWhiteSpace(installPath))
{
string ncaPath = installPath;
if (File.Exists(ncaPath))
{
LocalStorage ncaStorage = null;
try
{
ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open);
Nca nca = new(_system.KeySet, ncaStorage);
using var ncaFileSystem = nca.OpenFileSystem(NcaSectionType.Data, _system.FsIntegrityCheckLevel);
using var ncaFsRef = new UniqueRef<IFileSystem>(ncaFileSystem);
Result result = _fsClient.Register(mountName.ToU8Span(), ref ncaFsRef.Ref).ToHorizonResult();
if (result.IsFailure)
{
ncaStorage.Dispose();
}
else
{
_mountedStorages.TryAdd(mountName, ncaStorage);
}
return result;
}
catch (HorizonResultException ex)
{
ncaStorage?.Dispose();
return ex.ResultValue.ToHorizonResult();
}
}
}
// TODO: Return correct result here, this is likely wrong.
return LibHac.Fs.ResultFs.TargetNotFound.Handle().ToHorizonResult();
}
public Result OpenFile(out FileHandle handle, string path, OpenMode openMode)
{
var result = _fsClient.OpenFile(out var libhacHandle, path.ToU8Span(), (LibHac.Fs.OpenMode)openMode);
handle = new(libhacHandle);
return result.ToHorizonResult();
}
public Result QueryMountSystemDataCacheSize(out long size, ulong dataId)
{
// TODO.
size = 0;
return Result.Success;
}
public Result ReadFile(FileHandle handle, long offset, Span<byte> destination)
{
return _fsClient.ReadFile((LibHac.Fs.FileHandle)handle.Value, offset, destination).ToHorizonResult();
}
public void Unmount(string mountName)
{
if (_mountedStorages.TryRemove(mountName, out LocalStorage ncaStorage))
{
ncaStorage.Dispose();
}
_fsClient.Unmount(mountName.ToU8Span());
}
}
}

View File

@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
[Service("ldn:u")] [Service("ldn:u")]
class IUserServiceCreator : IpcService class IUserServiceCreator : IpcService
{ {
public IUserServiceCreator(ServiceCtx context) { } public IUserServiceCreator(ServiceCtx context) : base(context.Device.System.LdnServer) { }
[CommandCmif(0)] [CommandCmif(0)]
// CreateUserLocalCommunicationService() -> object<nn::ldn::detail::IUserLocalCommunicationService> // CreateUserLocalCommunicationService() -> object<nn::ldn::detail::IUserLocalCommunicationService>

View File

@ -7,10 +7,18 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
Success = 0, Success = 0,
DeviceNotAvailable = (16 << ErrorCodeShift) | ModuleId,
DeviceDisabled = (22 << ErrorCodeShift) | ModuleId, DeviceDisabled = (22 << ErrorCodeShift) | ModuleId,
InvalidState = (32 << ErrorCodeShift) | ModuleId, InvalidState = (32 << ErrorCodeShift) | ModuleId,
Unknown1 = (48 << ErrorCodeShift) | ModuleId, NodeNotFound = (48 << ErrorCodeShift) | ModuleId,
ConnectFailure = (64 << ErrorCodeShift) | ModuleId,
ConnectNotFound = (65 << ErrorCodeShift) | ModuleId,
ConnectTimeout = (66 << ErrorCodeShift) | ModuleId,
ConnectRejected = (67 << ErrorCodeShift) | ModuleId,
InvalidArgument = (96 << ErrorCodeShift) | ModuleId, InvalidArgument = (96 << ErrorCodeShift) | ModuleId,
InvalidObject = (97 << ErrorCodeShift) | ModuleId, InvalidObject = (97 << ErrorCodeShift) | ModuleId,
VersionTooLow = (113 << ErrorCodeShift) | ModuleId,
VersionTooHigh = (114 << ErrorCodeShift) | ModuleId,
TooManyPlayers = (144 << ErrorCodeShift) | ModuleId,
} }
} }

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
enum AcceptPolicy : byte
{
AcceptAll,
RejectAll,
BlackList,
WhiteList,
}
}

View File

@ -0,0 +1,13 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0xC)]
struct AddressEntry
{
public uint Ipv4Address;
public Array6<byte> MacAddress;
public ushort Reserved;
}
}

View File

@ -0,0 +1,11 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
struct AddressList
{
public Array8<AddressEntry> Addresses;
}
}

View File

@ -0,0 +1,16 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x30)]
struct CommonNetworkInfo
{
public Array6<byte> MacAddress;
public Ssid Ssid;
public ushort Channel;
public byte LinkLevel;
public byte NetworkType;
public uint Reserved;
}
}

View File

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
enum DisconnectReason : uint
{
None,
DisconnectedByUser,
DisconnectedBySystem,
DestroyedByUser,
DestroyedBySystem,
Rejected,
SignalLost,
}
}

View File

@ -0,0 +1,13 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct IntentId
{
public long LocalCommunicationId;
public ushort Reserved1;
public ushort SceneId;
public uint Reserved2;
}
}

View File

@ -0,0 +1,23 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x430)]
struct LdnNetworkInfo
{
public Array16<byte> SecurityParameter;
public ushort SecurityMode;
public AcceptPolicy StationAcceptPolicy;
public byte Reserved1;
public ushort Reserved2;
public byte NodeCountMax;
public byte NodeCount;
public Array8<NodeInfo> Nodes;
public ushort Reserved3;
public ushort AdvertiseDataSize;
public Array384<byte> AdvertiseData;
public Array140<byte> Reserved4;
public ulong AuthenticationId;
}
}

View File

@ -0,0 +1,16 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
struct NetworkConfig
{
public IntentId IntentId;
public ushort Channel;
public byte NodeCountMax;
public byte Reserved1;
public ushort LocalCommunicationVersion;
public Array10<byte> Reserved2;
}
}

View File

@ -0,0 +1,12 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
struct NetworkId
{
public IntentId IntentId;
public Array16<byte> SessionId;
}
}

View File

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x480)]
struct NetworkInfo
{
public NetworkId NetworkId;
public CommonNetworkInfo Common;
public LdnNetworkInfo Ldn;
}
}

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
enum NetworkType : uint
{
None,
General,
Ldn,
All,
}
}

View File

@ -0,0 +1,18 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x40)]
struct NodeInfo
{
public uint Ipv4Address;
public Array6<byte> MacAddress;
public byte NodeId;
public byte IsConnected;
public Array33<byte> UserName;
public byte Reserved1;
public ushort LocalCommunicationVersion;
public Array16<byte> Reserved2;
}
}

View File

@ -0,0 +1,62 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 8)]
struct NodeLatestUpdate
{
public NodeLatestUpdateFlags State;
public Array7<byte> Reserved;
}
static class NodeLatestUpdateHelper
{
private static readonly object _lock = new();
public static void CalculateLatestUpdate(this Array8<NodeLatestUpdate> array, Array8<NodeInfo> beforeNodes, Array8<NodeInfo> afterNodes)
{
lock (_lock)
{
for (int i = 0; i < 8; i++)
{
if (beforeNodes[i].IsConnected == 0)
{
if (afterNodes[i].IsConnected != 0)
{
array[i].State |= NodeLatestUpdateFlags.Connect;
}
}
else
{
if (afterNodes[i].IsConnected == 0)
{
array[i].State |= NodeLatestUpdateFlags.Disconnect;
}
}
}
}
}
public static NodeLatestUpdate[] ConsumeLatestUpdate(this Array8<NodeLatestUpdate> array, int number)
{
NodeLatestUpdate[] result = new NodeLatestUpdate[number];
lock (_lock)
{
for (int i = 0; i < number; i++)
{
result[i].Reserved = new Array7<byte>();
if (i < 8)
{
result[i].State = array[i].State;
array[i].State = NodeLatestUpdateFlags.None;
}
}
}
return result;
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[Flags]
enum NodeLatestUpdateFlags : byte
{
None = 0,
Connect = 1 << 0,
Disconnect = 1 << 1,
}
}

View File

@ -0,0 +1,16 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
struct ScanFilter
{
public NetworkId NetworkId;
public NetworkType NetworkType;
public Array6<byte> MacAddress;
public Ssid Ssid;
public Array16<byte> Reserved;
public ScanFilterFlag Flag;
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[Flags]
enum ScanFilterFlag : byte
{
LocalCommunicationId = 1 << 0,
SessionId = 1 << 1,
NetworkType = 1 << 2,
MacAddress = 1 << 3,
Ssid = 1 << 4,
SceneId = 1 << 5,
IntentId = LocalCommunicationId | SceneId,
NetworkId = IntentId | SessionId,
All = NetworkType | IntentId | SessionId | MacAddress | Ssid,
}
}

View File

@ -0,0 +1,13 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x44)]
struct SecurityConfig
{
public SecurityMode SecurityMode;
public ushort PassphraseSize;
public Array64<byte> Passphrase;
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
enum SecurityMode : ushort
{
All,
Retail,
Debug,
}
}

View File

@ -0,0 +1,12 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
struct SecurityParameter
{
public Array16<byte> Data;
public Array16<byte> SessionId;
}
}

View File

@ -0,0 +1,12 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x22)]
struct Ssid
{
public byte Length;
public Array33<byte> Name;
}
}

View File

@ -0,0 +1,12 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x30)]
struct UserConfig
{
public Array33<byte> UserName;
public Array15<byte> Unknown1;
}
}

View File

@ -0,0 +1,104 @@
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
class AccessPoint : IDisposable
{
private byte[] _advertiseData;
private readonly IUserLocalCommunicationService _parent;
public NetworkInfo NetworkInfo;
public Array8<NodeLatestUpdate> LatestUpdates = new();
public bool Connected { get; private set; }
public AccessPoint(IUserLocalCommunicationService parent)
{
_parent = parent;
_parent.NetworkClient.NetworkChange += NetworkChanged;
}
public void Dispose()
{
_parent.NetworkClient.DisconnectNetwork();
_parent.NetworkClient.NetworkChange -= NetworkChanged;
}
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
{
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);
NetworkInfo = e.Info;
if (Connected != e.Connected)
{
Connected = e.Connected;
if (Connected)
{
_parent.SetState(NetworkState.AccessPointCreated);
}
else
{
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedBySystem));
}
}
else
{
_parent.SetState();
}
}
public ResultCode SetAdvertiseData(byte[] advertiseData)
{
_advertiseData = advertiseData;
_parent.NetworkClient.SetAdvertiseData(_advertiseData);
return ResultCode.Success;
}
public ResultCode SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
{
_parent.NetworkClient.SetStationAcceptPolicy(acceptPolicy);
return ResultCode.Success;
}
public ResultCode CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig)
{
CreateAccessPointRequest request = new()
{
SecurityConfig = securityConfig,
UserConfig = userConfig,
NetworkConfig = networkConfig,
};
bool success = _parent.NetworkClient.CreateNetwork(request, _advertiseData ?? Array.Empty<byte>());
return success ? ResultCode.Success : ResultCode.InvalidState;
}
public ResultCode CreateNetworkPrivate(SecurityConfig securityConfig, SecurityParameter securityParameter, UserConfig userConfig, NetworkConfig networkConfig, AddressList addressList)
{
CreateAccessPointPrivateRequest request = new()
{
SecurityConfig = securityConfig,
SecurityParameter = securityParameter,
UserConfig = userConfig,
NetworkConfig = networkConfig,
AddressList = addressList,
};
bool success = _parent.NetworkClient.CreateNetworkPrivate(request, _advertiseData);
return success ? ResultCode.Success : ResultCode.InvalidState;
}
}
}

View File

@ -0,0 +1,15 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x4FC)]
struct ConnectRequest
{
public SecurityConfig SecurityConfig;
public UserConfig UserConfig;
public uint LocalCommunicationVersion;
public uint OptionUnknown;
public NetworkInfo NetworkInfo;
}
}

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