Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d9053bbe37 | ||
|
c25e8427aa | ||
|
21a081b185 | ||
|
b540ea80d1 | ||
|
d692a9b83e | ||
|
9677ddaa5d | ||
|
ce92e8cd04 | ||
|
456fc04007 | ||
|
458452279c | ||
|
817b89767a | ||
|
3fb583c98c | ||
|
d2686e0a5b | ||
|
4905101df1 | ||
|
8750b90a7f | ||
|
af01100050 | ||
|
c0821fee1f | ||
|
a5c2aead67 | ||
|
d41c95dcff | ||
|
fbf2b09706 | ||
|
1fc0f569de | ||
|
dff138229c |
@@ -36,7 +36,7 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of October 2022, Ryujinx has been tested on approximately 3,700 titles; over 3,500 boot past menus and into gameplay, with roughly 3,000 of those being considered playable.
|
||||
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
@@ -90,7 +90,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum) or Vulkan APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.2" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
@@ -112,6 +113,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
if (device == 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -119,6 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
|
||||
SDL_CloseAudioDevice(device);
|
||||
|
||||
return 0;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -71,6 +72,19 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
|
||||
{
|
||||
if ((uint)index > (uint)coefficients.Length)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return coefficients[index];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
|
||||
{
|
||||
@@ -84,8 +98,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
||||
short history0 = loopContext.History0;
|
||||
short history1 = loopContext.History1;
|
||||
short coefficient0 = coefficients[coefficientIndex * 2 + 0];
|
||||
short coefficient1 = coefficients[coefficientIndex * 2 + 1];
|
||||
short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
|
||||
short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
|
||||
|
||||
int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
|
||||
int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
|
||||
@@ -109,8 +123,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
|
||||
coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
||||
|
||||
coefficient0 = coefficients[coefficientIndex * 2 + 0];
|
||||
coefficient1 = coefficients[coefficientIndex * 2 + 1];
|
||||
coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
|
||||
coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
|
||||
|
||||
nibbles += 2;
|
||||
|
||||
|
@@ -116,6 +116,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
};
|
||||
}
|
||||
|
||||
public bool HasRemainingCommands(int sessionId)
|
||||
{
|
||||
return _sessionCommandList[sessionId] != null;
|
||||
}
|
||||
|
||||
public void Signal()
|
||||
{
|
||||
_mailbox.SendMessage(MailboxMessage.RenderStart);
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.AuxiliaryBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint InputBufferIndex { get; }
|
||||
public uint OutputBufferIndex { get; }
|
||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.BiquadFilter;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
||||
public int InputBufferIndex { get; }
|
||||
|
@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.CaptureBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint InputBufferIndex { get; }
|
||||
|
||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.CircularBufferSink;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort[] Input { get; }
|
||||
public uint InputCount { get; }
|
||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.ClearMixBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ClearMixBufferCommand(int nodeId)
|
||||
{
|
||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.CopyMixBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType { get; }
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Delay;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public DelayParameter Parameter => _parameter;
|
||||
public Memory<DelayState> State { get; }
|
||||
@@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 1;
|
||||
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
@@ -70,7 +70,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
float temp = input * inGain + delayLineValue * feedbackGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref temp, 1);
|
||||
state.UpdateLowPassFilter(ref temp, channelCount);
|
||||
|
||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
Y = state.DelayLines[1].Read(),
|
||||
};
|
||||
|
||||
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
W = state.DelayLines[3].Read()
|
||||
};
|
||||
|
||||
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
|
||||
|
||||
@@ -171,12 +171,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain,
|
||||
0.0f, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
|
||||
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f,
|
||||
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f,
|
||||
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackBaseGain, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain);
|
||||
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
|
||||
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
|
||||
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
|
||||
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
|
||||
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
@@ -200,7 +200,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
U = state.DelayLines[5].Read()
|
||||
};
|
||||
|
||||
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
|
||||
|
||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.DepopForMixBuffers;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferOffset { get; }
|
||||
|
||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.DepopPrepare;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.DeviceSink;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public string DeviceName { get; }
|
||||
|
||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.DownMixSurroundToStereo;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.GroupedBiquadFilter;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
private BiquadFilterParameter[] _parameters;
|
||||
private Memory<BiquadFilterState> _biquadFilterStates;
|
||||
@@ -47,9 +47,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Nintendo also implements a hot path for double biquad filters, but no generic path when the command definition suggests it could be done.
|
||||
// As such we currently only implement a generic path for simplicity.
|
||||
// TODO: Implement double biquad filters fast path.
|
||||
// NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done.
|
||||
// As such we currently only implement a generic path for simplicity for double biquad.
|
||||
if (_parameters.Length == 1)
|
||||
{
|
||||
BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);
|
||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType { get; }
|
||||
|
||||
public ulong EstimatedProcessingTime { get; }
|
||||
public uint EstimatedProcessingTime { get; }
|
||||
|
||||
public void Process(CommandList context);
|
||||
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.LimiterVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public LimiterParameter Parameter => _parameter;
|
||||
public Memory<LimiterState> State { get; }
|
||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.LimiterVersion2;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public LimiterParameter Parameter => _parameter;
|
||||
public Memory<LimiterState> State { get; }
|
||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Mix;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.MixRamp;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.MixRampGrouped;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Performance;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
|
||||
|
||||
|
@@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb3d;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
@@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ReverbParameter Parameter => _parameter;
|
||||
public Memory<ReverbState> State { get; }
|
||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Upsample;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint BufferCount { get; }
|
||||
public uint InputBufferIndex { get; }
|
||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.Volume;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
public CommandType CommandType => CommandType.VolumeRamp;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
@@ -28,6 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
private object _lock = new object();
|
||||
|
||||
private AudioRendererRenderingDevice _renderingDevice;
|
||||
private AudioRendererExecutionMode _executionMode;
|
||||
private IWritableEvent _systemEvent;
|
||||
private ManualResetEvent _terminationEvent;
|
||||
@@ -63,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
private uint _renderingTimeLimitPercent;
|
||||
private bool _voiceDropEnabled;
|
||||
private uint _voiceDropCount;
|
||||
private float _voiceDropParameter;
|
||||
private bool _isDspRunningBehind;
|
||||
|
||||
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
|
||||
@@ -95,6 +97,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
|
||||
_totalElapsedTicksUpdating = 0;
|
||||
_sessionId = 0;
|
||||
_voiceDropParameter = 1.0f;
|
||||
}
|
||||
|
||||
public ResultCode Initialize(
|
||||
@@ -130,6 +133,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
_upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
|
||||
_appletResourceId = appletResourceId;
|
||||
_memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
|
||||
_renderingDevice = parameter.RenderingDevice;
|
||||
_executionMode = parameter.ExecutionMode;
|
||||
_sessionId = sessionId;
|
||||
MemoryManager = memoryManager;
|
||||
@@ -337,6 +341,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
|
||||
_processHandle = processHandle;
|
||||
_elapsedFrameCount = 0;
|
||||
_voiceDropParameter = 1.0f;
|
||||
|
||||
switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
|
||||
{
|
||||
@@ -515,7 +520,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
|
||||
}
|
||||
|
||||
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
|
||||
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -584,7 +589,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
command.Enabled = false;
|
||||
|
||||
voicesEstimatedTime -= (long)command.EstimatedProcessingTime;
|
||||
voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -618,13 +623,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
_voiceContext.Sort();
|
||||
commandGenerator.GenerateVoices();
|
||||
|
||||
long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
|
||||
uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
|
||||
|
||||
commandGenerator.GenerateSubMixes();
|
||||
commandGenerator.GenerateFinalMixes();
|
||||
commandGenerator.GenerateSinks();
|
||||
|
||||
long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
|
||||
uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
|
||||
|
||||
if (_voiceDropEnabled)
|
||||
{
|
||||
@@ -665,14 +670,21 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
_terminationEvent.Reset();
|
||||
|
||||
GenerateCommandList(out CommandList commands);
|
||||
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
||||
{
|
||||
GenerateCommandList(out CommandList commands);
|
||||
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
|
||||
_systemEvent.Signal();
|
||||
_systemEvent.Signal();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDspRunningBehind = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -856,5 +868,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVoiceDropParameter(float voiceDropParameter)
|
||||
{
|
||||
_voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f);
|
||||
}
|
||||
|
||||
public float GetVoiceDropParameter()
|
||||
{
|
||||
return _voiceDropParameter;
|
||||
}
|
||||
|
||||
public ResultCode ExecuteAudioRendererRendering()
|
||||
{
|
||||
if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu)
|
||||
{
|
||||
// NOTE: Here Nintendo aborts with this error code, we don't want that.
|
||||
return ResultCode.InvalidExecutionContextOperation;
|
||||
}
|
||||
|
||||
return ResultCode.UnsupportedOperation;
|
||||
}
|
||||
}
|
||||
}
|
@@ -94,8 +94,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// REV11:
|
||||
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
||||
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 14.0.0</remarks>
|
||||
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
||||
public const int Revision11 = 11 << 24;
|
||||
|
||||
/// <summary>
|
||||
|
@@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <summary>
|
||||
/// The estimated total processing time.
|
||||
/// </summary>
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The command list that is populated by the <see cref="CommandBuffer"/>.
|
||||
|
@@ -263,12 +263,12 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
||||
return UpdateResult.Success;
|
||||
}
|
||||
|
||||
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0)
|
||||
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0)
|
||||
{
|
||||
return UpdateResult.InvalidParameter;
|
||||
}
|
||||
|
||||
if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0)
|
||||
if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0)
|
||||
{
|
||||
return UpdateResult.InvalidParameter;
|
||||
}
|
||||
|
@@ -17,5 +17,6 @@ namespace Ryujinx.Audio
|
||||
InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
|
||||
InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
|
||||
UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
|
||||
InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId,
|
||||
}
|
||||
}
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
|
||||
"SettingsButtonSave": "Speichern",
|
||||
"SettingsButtonClose": "Schließen",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Abbrechen",
|
||||
"SettingsButtonApply": "Übernehmen",
|
||||
"ControllerSettingsPlayer": "Spieler",
|
||||
"ControllerSettingsPlayer1": "Spieler 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
|
||||
"SettingsButtonSave": "Αποθήκευση",
|
||||
"SettingsButtonClose": "Κλείσιμο",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Ακύρωση",
|
||||
"SettingsButtonApply": "Εφαρμογή",
|
||||
"ControllerSettingsPlayer": "Παίχτης",
|
||||
"ControllerSettingsPlayer1": "Παίχτης 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
|
||||
"SettingsButtonSave": "Save",
|
||||
"SettingsButtonClose": "Close",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancel",
|
||||
"SettingsButtonApply": "Apply",
|
||||
"ControllerSettingsPlayer": "Player",
|
||||
"ControllerSettingsPlayer1": "Player 1",
|
||||
@@ -594,7 +596,18 @@
|
||||
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
||||
"VolumeShort": "Vol",
|
||||
"SettingsEnableMacroHLE": "Enable Macro HLE",
|
||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure."
|
||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
|
||||
"VolumeShort": "Vol",
|
||||
"UserProfilesManageSaves": "Manage Saves",
|
||||
"DeleteUserSave": "Do you want to delete user save for this game?",
|
||||
"IrreversibleActionNote": "This action is not reversible.",
|
||||
"SaveManagerHeading": "Manage Saves for {0}",
|
||||
"SaveManagerTitle": "Save Manager",
|
||||
"Name": "Name",
|
||||
"Size": "Size",
|
||||
"Search": "Search",
|
||||
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
|
||||
}
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
|
||||
"SettingsButtonSave": "Guardar",
|
||||
"SettingsButtonClose": "Cerrar",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancelar",
|
||||
"SettingsButtonApply": "Aplicar",
|
||||
"ControllerSettingsPlayer": "Jugador",
|
||||
"ControllerSettingsPlayer1": "Jugador 1",
|
||||
|
@@ -161,6 +161,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
|
||||
"SettingsButtonSave": "Enregistrer",
|
||||
"SettingsButtonClose": "Fermer",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Annuler",
|
||||
"SettingsButtonApply": "Appliquer",
|
||||
"ControllerSettingsPlayer": "Joueur",
|
||||
"ControllerSettingsPlayer1": "Joueur 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
|
||||
"SettingsButtonSave": "Salva",
|
||||
"SettingsButtonClose": "Chiudi",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancella",
|
||||
"SettingsButtonApply": "Applica",
|
||||
"ControllerSettingsPlayer": "Giocatore",
|
||||
"ControllerSettingsPlayer1": "Giocatore 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
|
||||
"SettingsButtonSave": "セーブ",
|
||||
"SettingsButtonClose": "閉じる",
|
||||
"SettingsButtonOk": "オーケー",
|
||||
"SettingsButtonCancel": "キャンセル",
|
||||
"SettingsButtonApply": "適用",
|
||||
"ControllerSettingsPlayer": "プレイヤー",
|
||||
"ControllerSettingsPlayer1": "プレイヤー 1",
|
||||
|
@@ -167,6 +167,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
|
||||
"SettingsButtonSave": "구하다",
|
||||
"SettingsButtonClose": "출구",
|
||||
"SettingsButtonOk": "좋아",
|
||||
"SettingsButtonCancel": "취소",
|
||||
"SettingsButtonApply": "적용하다",
|
||||
"ControllerSettingsPlayer": "플레이어",
|
||||
"ControllerSettingsPlayer1": "플레이어 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
|
||||
"SettingsButtonSave": "Zapisz",
|
||||
"SettingsButtonClose": "Zamknij",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Anuluj",
|
||||
"SettingsButtonApply": "Zastosuj",
|
||||
"ControllerSettingsPlayer": "Gracz",
|
||||
"ControllerSettingsPlayer1": "Gracz 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
|
||||
"SettingsButtonSave": "Salvar",
|
||||
"SettingsButtonClose": "Fechar",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancelar",
|
||||
"SettingsButtonApply": "Aplicar",
|
||||
"ControllerSettingsPlayer": "Jogador",
|
||||
"ControllerSettingsPlayer1": "Jogador 1",
|
||||
|
@@ -167,6 +167,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
|
||||
"SettingsButtonSave": "Сохранить",
|
||||
"SettingsButtonClose": "Закрыть",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Отмена",
|
||||
"SettingsButtonApply": "Применить",
|
||||
"ControllerSettingsPlayer": "Игрок",
|
||||
"ControllerSettingsPlayer1": "Игрок 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
|
||||
"SettingsButtonSave": "Kaydet",
|
||||
"SettingsButtonClose": "Kapat",
|
||||
"SettingsButtonOk": "Tamam",
|
||||
"SettingsButtonCancel": "İptal",
|
||||
"SettingsButtonApply": "Uygula",
|
||||
"ControllerSettingsPlayer": "Oyuncu",
|
||||
"ControllerSettingsPlayer1": "Oyuncu 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
|
||||
"SettingsButtonSave": "保存",
|
||||
"SettingsButtonClose": "关闭",
|
||||
"SettingsButtonOk": "批准",
|
||||
"SettingsButtonCancel": "取消",
|
||||
"SettingsButtonApply": "应用",
|
||||
"ControllerSettingsPlayer": "玩家",
|
||||
"ControllerSettingsPlayer1": "玩家 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
|
||||
"SettingsButtonSave": "儲存",
|
||||
"SettingsButtonClose": "關閉",
|
||||
"SettingsButtonOk": "嘛好",
|
||||
"SettingsButtonCancel": "取消",
|
||||
"SettingsButtonApply": "套用",
|
||||
"ControllerSettingsPlayer": "玩家",
|
||||
"ControllerSettingsPlayer1": "玩家 1",
|
||||
|
@@ -41,6 +41,9 @@
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
|
||||
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
|
||||
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
||||
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
|
||||
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Height="2000" Padding="20">
|
||||
@@ -269,13 +268,15 @@
|
||||
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
|
||||
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
|
||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||
<sys:Double x:Key="ScrollBarThickness">15</sys:Double>
|
||||
<sys:Double x:Key="FontSizeSmall">8</sys:Double>
|
||||
<sys:Double x:Key="FontSizeNormal">10</sys:Double>
|
||||
<sys:Double x:Key="FontSize">12</sys:Double>
|
||||
<sys:Double x:Key="FontSizeLarge">15</sys:Double>
|
||||
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double>
|
||||
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||
<x:Double x:Key="FontSize">12</x:Double>
|
||||
<x:Double x:Key="FontSizeLarge">15</x:Double>
|
||||
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
|
||||
<x:Double x:Key="MenuItemHeight">26</x:Double>
|
||||
<x:Double x:Key="TabItemMinHeight">28</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
|
||||
return;
|
||||
}
|
||||
|
||||
OpenSaveDir(saveDataId);
|
||||
}
|
||||
|
||||
public static void OpenSaveDir(ulong saveDataId)
|
||||
{
|
||||
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
|
||||
|
||||
if (!Directory.Exists(saveRootPath))
|
||||
|
@@ -1,8 +1,5 @@
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Avalonia;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@@ -23,18 +20,15 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static double ActualScaleFactor { get; set; }
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
public static RenderTimer RenderTimer { get; private set; }
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||
|
||||
private const uint MB_ICONWARNING = 0x30;
|
||||
private const int BaseDpi = 96;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
@@ -49,11 +43,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
Initialize(args);
|
||||
|
||||
RenderTimer = new RenderTimer();
|
||||
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
||||
RenderTimer.Dispose();
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
@@ -65,7 +55,7 @@ namespace Ryujinx.Ava
|
||||
EnableMultiTouch = true,
|
||||
EnableIme = true,
|
||||
UseEGL = false,
|
||||
UseGpu = false
|
||||
UseGpu = true
|
||||
})
|
||||
.With(new Win32PlatformOptions
|
||||
{
|
||||
@@ -75,12 +65,6 @@ namespace Ryujinx.Ava
|
||||
CompositionBackdropCornerRadius = 8.0f,
|
||||
})
|
||||
.UseSkia()
|
||||
.AfterSetup(_ =>
|
||||
{
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<IRenderTimer>().ToConstant(RenderTimer)
|
||||
.Bind<IRenderLoop>().ToConstant(new RenderLoop(RenderTimer, Dispatcher.UIThread));
|
||||
})
|
||||
.LogToTrace();
|
||||
}
|
||||
|
||||
@@ -115,7 +99,6 @@ namespace Ryujinx.Ava
|
||||
ForceDpiAware.Windows();
|
||||
|
||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
||||
|
||||
// Logging system information.
|
||||
PrintSystemInfo();
|
||||
|
@@ -27,15 +27,15 @@
|
||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageReference Include="DynamicData" Version="7.12.8" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="1.4.4" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
|
||||
|
||||
<PackageReference Include="OpenTK.Core" Version="4.7.2" />
|
||||
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
|
@@ -127,9 +127,16 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
contentDialog.PrimaryButtonClick += deferCloseAction;
|
||||
}
|
||||
|
||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||
if (useOverlay)
|
||||
{
|
||||
await contentDialog.ShowAsync(overlay, ContentDialogPlacement.Popup);
|
||||
|
||||
overlay?.Close();
|
||||
overlay!.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||
}
|
||||
}
|
||||
|
||||
if (useOverlay)
|
||||
@@ -391,4 +398,4 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
@@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public AccountManager AccountManager { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
public HorizonClient HorizonClient { get; }
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public NavigationDialogHost()
|
||||
@@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
|
||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||
VirtualFileSystem virtualFileSystem)
|
||||
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
||||
{
|
||||
AccountManager = accountManager;
|
||||
ContentManager = contentManager;
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
HorizonClient = horizonClient;
|
||||
ViewModel = new UserProfileViewModel(this);
|
||||
|
||||
|
||||
@@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
ContentFrame.Navigate(sourcePageType, parameter);
|
||||
}
|
||||
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
||||
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
||||
{
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||
|
@@ -1,100 +0,0 @@
|
||||
using Avalonia.Rendering;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
internal class RenderTimer : IRenderTimer, IDisposable
|
||||
{
|
||||
public event Action<TimeSpan> Tick
|
||||
{
|
||||
add
|
||||
{
|
||||
_tick += value;
|
||||
|
||||
if (_subscriberCount++ == 0)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
if (--_subscriberCount == 0)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
_tick -= value;
|
||||
}
|
||||
}
|
||||
|
||||
private Thread _tickThread;
|
||||
private readonly System.Timers.Timer _timer;
|
||||
|
||||
private Action<TimeSpan> _tick;
|
||||
private int _subscriberCount;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
private AutoResetEvent _resetEvent;
|
||||
|
||||
public RenderTimer()
|
||||
{
|
||||
_timer = new System.Timers.Timer(15);
|
||||
_resetEvent = new AutoResetEvent(true);
|
||||
_timer.Elapsed += Timer_Elapsed;
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
TickNow();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_timer.Start();
|
||||
if (_tickThread == null)
|
||||
{
|
||||
_tickThread = new Thread(RunTick);
|
||||
_tickThread.Name = "RenderTimerTickThread";
|
||||
_tickThread.IsBackground = true;
|
||||
_isRunning = true;
|
||||
_tickThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void RunTick()
|
||||
{
|
||||
while (_isRunning)
|
||||
{
|
||||
_resetEvent.WaitOne();
|
||||
_tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount));
|
||||
}
|
||||
}
|
||||
|
||||
public void TickNow()
|
||||
{
|
||||
lock (_timer)
|
||||
{
|
||||
_resetEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Elapsed -= Timer_Elapsed;
|
||||
_timer.Stop();
|
||||
_isRunning = false;
|
||||
_resetEvent.Set();
|
||||
_tickThread.Join();
|
||||
_resetEvent.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,11 +4,4 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
||||
<ContentControl
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Name="View"
|
||||
/>
|
||||
</UserControl>
|
||||
|
@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||
View.Content = _currentWindow;
|
||||
Content = _currentWindow;
|
||||
}
|
||||
|
||||
public void CreateVulkan()
|
||||
|
102
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
102
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
@@ -0,0 +1,102 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Height="400"
|
||||
Width="550"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SaveManager">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Name}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Size}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderAscending}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Descending}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<Grid Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0, 0, 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Content="{locale:Locale Search}" VerticalAlignment="Center"/>
|
||||
<TextBox Margin="5,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" Text="{Binding Search}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border Grid.Row="1" Margin="0,5" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SaveModel">
|
||||
<Grid HorizontalAlignment="Stretch" Margin="0,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Border Height="42" Margin="2" Width="42" Padding="10"
|
||||
IsVisible="{Binding !InGameList}">
|
||||
<ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image IsVisible="{Binding InGameList}"
|
||||
Margin="2"
|
||||
Width="42"
|
||||
Height="42"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock MaxLines="3" Width="320" Margin="5" TextWrapping="Wrap"
|
||||
Text="{Binding Title}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Spacing="10" HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Label Content="{Binding SizeString}" IsVisible="{Binding SizeAvailable}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||
MinWidth="0" MinHeight="0" Name="OpenLocation" Command="{Binding OpenLocation}">
|
||||
<ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||
MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}">
|
||||
<ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Avalonia.Controls;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public partial class SaveManager : UserControl
|
||||
{
|
||||
private readonly UserProfile _userProfile;
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private int _sortIndex;
|
||||
private int _orderIndex;
|
||||
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
||||
private string _search;
|
||||
|
||||
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
||||
|
||||
public ObservableCollection<SaveModel> View
|
||||
{
|
||||
get => _view;
|
||||
set => _view = value;
|
||||
}
|
||||
|
||||
public int SortIndex
|
||||
{
|
||||
get => _sortIndex;
|
||||
set
|
||||
{
|
||||
_sortIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public int OrderIndex
|
||||
{
|
||||
get => _orderIndex;
|
||||
set
|
||||
{
|
||||
_orderIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public SaveManager()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_userProfile = userProfile;
|
||||
_horizonClient = horizonClient;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = this;
|
||||
|
||||
Task.Run(LoadSaves);
|
||||
}
|
||||
|
||||
public void LoadSaves()
|
||||
{
|
||||
Saves.Clear();
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
if (save.ProgramId.Value != 0)
|
||||
{
|
||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||
Saves.Add(saveModel);
|
||||
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
|
||||
}
|
||||
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
Saves.AsObservableChangeSet()
|
||||
.Filter(Filter)
|
||||
.Sort(GetComparer())
|
||||
.Bind(out var view).AsObservableList();
|
||||
|
||||
_view.Clear();
|
||||
_view.AddRange(view);
|
||||
}
|
||||
|
||||
private IComparer<SaveModel> GetComparer()
|
||||
{
|
||||
switch (SortIndex)
|
||||
{
|
||||
case 0:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
|
||||
case 1:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Filter(object arg)
|
||||
{
|
||||
if (arg is SaveModel save)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
Margin="0"
|
||||
MinWidth="500"
|
||||
Padding="0"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
@@ -63,7 +64,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxLength="{Binding MaxProfileNameLength}"
|
||||
Text="{Binding Name}" />
|
||||
<TextBlock Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
|
@@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||
_isNewUser = args.isNewUser;
|
||||
if (!_isNewUser)
|
||||
{
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempProfile = new TempProfile();
|
||||
}
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
|
||||
_parent = args.parent;
|
||||
break;
|
||||
@@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
DataContext = TempProfile;
|
||||
|
||||
AddPictureButton.IsVisible = _isNewUser;
|
||||
IdLabel.IsVisible = !_isNewUser;
|
||||
IdLabel.IsVisible = _profile != null;
|
||||
IdText.IsVisible = _profile != null;
|
||||
ChangePictureButton.IsVisible = !_isNewUser;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (_profile != null)
|
||||
if (_profile != null && !_isNewUser)
|
||||
{
|
||||
_profile.Name = TempProfile.Name;
|
||||
_profile.Image = TempProfile.Image;
|
||||
@@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
else if (_isNewUser)
|
||||
{
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
70
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
70
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
@@ -0,0 +1,70 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
MinHeight="400"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UserRecoverer">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0"
|
||||
Margin="5"
|
||||
Height="30"
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding GoBack}">
|
||||
<ui:SymbolIcon Symbol="Back"/>
|
||||
</Button>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Locale:Locale UserProfilesRecoverHeading}"/>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding LostProfiles}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding UserId}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding Recover}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Locale:Locale Recover}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public partial class UserRecoverer : UserControl
|
||||
{
|
||||
private UserProfileViewModel _viewModel;
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserRecoverer()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
|
||||
|
||||
_viewModel = args.viewModel;
|
||||
_parent = args.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
@@ -25,6 +26,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DoubleTapped="ProfilesList_DoubleTapped"
|
||||
@@ -88,21 +90,56 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
HorizontalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding AddUser}"
|
||||
Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Margin="2"
|
||||
Grid.Column="1"
|
||||
Command="{Binding EditUser}"
|
||||
Content="{Locale:Locale UserProfilesEditProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Content="{Locale:Locale UserProfilesManageSaves}"
|
||||
Command="{Binding ManageSaves}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="2"
|
||||
Command="{Binding DeleteUser}"
|
||||
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfileDeletable}" />
|
||||
</StackPanel>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding RecoverLostAccounts}"
|
||||
Content="{Locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.Ncm;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class SaveModel : BaseModel
|
||||
{
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private long _size;
|
||||
|
||||
public Action DeleteAction { get; set; }
|
||||
public ulong SaveId { get; }
|
||||
public ProgramId TitleId { get; }
|
||||
public string TitleIdString => $"{TitleId.Value:X16}";
|
||||
public UserId UserId { get; }
|
||||
public bool InGameList { get; }
|
||||
public string Title { get; }
|
||||
public byte[] Icon { get; }
|
||||
|
||||
public long Size
|
||||
{
|
||||
get => _size; set
|
||||
{
|
||||
_size = value;
|
||||
SizeAvailable = true;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SizeString));
|
||||
OnPropertyChanged(nameof(SizeAvailable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SizeAvailable { get; set; }
|
||||
|
||||
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
||||
|
||||
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_horizonClient = horizonClient;
|
||||
SaveId = info.SaveDataId;
|
||||
TitleId = info.ProgramId;
|
||||
UserId = info.UserId;
|
||||
|
||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
||||
|
||||
InGameList = appData != null;
|
||||
|
||||
if (InGameList)
|
||||
{
|
||||
Icon = appData.Icon;
|
||||
Title = appData.TitleName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var appMetadata = MainWindow.MainWindowViewModel.ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
|
||||
Title = appMetadata.Title ?? TitleIdString;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
|
||||
|
||||
long total_size = GetDirectorySize(saveRoot);
|
||||
long GetDirectorySize(string path)
|
||||
{
|
||||
long size = 0;
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var directories = Directory.GetDirectories(path);
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
size += GetDirectorySize(directory);
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(path);
|
||||
foreach (var file in files)
|
||||
{
|
||||
size += new FileInfo(file).Length;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
Size = total_size;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void OpenLocation()
|
||||
{
|
||||
ApplicationHelper.OpenSaveDir(SaveId);
|
||||
}
|
||||
|
||||
public async void Delete()
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DeleteUserSave"],
|
||||
LocaleManager.Instance["IrreversibleActionNote"],
|
||||
LocaleManager.Instance["InputDialogYes"],
|
||||
LocaleManager.Instance["InputDialogNo"], "");
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
|
||||
|
||||
DeleteAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,9 +45,12 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
_profile = profile;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
if (_profile != null)
|
||||
{
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
}
|
||||
}
|
||||
|
||||
public TempProfile(){}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
@@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
public class UserProfile : BaseModel
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private readonly NavigationDialogHost _owner;
|
||||
private byte[] _image;
|
||||
private string _name;
|
||||
private UserId _userId;
|
||||
@@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
}
|
||||
}
|
||||
|
||||
public UserProfile(Profile profile)
|
||||
public UserProfile(Profile profile, NavigationDialogHost owner)
|
||||
{
|
||||
_profile = profile;
|
||||
_owner = owner;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
@@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
OnPropertyChanged(nameof(IsOpened));
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
public void Recover(UserProfile userProfile)
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
|
||||
}
|
||||
}
|
||||
}
|
@@ -76,6 +76,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
private bool _showAll;
|
||||
private string _lastScannedAmiiboId;
|
||||
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
|
||||
public ApplicationLibrary ApplicationLibrary => _owner.ApplicationLibrary;
|
||||
|
||||
public string TitleName { get; internal set; }
|
||||
|
||||
@@ -103,8 +104,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
_owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
|
||||
Ptc.PtcStateChanged -= ProgressHandler;
|
||||
Ptc.PtcStateChanged += ProgressHandler;
|
||||
@@ -817,7 +818,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
Thread thread = new(() =>
|
||||
{
|
||||
_owner.ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
|
||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
|
||||
|
||||
_isLoading = false;
|
||||
})
|
||||
@@ -1005,7 +1006,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public async void ManageProfiles()
|
||||
{
|
||||
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
|
||||
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem, _owner.LibHacHorizonManager.RyujinxClient);
|
||||
}
|
||||
|
||||
public async void OpenAboutWindow()
|
||||
@@ -1098,7 +1099,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
selection.Favorite = !selection.Favorite;
|
||||
|
||||
_owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||
ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||
{
|
||||
appMetadata.Favorite = selection.Favorite;
|
||||
});
|
||||
|
@@ -118,7 +118,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsMacOS
|
||||
{
|
||||
get => OperatingSystem.IsMacOS();
|
||||
}
|
||||
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
public bool ShowConfirmExit { get; set; }
|
||||
@@ -474,11 +479,40 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
||||
_previousVolumeLevel = Volume;
|
||||
|
||||
if (_owner is SettingsWindow owner)
|
||||
{
|
||||
owner.ControllerSettings?.SaveCurrentProfile();
|
||||
}
|
||||
|
||||
if (_owner.Owner is MainWindow window && _directoryChanged)
|
||||
{
|
||||
window.ViewModel.LoadApplications();
|
||||
}
|
||||
|
||||
_directoryChanged = false;
|
||||
}
|
||||
|
||||
public void RevertIfNotSaved()
|
||||
{
|
||||
Program.ReloadConfig();
|
||||
}
|
||||
|
||||
public void ApplyButton()
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void OkButton()
|
||||
{
|
||||
SaveSettings();
|
||||
_owner.Close();
|
||||
}
|
||||
|
||||
public void CancelButton()
|
||||
{
|
||||
RevertIfNotSaved();
|
||||
_owner.Close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
@@ -19,6 +25,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
public UserProfileViewModel()
|
||||
{
|
||||
Profiles = new ObservableCollection<UserProfile>();
|
||||
LostProfiles = new ObservableCollection<UserProfile>();
|
||||
}
|
||||
|
||||
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
||||
@@ -30,6 +37,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
||||
|
||||
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||
|
||||
public UserProfile SelectedProfile
|
||||
{
|
||||
get => _selectedProfile;
|
||||
@@ -65,12 +74,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
public void LoadProfiles()
|
||||
{
|
||||
Profiles.Clear();
|
||||
LostProfiles.Clear();
|
||||
|
||||
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
Profiles.Add(new UserProfile(profile));
|
||||
Profiles.Add(new UserProfile(profile, _owner));
|
||||
}
|
||||
|
||||
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
||||
@@ -84,6 +94,42 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
default, saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_owner.HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
HashSet<HLE.HOS.Services.Account.Acc.UserId> lostAccounts = new HashSet<HLE.HOS.Services.Account.Acc.UserId>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
var id = new HLE.HOS.Services.Account.Acc.UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
||||
if (Profiles.FirstOrDefault( x=> x.UserId == id) == null)
|
||||
{
|
||||
lostAccounts.Add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach(var account in lostAccounts)
|
||||
{
|
||||
LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), _owner));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddUser()
|
||||
@@ -93,6 +139,25 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
|
||||
}
|
||||
|
||||
public async void ManageSaves()
|
||||
{
|
||||
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
||||
|
||||
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
||||
Content = manager,
|
||||
Padding = new Thickness(0)
|
||||
};
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
}
|
||||
|
||||
public void EditUser()
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
||||
@@ -134,5 +199,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
_owner.GoBack();
|
||||
}
|
||||
|
||||
public void RecoverLostAccounts()
|
||||
{
|
||||
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@
|
||||
Title="Ryujinx"
|
||||
Width="1280"
|
||||
Height="785"
|
||||
MinWidth="1024"
|
||||
MinWidth="1092"
|
||||
MinHeight="680"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
@@ -57,6 +57,8 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Name="MenuBar"
|
||||
MinHeight="35"
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
@@ -549,6 +551,7 @@
|
||||
<Grid
|
||||
Name="StatusBar"
|
||||
Grid.Row="2"
|
||||
MinHeight="30"
|
||||
Height="30"
|
||||
Margin="0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public partial class MainWindow : StyleableWindow
|
||||
{
|
||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||
private bool _canUpdate;
|
||||
private bool _isClosing;
|
||||
private bool _isLoading;
|
||||
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
ViewModel = new MainWindowViewModel(this);
|
||||
|
||||
MainWindowViewModel = ViewModel;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
@@ -90,7 +93,9 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
|
||||
Title = $"Ryujinx {Program.Version}";
|
||||
|
||||
Height /= Program.WindowScaleFactor;
|
||||
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
||||
double barHeight = MenuBar.MinHeight + StatusBar.MinHeight;
|
||||
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
|
||||
Width /= Program.WindowScaleFactor;
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
|
@@ -955,16 +955,25 @@
|
||||
Icon="Document" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
</ui:NavigationView>
|
||||
<StackPanel
|
||||
<ReversibleStackPanel
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="{locale:Locale SettingsButtonSave}" Click="SaveButton_Clicked" />
|
||||
<Button Content="{locale:Locale SettingsButtonClose}" Click="CloseButton_Clicked" />
|
||||
<Button Content="{locale:Locale SettingsButtonApply}"
|
||||
Click="ApplyButton_Clicked" />
|
||||
</StackPanel>
|
||||
HorizontalAlignment="Right"
|
||||
ReverseOrder="{ReflectionBinding IsMacOS}">
|
||||
<Button
|
||||
HotKey="Enter"
|
||||
Classes="accent"
|
||||
Content="{locale:Locale SettingsButtonOk}"
|
||||
Command="{ReflectionBinding OkButton}" />
|
||||
<Button
|
||||
HotKey="Escape"
|
||||
Content="{locale:Locale SettingsButtonCancel}"
|
||||
Command="{ReflectionBinding CancelButton}" />
|
||||
<Button
|
||||
Content="{locale:Locale SettingsButtonApply}"
|
||||
Command="{ReflectionBinding ApplyButton}" />
|
||||
</ReversibleStackPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@@ -199,36 +199,6 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveButton_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CloseButton_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.RevertIfNotSaved();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ApplyButton_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
ViewModel.SaveSettings();
|
||||
ControllerSettings?.SaveCurrentProfile();
|
||||
|
||||
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
|
||||
{
|
||||
window.ViewModel.LoadApplications();
|
||||
}
|
||||
|
||||
ViewModel.DirectoryChanged = false;
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
ControllerSettings.Dispose();
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
<PackageReference Include="System.Management" Version="6.0.0" />
|
||||
<PackageReference Include="System.Management" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
|
||||
Capabilities GetCapabilities();
|
||||
ulong GetCurrentSync();
|
||||
HardwareInfo GetHardwareInfo();
|
||||
|
||||
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
|
||||
|
@@ -338,6 +338,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
return box.Result;
|
||||
}
|
||||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
return _baseRenderer.GetCurrentSync();
|
||||
}
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
{
|
||||
return _baseRenderer.GetHardwareInfo();
|
||||
|
@@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private bool _vsUsesDrawParameters;
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
private uint _vbEnableMask;
|
||||
|
||||
private bool _prevDrawIndexed;
|
||||
private bool _prevDrawIndirect;
|
||||
@@ -76,6 +77,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.VertexBufferState),
|
||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||
|
||||
// Must be done after vertex buffer updates.
|
||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateBlendState,
|
||||
@@ -852,12 +854,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateVertexAttribState()
|
||||
{
|
||||
uint vbEnableMask = _vbEnableMask;
|
||||
|
||||
Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs];
|
||||
|
||||
for (int index = 0; index < Constants.TotalVertexAttribs; index++)
|
||||
{
|
||||
var vertexAttrib = _state.State.VertexAttribState[index];
|
||||
|
||||
int bufferIndex = vertexAttrib.UnpackBufferIndex();
|
||||
|
||||
if ((vbEnableMask & (1u << bufferIndex)) == 0)
|
||||
{
|
||||
// Using a vertex buffer that doesn't exist is invalid, so let's use a dummy attribute for those cases.
|
||||
vertexAttribs[index] = new VertexAttribDescriptor(0, 0, true, Format.R32G32B32A32Float);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
||||
@@ -866,7 +879,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
|
||||
vertexAttribs[index] = new VertexAttribDescriptor(
|
||||
vertexAttrib.UnpackBufferIndex(),
|
||||
bufferIndex,
|
||||
vertexAttrib.UnpackOffset(),
|
||||
vertexAttrib.UnpackIsConstant(),
|
||||
format);
|
||||
@@ -954,6 +967,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
bool drawIndexed = _drawState.DrawIndexed;
|
||||
bool drawIndirect = _drawState.DrawIndirect;
|
||||
uint vbEnableMask = 0;
|
||||
|
||||
for (int index = 0; index < Constants.TotalVertexBuffers; index++)
|
||||
{
|
||||
@@ -971,6 +985,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
ulong address = vertexBuffer.Address.Pack();
|
||||
|
||||
if (_channel.MemoryManager.IsMapped(address))
|
||||
{
|
||||
vbEnableMask |= 1u << index;
|
||||
}
|
||||
|
||||
int stride = vertexBuffer.UnpackStride();
|
||||
|
||||
bool instanced = _state.State.VertexBufferInstanced[index];
|
||||
@@ -1017,6 +1036,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
|
||||
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
|
||||
}
|
||||
|
||||
if (_vbEnableMask != vbEnableMask)
|
||||
{
|
||||
_vbEnableMask = vbEnableMask;
|
||||
UpdateVertexAttribState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -69,6 +69,12 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
internal List<Action> SyncpointActions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
|
||||
/// copies have completed on the GPU, and their data can be freed.
|
||||
/// </summary>
|
||||
internal List<BufferMigration> BufferMigrations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queue with deferred actions that must run on the render thread.
|
||||
/// </summary>
|
||||
@@ -90,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
|
||||
|
||||
private Thread _gpuThread;
|
||||
private bool _pendingSync;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU emulation context.
|
||||
@@ -109,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
SyncActions = new List<Action>();
|
||||
SyncpointActions = new List<Action>();
|
||||
BufferMigrations = new List<BufferMigration>();
|
||||
|
||||
DeferredActions = new Queue<Action>();
|
||||
|
||||
@@ -273,6 +281,17 @@ namespace Ryujinx.Graphics.Gpu
|
||||
SequenceNumber++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases,
|
||||
/// and the migration copy has completed.
|
||||
/// </summary>
|
||||
/// <param name="migration">The buffer migration</param>
|
||||
internal void RegisterBufferMigration(BufferMigration migration)
|
||||
{
|
||||
BufferMigrations.Add(migration);
|
||||
_pendingSync = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an action to be performed the next time a syncpoint is incremented.
|
||||
/// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
|
||||
@@ -288,6 +307,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
else
|
||||
{
|
||||
SyncActions.Add(action);
|
||||
_pendingSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +318,24 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// <param name="syncpoint">True if host sync is being created by a syncpoint</param>
|
||||
public void CreateHostSyncIfNeeded(bool syncpoint)
|
||||
{
|
||||
if (SyncActions.Count > 0 || (syncpoint && SyncpointActions.Count > 0))
|
||||
if (BufferMigrations.Count > 0)
|
||||
{
|
||||
ulong currentSyncNumber = Renderer.GetCurrentSync();
|
||||
|
||||
for (int i = 0; i < BufferMigrations.Count; i++)
|
||||
{
|
||||
BufferMigration migration = BufferMigrations[i];
|
||||
long diff = (long)(currentSyncNumber - migration.SyncNumber);
|
||||
|
||||
if (diff >= 0)
|
||||
{
|
||||
migration.Dispose();
|
||||
BufferMigrations.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingSync || (syncpoint && SyncpointActions.Count > 0))
|
||||
{
|
||||
Renderer.CreateSync(SyncNumber);
|
||||
|
||||
@@ -317,6 +354,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
SyncActions.Clear();
|
||||
SyncpointActions.Clear();
|
||||
}
|
||||
|
||||
_pendingSync = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private bool _useGranular;
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
private int _referenceCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
@@ -229,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (_modifiedRanges == null)
|
||||
{
|
||||
_modifiedRanges = new BufferModifiedRangeList(_context);
|
||||
_modifiedRanges = new BufferModifiedRangeList(_context, this, Flush);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="from">The buffer to inherit from</param>
|
||||
public void InheritModifiedRanges(Buffer from)
|
||||
{
|
||||
if (from._modifiedRanges != null)
|
||||
if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
|
||||
{
|
||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||
{
|
||||
@@ -310,17 +312,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
};
|
||||
|
||||
if (_modifiedRanges == null)
|
||||
{
|
||||
_modifiedRanges = from._modifiedRanges;
|
||||
_modifiedRanges.ReregisterRanges(registerRangeAction);
|
||||
EnsureRangeList();
|
||||
|
||||
from._modifiedRanges = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
|
||||
}
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (ranges != null)
|
||||
{
|
||||
(address, size) = PageAlign(address, size);
|
||||
ranges.WaitForAndGetRanges(address, size, Flush);
|
||||
ranges.WaitForAndFlushRanges(address, size);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
@@ -508,6 +502,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
UnmappedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the buffer reference count.
|
||||
/// </summary>
|
||||
public void IncrementReferenceCount()
|
||||
{
|
||||
_referenceCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the buffer reference count.
|
||||
/// </summary>
|
||||
public void DecrementReferenceCount()
|
||||
{
|
||||
if (--_referenceCount == 0)
|
||||
{
|
||||
DisposeData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the host buffer's data, not its tracking handles.
|
||||
/// </summary>
|
||||
@@ -528,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_memoryTrackingGranular?.Dispose();
|
||||
_memoryTracking?.Dispose();
|
||||
|
||||
DisposeData();
|
||||
DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
@@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
buffer.CopyTo(newBuffer, dstOffset);
|
||||
newBuffer.InheritModifiedRanges(buffer);
|
||||
|
||||
buffer.DisposeData();
|
||||
buffer.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
newBuffer.SynchronizeMemory(address, newSize);
|
||||
|
125
Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
Normal file
125
Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
|
||||
/// Keeps the source buffer alive for data flushes until the migration is complete.
|
||||
/// </summary>
|
||||
internal class BufferMigration : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The offset for the migrated region.
|
||||
/// </summary>
|
||||
private readonly ulong _offset;
|
||||
|
||||
/// <summary>
|
||||
/// The size for the migrated region.
|
||||
/// </summary>
|
||||
private readonly ulong _size;
|
||||
|
||||
/// <summary>
|
||||
/// The buffer that was migrated from.
|
||||
/// </summary>
|
||||
private readonly Buffer _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// The source range action, to be called on overlap with an unreached sync number.
|
||||
/// </summary>
|
||||
private readonly Action<ulong, ulong> _sourceRangeAction;
|
||||
|
||||
/// <summary>
|
||||
/// The source range list.
|
||||
/// </summary>
|
||||
private readonly BufferModifiedRangeList _source;
|
||||
|
||||
/// <summary>
|
||||
/// The destination range list. This range list must be updated when flushing the source.
|
||||
/// </summary>
|
||||
public readonly BufferModifiedRangeList Destination;
|
||||
|
||||
/// <summary>
|
||||
/// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation.
|
||||
/// </summary>
|
||||
public readonly ulong SyncNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a record for a buffer migration.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The source buffer for this migration</param>
|
||||
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
|
||||
/// <param name="source">The modified range list for the source buffer</param>
|
||||
/// <param name="dest">The modified range list for the destination buffer</param>
|
||||
/// <param name="syncNumber">The sync number for when the migration is complete</param>
|
||||
public BufferMigration(
|
||||
Buffer buffer,
|
||||
Action<ulong, ulong> sourceRangeAction,
|
||||
BufferModifiedRangeList source,
|
||||
BufferModifiedRangeList dest,
|
||||
ulong syncNumber)
|
||||
{
|
||||
_offset = buffer.Address;
|
||||
_size = buffer.Size;
|
||||
_buffer = buffer;
|
||||
_sourceRangeAction = sourceRangeAction;
|
||||
_source = source;
|
||||
Destination = dest;
|
||||
SyncNumber = syncNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the given range overlaps this migration, and has not been completed yet.
|
||||
/// </summary>
|
||||
/// <param name="offset">Start offset</param>
|
||||
/// <param name="size">Range size</param>
|
||||
/// <param name="syncNumber">The sync number that was waited on</param>
|
||||
/// <returns>True if overlapping and in progress, false otherwise</returns>
|
||||
public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
|
||||
{
|
||||
ulong end = offset + size;
|
||||
ulong destEnd = _offset + _size;
|
||||
long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
|
||||
|
||||
return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the given range matches this migration.
|
||||
/// </summary>
|
||||
/// <param name="offset">Start offset</param>
|
||||
/// <param name="size">Range size</param>
|
||||
/// <returns>True if the range exactly matches, false otherwise</returns>
|
||||
public bool FullyMatches(ulong offset, ulong size)
|
||||
{
|
||||
return _offset == offset && _size == size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform the migration source's range action on the range provided, clamped to the bounds of the source buffer.
|
||||
/// </summary>
|
||||
/// <param name="offset">Start offset</param>
|
||||
/// <param name="size">Range size</param>
|
||||
/// <param name="syncNumber">Current sync number</param>
|
||||
/// <param name="parent">The modified range list that originally owned this range</param>
|
||||
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
|
||||
{
|
||||
ulong end = offset + size;
|
||||
end = Math.Min(_offset + _size, end);
|
||||
offset = Math.Max(_offset, offset);
|
||||
|
||||
size = end - offset;
|
||||
|
||||
_source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes this reference to the range list, potentially allowing for the source buffer to be disposed.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Destination.RemoveMigration(this);
|
||||
|
||||
_buffer.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@@ -30,17 +32,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public ulong SyncNumber { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The range list that originally owned this range.
|
||||
/// </summary>
|
||||
public BufferModifiedRangeList Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a modified range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <param name="syncNumber">The GPU sync number at the time of creation</param>
|
||||
public BufferModifiedRange(ulong address, ulong size, ulong syncNumber)
|
||||
/// <param name="parent">The range list that owns this range</param>
|
||||
public BufferModifiedRange(ulong address, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
SyncNumber = syncNumber;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,16 +72,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private const int BackingInitialSize = 8;
|
||||
|
||||
private GpuContext _context;
|
||||
private Buffer _parent;
|
||||
private Action<ulong, ulong> _flushAction;
|
||||
|
||||
private List<BufferMigration> _sources;
|
||||
private BufferMigration _migrationTarget;
|
||||
|
||||
private object _lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the modified range list has any entries or not.
|
||||
/// </summary>
|
||||
public bool HasRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a modified range list.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the buffer range list belongs to</param>
|
||||
public BufferModifiedRangeList(GpuContext context) : base(BackingInitialSize)
|
||||
/// <param name="parent">The parent buffer that owns this range list</param>
|
||||
/// <param name="flushAction">The flush action for the parent buffer</param>
|
||||
public BufferModifiedRangeList(GpuContext context, Buffer parent, Action<ulong, ulong> flushAction) : base(BackingInitialSize)
|
||||
{
|
||||
_context = context;
|
||||
_parent = parent;
|
||||
_flushAction = flushAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -142,6 +174,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// Region already exists. Just update the existing sync number.
|
||||
overlap.SyncNumber = syncNumber;
|
||||
overlap.Parent = this;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -152,18 +185,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// A split item must be created behind this overlap.
|
||||
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber));
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
|
||||
if (overlap.Address < endAddress && overlap.EndAddress > endAddress)
|
||||
{
|
||||
// A split item must be created after this overlap.
|
||||
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber));
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
}
|
||||
|
||||
Add(new BufferModifiedRange(address, size, syncNumber));
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,9 +240,102 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the given range action, or one from a migration that overlaps and has not synced yet.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset to pass to the action</param>
|
||||
/// <param name="size">The size to pass to the action</param>
|
||||
/// <param name="syncNumber">The sync number that has been reached</param>
|
||||
/// <param name="parent">The modified range list that originally owned this range</param>
|
||||
/// <param name="rangeAction">The action to perform</param>
|
||||
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
bool firstSource = true;
|
||||
|
||||
if (parent != this)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_sources != null)
|
||||
{
|
||||
foreach (BufferMigration source in _sources)
|
||||
{
|
||||
if (source.Overlaps(offset, size, syncNumber))
|
||||
{
|
||||
if (firstSource && !source.FullyMatches(offset, size))
|
||||
{
|
||||
// Perform this buffer's action first. The migrations will run after.
|
||||
rangeAction(offset, size);
|
||||
}
|
||||
|
||||
source.RangeActionWithMigration(offset, size, syncNumber, parent);
|
||||
|
||||
firstSource = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstSource)
|
||||
{
|
||||
// No overlapping migrations, or they are not meant for this range, flush the data using the given action.
|
||||
rangeAction(offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes modified ranges ready by the sync number from the list, and flushes their buffer data within a given address range.
|
||||
/// </summary>
|
||||
/// <param name="overlaps">Overlapping ranges to check</param>
|
||||
/// <param name="rangeCount">Number of overlapping ranges</param>
|
||||
/// <param name="highestDiff">The highest difference between an overlapping range's sync number and the current one</param>
|
||||
/// <param name="currentSync">The current sync number</param>
|
||||
/// <param name="address">The start address of the flush range</param>
|
||||
/// <param name="endAddress">The end address of the flush range</param>
|
||||
private void RemoveRangesAndFlush(
|
||||
BufferModifiedRange[] overlaps,
|
||||
int rangeCount,
|
||||
long highestDiff,
|
||||
ulong currentSync,
|
||||
ulong address,
|
||||
ulong endAddress)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_migrationTarget == null)
|
||||
{
|
||||
ulong waitSync = currentSync + (ulong)highestDiff;
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
if (diff <= highestDiff)
|
||||
{
|
||||
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
|
||||
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
|
||||
|
||||
_migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets modified ranges within the specified region, waits on ones from a previous sync number,
|
||||
/// and then fires the given action for each range individually.
|
||||
/// and then fires the flush action for each range individually.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function assumes it is called from the background thread.
|
||||
@@ -218,8 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address to query</param>
|
||||
/// <param name="size">Size to query</param>
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void WaitForAndGetRanges(ulong address, ulong size, Action<ulong, ulong> rangeAction)
|
||||
public void WaitForAndFlushRanges(ulong address, ulong size)
|
||||
{
|
||||
ulong endAddress = address + size;
|
||||
ulong currentSync = _context.SyncNumber;
|
||||
@@ -231,10 +356,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Range list must be consistent for this operation
|
||||
lock (_lock)
|
||||
{
|
||||
rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
if (_migrationTarget != null)
|
||||
{
|
||||
rangeCount = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
}
|
||||
}
|
||||
|
||||
if (rangeCount == 0)
|
||||
if (rangeCount == -1)
|
||||
{
|
||||
_migrationTarget.Destination.WaitForAndFlushRanges(address, size);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (rangeCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -264,47 +402,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Wait for the syncpoint.
|
||||
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
|
||||
|
||||
// Flush and remove all regions with the older syncpoint.
|
||||
lock (_lock)
|
||||
{
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
if (diff <= highestDiff)
|
||||
{
|
||||
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
|
||||
rangeAction(clampAddress, clampEnd - clampAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherit ranges from another modified range list.
|
||||
/// </summary>
|
||||
/// <param name="ranges">The range list to inherit from</param>
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> rangeAction)
|
||||
/// <param name="registerRangeAction">The action to call for each modified range</param>
|
||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
||||
{
|
||||
BufferModifiedRange[] inheritRanges;
|
||||
|
||||
lock (ranges._lock)
|
||||
{
|
||||
inheritRanges = ranges.ToArray();
|
||||
}
|
||||
BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
ranges._parent.IncrementReferenceCount();
|
||||
ranges._migrationTarget = migration;
|
||||
|
||||
_context.RegisterBufferMigration(migration);
|
||||
|
||||
inheritRanges = ranges.ToArray();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Add(range);
|
||||
(_sources ??= new List<BufferMigration>()).Add(migration);
|
||||
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
{
|
||||
Add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,44 +441,20 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (range.SyncNumber != currentSync)
|
||||
{
|
||||
rangeAction(range.Address, range.Size);
|
||||
registerRangeAction(range.Address, range.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the given action for modified ranges that aren't from the current sync number.
|
||||
/// Removes a source buffer migration, indicating its copy has completed.
|
||||
/// </summary>
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void ReregisterRanges(Action<ulong, ulong> rangeAction)
|
||||
/// <param name="migration">The migration to remove</param>
|
||||
public void RemoveMigration(BufferMigration migration)
|
||||
{
|
||||
ref var ranges = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
int count;
|
||||
|
||||
// Range list must be consistent for this operation.
|
||||
lock (_lock)
|
||||
{
|
||||
count = Count;
|
||||
if (ranges.Length < count)
|
||||
{
|
||||
Array.Resize(ref ranges, count);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (BufferModifiedRange range in this)
|
||||
{
|
||||
ranges[i++] = range;
|
||||
}
|
||||
}
|
||||
|
||||
ulong currentSync = _context.SyncNumber;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
BufferModifiedRange range = ranges[i];
|
||||
if (range.SyncNumber != currentSync)
|
||||
{
|
||||
rangeAction(range.Address, range.Size);
|
||||
}
|
||||
_sources.Remove(migration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,12 +466,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (overlap.Address < address)
|
||||
{
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber));
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
|
||||
if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber));
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 3897;
|
||||
private const uint CodeGenVersion = 3943;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -331,7 +331,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
case PrimitiveTopology.QuadStrip:
|
||||
return PrimitiveType.QuadStrip;
|
||||
case PrimitiveTopology.Polygon:
|
||||
return PrimitiveType.Polygon;
|
||||
return PrimitiveType.TriangleFan;
|
||||
case PrimitiveTopology.LinesAdjacency:
|
||||
return PrimitiveType.LinesAdjacency;
|
||||
case PrimitiveTopology.LineStripAdjacency:
|
||||
|
@@ -238,6 +238,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_sync.Wait(id);
|
||||
}
|
||||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
return _sync.GetCurrent();
|
||||
}
|
||||
|
||||
public void Screenshot()
|
||||
{
|
||||
_window.ScreenCaptureRequested = true;
|
||||
|
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
|
||||
<PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -40,6 +40,37 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetCurrent()
|
||||
{
|
||||
lock (_handles)
|
||||
{
|
||||
ulong lastHandle = _firstHandle;
|
||||
|
||||
foreach (SyncHandle handle in _handles)
|
||||
{
|
||||
lock (handle)
|
||||
{
|
||||
if (handle.Handle == IntPtr.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handle.ID > lastHandle)
|
||||
{
|
||||
WaitSyncStatus syncResult = GL.ClientWaitSync(handle.Handle, _syncFlags, 0);
|
||||
|
||||
if (syncResult == WaitSyncStatus.AlreadySignaled)
|
||||
{
|
||||
lastHandle = handle.ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastHandle;
|
||||
}
|
||||
}
|
||||
|
||||
public void Wait(ulong id)
|
||||
{
|
||||
SyncHandle result = null;
|
||||
|
@@ -10,13 +10,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
|
||||
private bool _needsAttribsUpdate;
|
||||
|
||||
private readonly VertexAttribDescriptor[] _vertexAttribs;
|
||||
private readonly VertexBufferDescriptor[] _vertexBuffers;
|
||||
|
||||
private int _vertexAttribsCount;
|
||||
private int _vertexBuffersCount;
|
||||
private int _minVertexCount;
|
||||
|
||||
private uint _vertexAttribsInUse;
|
||||
@@ -76,9 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_vertexBuffers[bindingIndex] = vb;
|
||||
}
|
||||
|
||||
_vertexBuffersCount = bindingIndex;
|
||||
_minVertexCount = minVertexCount;
|
||||
_needsAttribsUpdate = true;
|
||||
}
|
||||
|
||||
public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
|
||||
@@ -131,8 +125,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_vertexAttribs[index] = attrib;
|
||||
}
|
||||
|
||||
_vertexAttribsCount = index;
|
||||
|
||||
for (; index < Constants.MaxVertexAttribs; index++)
|
||||
{
|
||||
DisableVertexAttrib(index);
|
||||
@@ -160,13 +152,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
public void PreDraw(int vertexCount)
|
||||
{
|
||||
LimitVertexBuffers(vertexCount);
|
||||
Validate();
|
||||
}
|
||||
|
||||
public void PreDrawVbUnbounded()
|
||||
{
|
||||
UnlimitVertexBuffers();
|
||||
Validate();
|
||||
}
|
||||
|
||||
public void LimitVertexBuffers(int vertexCount)
|
||||
@@ -252,36 +242,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_vertexBuffersLimited = 0;
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
for (int attribIndex = 0; attribIndex < _vertexAttribsCount; attribIndex++)
|
||||
{
|
||||
VertexAttribDescriptor attrib = _vertexAttribs[attribIndex];
|
||||
|
||||
if (!attrib.IsZero)
|
||||
{
|
||||
if ((uint)attrib.BufferIndex >= _vertexBuffersCount)
|
||||
{
|
||||
DisableVertexAttrib(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_vertexBuffers[attrib.BufferIndex].Buffer.Handle == BufferHandle.Null)
|
||||
{
|
||||
DisableVertexAttrib(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_needsAttribsUpdate)
|
||||
{
|
||||
EnableVertexAttrib(attribIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_needsAttribsUpdate = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnableVertexAttrib(int index)
|
||||
{
|
||||
|
@@ -234,7 +234,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var source = operation.GetSource(0);
|
||||
|
||||
var uvec4Type = context.TypeVector(context.TypeU32(), 4);
|
||||
var execution = context.Constant(context.TypeU32(), 3); // Subgroup
|
||||
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
|
||||
|
||||
var maskVector = context.GroupNonUniformBallot(uvec4Type, execution, context.Get(AggregateType.Bool, source));
|
||||
var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)0);
|
||||
@@ -1233,7 +1233,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
|
||||
var srcThreadId = context.BitwiseOr(context.TypeU32(), indexNotSegMask, minThreadId);
|
||||
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
|
||||
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
@@ -1263,7 +1263,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
|
||||
var srcThreadId = context.IAdd(context.TypeU32(), threadId, index);
|
||||
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
|
||||
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
@@ -1289,7 +1289,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
|
||||
var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
|
||||
var valid = context.SGreaterThanEqual(context.TypeBool(), srcThreadId, minThreadId);
|
||||
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
@@ -1319,7 +1319,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
|
||||
var srcThreadId = context.BitwiseXor(context.TypeU32(), threadId, index);
|
||||
var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
|
||||
var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
|
||||
var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId);
|
||||
var result = context.Select(context.TypeFP32(), valid, value, x);
|
||||
|
||||
var validLocal = (AstOperand)operation.GetSource(3);
|
||||
@@ -1861,19 +1861,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
private static OperationResult GenerateVoteAll(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var result = context.SubgroupAllKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
|
||||
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
|
||||
var result = context.GroupNonUniformAll(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0)));
|
||||
return new OperationResult(AggregateType.Bool, result);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateVoteAllEqual(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var result = context.SubgroupAllEqualKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
|
||||
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
|
||||
var result = context.GroupNonUniformAllEqual(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0)));
|
||||
return new OperationResult(AggregateType.Bool, result);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateVoteAny(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var result = context.SubgroupAnyKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
|
||||
var execution = context.Constant(context.TypeU32(), Scope.Subgroup);
|
||||
var result = context.GroupNonUniformAny(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0)));
|
||||
return new OperationResult(AggregateType.Bool, result);
|
||||
}
|
||||
|
||||
|
@@ -50,12 +50,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
CodeGenContext context = new CodeGenContext(info, config, instPool, integerPool);
|
||||
|
||||
context.AddCapability(Capability.GroupNonUniformBallot);
|
||||
context.AddCapability(Capability.GroupNonUniformShuffle);
|
||||
context.AddCapability(Capability.GroupNonUniformVote);
|
||||
context.AddCapability(Capability.ImageBuffer);
|
||||
context.AddCapability(Capability.ImageGatherExtended);
|
||||
context.AddCapability(Capability.ImageQuery);
|
||||
context.AddCapability(Capability.SampledBuffer);
|
||||
context.AddCapability(Capability.SubgroupBallotKHR);
|
||||
context.AddCapability(Capability.SubgroupVoteKHR);
|
||||
|
||||
if (config.TransformFeedbackEnabled && config.LastInVertexPipeline)
|
||||
{
|
||||
@@ -94,9 +94,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
context.AddCapability(Capability.DrawParameters);
|
||||
}
|
||||
|
||||
context.AddExtension("SPV_KHR_shader_ballot");
|
||||
context.AddExtension("SPV_KHR_subgroup_vote");
|
||||
|
||||
Declarations.DeclareAll(context, info);
|
||||
|
||||
if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)
|
||||
|
@@ -12,12 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
public const AccessFlags DefaultAccessFlags =
|
||||
AccessFlags.AccessIndirectCommandReadBit |
|
||||
AccessFlags.AccessShaderReadBit |
|
||||
AccessFlags.AccessShaderWriteBit |
|
||||
AccessFlags.AccessTransferReadBit |
|
||||
AccessFlags.AccessTransferWriteBit |
|
||||
AccessFlags.AccessUniformReadBit;
|
||||
AccessFlags.IndirectCommandReadBit |
|
||||
AccessFlags.ShaderReadBit |
|
||||
AccessFlags.ShaderWriteBit |
|
||||
AccessFlags.TransferReadBit |
|
||||
AccessFlags.TransferWriteBit |
|
||||
AccessFlags.UniformReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
@@ -87,9 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
DependencyFlags.DependencyDeviceGroupBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
DependencyFlags.DeviceGroupBit,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
@@ -273,9 +273,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
dstOffset,
|
||||
data.Length);
|
||||
|
||||
@@ -293,10 +293,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
dstOffset,
|
||||
data.Length);
|
||||
|
||||
@@ -320,9 +320,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
dstOffset,
|
||||
size);
|
||||
|
||||
@@ -334,10 +334,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
dstOffset,
|
||||
size);
|
||||
}
|
||||
|
@@ -10,28 +10,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
class BufferManager : IDisposable
|
||||
{
|
||||
private const MemoryPropertyFlags DefaultBufferMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyHostVisibleBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostCachedBit;
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.HostCachedBit;
|
||||
|
||||
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyHostVisibleBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
|
||||
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const BufferUsageFlags DefaultBufferUsageFlags =
|
||||
BufferUsageFlags.BufferUsageTransferSrcBit |
|
||||
BufferUsageFlags.BufferUsageTransferDstBit |
|
||||
BufferUsageFlags.BufferUsageUniformTexelBufferBit |
|
||||
BufferUsageFlags.BufferUsageStorageTexelBufferBit |
|
||||
BufferUsageFlags.BufferUsageUniformBufferBit |
|
||||
BufferUsageFlags.BufferUsageStorageBufferBit |
|
||||
BufferUsageFlags.BufferUsageIndexBufferBit |
|
||||
BufferUsageFlags.BufferUsageVertexBufferBit |
|
||||
BufferUsageFlags.BufferUsageTransformFeedbackBufferBitExt;
|
||||
BufferUsageFlags.TransferSrcBit |
|
||||
BufferUsageFlags.TransferDstBit |
|
||||
BufferUsageFlags.UniformTexelBufferBit |
|
||||
BufferUsageFlags.StorageTexelBufferBit |
|
||||
BufferUsageFlags.UniformBufferBit |
|
||||
BufferUsageFlags.StorageBufferBit |
|
||||
BufferUsageFlags.IndexBufferBit |
|
||||
BufferUsageFlags.VertexBufferBit |
|
||||
BufferUsageFlags.TransformFeedbackBufferBitExt;
|
||||
|
||||
private readonly PhysicalDevice _physicalDevice;
|
||||
private readonly Device _device;
|
||||
@@ -76,11 +76,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering)
|
||||
{
|
||||
usage |= BufferUsageFlags.BufferUsageConditionalRenderingBitExt;
|
||||
usage |= BufferUsageFlags.ConditionalRenderingBitExt;
|
||||
}
|
||||
else if (gd.Capabilities.SupportsIndirectParameters)
|
||||
{
|
||||
usage |= BufferUsageFlags.BufferUsageIndirectBufferBit;
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
var bufferCreateInfo = new BufferCreateInfo()
|
||||
|
@@ -71,8 +71,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
QueueFamilyIndex = queueFamilyIndex,
|
||||
Flags = CommandPoolCreateFlags.CommandPoolCreateTransientBit |
|
||||
CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit
|
||||
Flags = CommandPoolCreateFlags.TransientBit |
|
||||
CommandPoolCreateFlags.ResetCommandBufferBit
|
||||
};
|
||||
|
||||
api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
|
@@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertBarrier(cbs, AccessFlags.AccessShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.InsertBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user