Compare commits

..

5 Commits

Author SHA1 Message Date
db4242c5dc Implement DMA texture copy component shuffle (#4717)
* Implement DMA texture copy component shuffle

* Set UInt24 alignment to 1
2023-04-24 15:28:03 +02:00
4dd77316f7 Use vector transform feedback outputs with fragment shaders (#4708)
* Use vector transform feedback outputs with fragment shaders

* Shader cache version bump

* Fix missing outputs when vector transform feedback outputs are used
2023-04-24 08:34:38 +02:00
3f98369a17 Set the console title for GTK again (#4706)
Fixes a regression from #3707 where I accidentally removed that line.
2023-04-24 08:15:19 +02:00
c26aeefe03 Fix amiibo timeout issues & log errors/exceptions (#4712) 2023-04-24 02:08:31 +00:00
666e05f5cb Reducing Memory Allocations 202303 (#4624)
* use ArrayPool, avoid 6000-7000 allocs/sec of runtime

* use ArrayPool, avoid ~7k allocs/second during game execution

* use ArrayPool, avoid ~3000 allocs/sec during game execution

* use MemoryPool, reduce 0.5 MB/sec of new allocations during game execution

* avoid over-allocation by setting List<> Capacity when known

* remove LINQ in KTimeManager.UnscheduleFutureInvocation

* KTimeManager - avoid spinning one more time when the time has arrived

* KTimeManager - let SpinWait decide when to Thread.Yield(), and don't SpinOnce() immediately after Thread.Yield()

* use MemoryPool, reduce ~175k bytes/sec allocation during game execution

* IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array

* Make ButtonMappingEntry a record struct to avoid allocations. Set the List<ButtonMappingEntry> capacity according to use.

* add MemoryBuffer type for working with MemoryPool<byte>

* update changes to use MemoryBuffer

* make parameter ReadOnlySpan instead of Span

* whitespace fix

* Revert "IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array"

This reverts commit f2c698bdf65f049e8481c9f2ec7138d9b9a8261d.

* tweak KTimeManager spin behavior

* replace MemoryBuffer with ByteMemoryPool modeled after System.Buffers.ArrayMemoryPool<T>

* make ByteMemoryPoolBuffer responsible for renting memory
2023-04-24 02:06:23 +00:00
26 changed files with 612 additions and 206 deletions

View File

@ -11,6 +11,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common.Logging;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -149,12 +150,16 @@ namespace Ryujinx.Audio.Renderer.Server
state.InUse = false;
}
Memory<VoiceUpdateState>[] voiceUpdateStatesArray = ArrayPool<Memory<VoiceUpdateState>>.Shared.Rent(Constants.VoiceChannelCountMax);
Span<Memory<VoiceUpdateState>> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax);
// Start processing
for (int i = 0; i < context.GetCount(); i++)
{
VoiceInParameter parameter = parameters[i];
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty);
ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<VoiceOutStatus>(ref _output)[0];
@ -197,6 +202,8 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
ArrayPool<Memory<VoiceUpdateState>>.Shared.Return(voiceUpdateStatesArray);
int currentOutputSize = _output.Length;
OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf<VoiceOutStatus>() * context.GetCount());

View File

@ -378,7 +378,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="outStatus">The given user output.</param>
/// <param name="parameter">The user parameter.</param>
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates)
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
{
#if DEBUG
// Sanity check in debug mode of the internal state
@ -424,7 +424,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
/// <param name="mapper">The mapper to use.</param>
/// <param name="behaviourContext">The behaviour context.</param>
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
{
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];

View File

@ -7,6 +7,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Models.Amiibo;
using System;
@ -42,13 +43,18 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _showAllAmiibo;
private bool _useRandomUuid;
private string _usage;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
{
_owner = owner;
_httpClient = new HttpClient { Timeout = TimeSpan.FromMilliseconds(5000) };
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
LastScannedAmiiboId = lastScannedAmiiboId;
TitleId = titleId;
@ -89,9 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_showAllAmiibo = value;
#pragma warning disable 4014
ParseAmiiboData();
#pragma warning restore 4014
OnPropertyChanged();
}
@ -203,8 +207,10 @@ namespace Ryujinx.Ava.UI.ViewModels
{
amiiboJsonString = await DownloadAmiiboJson();
}
catch
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data: {ex}");
ShowInfoDialog();
}
}
@ -369,8 +375,10 @@ namespace Ryujinx.Ava.UI.ViewModels
return false;
}
catch
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to check for amiibo updates: {ex}");
ShowInfoDialog();
return false;
@ -393,6 +401,8 @@ namespace Ryujinx.Ava.UI.ViewModels
return amiiboJsonString;
}
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}");
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk],
@ -429,6 +439,10 @@ namespace Ryujinx.Ava.UI.ViewModels
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
}
}
else
{
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
}
}
private void ResetAmiiboPreview()

View File

@ -0,0 +1,51 @@
using System;
using System.Buffers;
using System.Threading;
namespace Ryujinx.Common.Memory
{
public sealed partial class ByteMemoryPool
{
/// <summary>
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
/// <see cref="ArrayPool{Byte}.Shared"/> and exposes it as <see cref="Memory{Byte}"/>
/// with a length of the requested size.
/// </summary>
private sealed class ByteMemoryPoolBuffer : IMemoryOwner<byte>
{
private byte[] _array;
private readonly int _length;
public ByteMemoryPoolBuffer(int length)
{
_array = ArrayPool<byte>.Shared.Rent(length);
_length = length;
}
/// <summary>
/// Returns a <see cref="Memory{Byte}"/> belonging to this owner.
/// </summary>
public Memory<byte> Memory
{
get
{
byte[] array = _array;
ObjectDisposedException.ThrowIf(array is null, this);
return new Memory<byte>(array, 0, _length);
}
}
public void Dispose()
{
var array = Interlocked.Exchange(ref _array, null);
if (array != null)
{
ArrayPool<byte>.Shared.Return(array);
}
}
}
}
}

View File

@ -0,0 +1,108 @@
using System;
using System.Buffers;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Provides a pool of re-usable byte array instances.
/// </summary>
public sealed partial class ByteMemoryPool
{
private static readonly ByteMemoryPool _shared = new ByteMemoryPool();
/// <summary>
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
/// the <see cref="ByteMemoryPool.Shared"/> instance.
/// </summary>
private ByteMemoryPool()
{
// No implementation
}
/// <summary>
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
/// </summary>
public static ByteMemoryPool Shared => _shared;
/// <summary>
/// Returns the maximum buffer size supported by this pool.
/// </summary>
public int MaxBufferSize => Array.MaxLength;
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> Rent(long length)
=> RentImpl(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> Rent(ulong length)
=> RentImpl(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> Rent(int length)
=> RentImpl(length);
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> RentCleared(long length)
=> RentCleared(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> RentCleared(ulong length)
=> RentCleared(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IMemoryOwner<byte> RentCleared(int length)
{
var buffer = RentImpl(length);
buffer.Memory.Span.Clear();
return buffer;
}
private static ByteMemoryPoolBuffer RentImpl(int length)
{
if ((uint)length > Array.MaxLength)
{
throw new ArgumentOutOfRangeException(nameof(length), length, null);
}
return new ByteMemoryPoolBuffer(length);
}
}
}

View File

@ -6,6 +6,7 @@ using Ryujinx.Graphics.Texture;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Engine.Dma
@ -32,6 +33,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
RemapEnable = 1 << 10
}
/// <summary>
/// Texture parameters for copy.
/// </summary>
private struct TextureParams
{
/// <summary>
/// Copy region X coordinate.
/// </summary>
public readonly int RegionX;
/// <summary>
/// Copy region Y coordinate.
/// </summary>
public readonly int RegionY;
/// <summary>
/// Offset from the base pointer of the data in memory.
/// </summary>
public readonly int BaseOffset;
/// <summary>
/// Bytes per pixel.
/// </summary>
public readonly int Bpp;
/// <summary>
/// Whether the texture is linear. If false, the texture is block linear.
/// </summary>
public readonly bool Linear;
/// <summary>
/// Pixel offset from XYZ coordinates calculator.
/// </summary>
public readonly OffsetCalculator Calculator;
/// <summary>
/// Creates texture parameters.
/// </summary>
/// <param name="regionX">Copy region X coordinate</param>
/// <param name="regionY">Copy region Y coordinate</param>
/// <param name="baseOffset">Offset from the base pointer of the data in memory</param>
/// <param name="bpp">Bytes per pixel</param>
/// <param name="linear">Whether the texture is linear. If false, the texture is block linear</param>
/// <param name="calculator">Pixel offset from XYZ coordinates calculator</param>
public TextureParams(int regionX, int regionY, int baseOffset, int bpp, bool linear, OffsetCalculator calculator)
{
RegionX = regionX;
RegionY = regionY;
BaseOffset = baseOffset;
Bpp = bpp;
Linear = linear;
Calculator = calculator;
}
}
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
private struct UInt24
{
public byte Byte0;
public byte Byte1;
public byte Byte2;
}
/// <summary>
/// Creates a new instance of the DMA copy engine class.
/// </summary>
@ -154,8 +218,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
{
// Buffer to texture copy.
int componentSize = (int)_state.State.SetRemapComponentsComponentSize + 1;
int srcBpp = remap ? ((int)_state.State.SetRemapComponentsNumSrcComponents + 1) * componentSize : 1;
int dstBpp = remap ? ((int)_state.State.SetRemapComponentsNumDstComponents + 1) * componentSize : 1;
int srcComponents = (int)_state.State.SetRemapComponentsNumSrcComponents + 1;
int dstComponents = (int)_state.State.SetRemapComponentsNumDstComponents + 1;
int srcBpp = remap ? srcComponents * componentSize : 1;
int dstBpp = remap ? dstComponents * componentSize : 1;
var dst = Unsafe.As<uint, DmaTexture>(ref _state.State.SetDstBlockSize);
var src = Unsafe.As<uint, DmaTexture>(ref _state.State.SetSrcBlockSize);
@ -274,63 +340,51 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
}
unsafe bool Convert<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan) where T : unmanaged
{
if (srcLinear && dstLinear && srcBpp == dstBpp)
{
// Optimized path for purely linear copies - we don't need to calculate every single byte offset,
// and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
for (int y = 0; y < yCount; y++)
{
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
int srcOffset = srcCalculator.GetOffset(srcRegionX);
int dstOffset = dstCalculator.GetOffset(dstRegionX);
srcSpan.Slice(srcOffset - srcBaseOffset, xCount * srcBpp)
.CopyTo(dstSpan.Slice(dstOffset - dstBaseOffset, xCount * dstBpp));
}
}
else
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - srcBaseOffset;
for (int y = 0; y < yCount; y++)
{
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
for (int x = 0; x < xCount; x++)
{
int srcOffset = srcCalculator.GetOffset(srcRegionX + x);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
}
}
}
return true;
}
// OPT: This allocates a (potentially) huge temporary array and then copies an existing
// region of memory into it, data that might get overwritten entirely anyways. Ideally this should
// all be rewritten to use pooled arrays, but that gets complicated with packed data and strides
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
bool _ = srcBpp switch
TextureParams srcParams = new TextureParams(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
TextureParams dstParams = new TextureParams(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
// If remapping is enabled, we always copy the components directly, in order.
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
bool isIdentityRemap = !remap ||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
if (isIdentityRemap)
{
1 => Convert<byte>(dstSpan, srcSpan),
2 => Convert<ushort>(dstSpan, srcSpan),
4 => Convert<uint>(dstSpan, srcSpan),
8 => Convert<ulong>(dstSpan, srcSpan),
12 => Convert<Bpp12Pixel>(dstSpan, srcSpan),
16 => Convert<Vector128<byte>>(dstSpan, srcSpan),
_ => throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.")
};
// The order of the components doesn't change, so we can just copy directly
// (with layout conversion if necessary).
switch (srcBpp)
{
case 1: Copy<byte>(dstSpan, srcSpan, dstParams, srcParams); break;
case 2: Copy<ushort>(dstSpan, srcSpan, dstParams, srcParams); break;
case 4: Copy<uint>(dstSpan, srcSpan, dstParams, srcParams); break;
case 8: Copy<ulong>(dstSpan, srcSpan, dstParams, srcParams); break;
case 12: Copy<Bpp12Pixel>(dstSpan, srcSpan, dstParams, srcParams); break;
case 16: Copy<Vector128<byte>>(dstSpan, srcSpan, dstParams, srcParams); break;
default: throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.");
}
}
else
{
// The order or value of the components might change.
switch (componentSize)
{
case 1: CopyShuffle<byte>(dstSpan, srcSpan, dstParams, srcParams); break;
case 2: CopyShuffle<ushort>(dstSpan, srcSpan, dstParams, srcParams); break;
case 3: CopyShuffle<UInt24>(dstSpan, srcSpan, dstParams, srcParams); break;
case 4: CopyShuffle<uint>(dstSpan, srcSpan, dstParams, srcParams); break;
default: throw new NotSupportedException($"Unable to copy ${componentSize} component size.");
}
}
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, dstSpan);
}
@ -372,6 +426,133 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
}
/// <summary>
/// Copies data from one texture to another, while performing layout conversion if necessary.
/// </summary>
/// <typeparam name="T">Pixel type</typeparam>
/// <param name="dstSpan">Destination texture memory region</param>
/// <param name="srcSpan">Source texture memory region</param>
/// <param name="dst">Destination texture parameters</param>
/// <param name="src">Source texture parameters</param>
private unsafe void Copy<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan, TextureParams dst, TextureParams src) where T : unmanaged
{
int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount;
if (src.Linear && dst.Linear && src.Bpp == dst.Bpp)
{
// Optimized path for purely linear copies - we don't need to calculate every single byte offset,
// and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
for (int y = 0; y < yCount; y++)
{
src.Calculator.SetY(src.RegionY + y);
dst.Calculator.SetY(dst.RegionY + y);
int srcOffset = src.Calculator.GetOffset(src.RegionX);
int dstOffset = dst.Calculator.GetOffset(dst.RegionX);
srcSpan.Slice(srcOffset - src.BaseOffset, xCount * src.Bpp)
.CopyTo(dstSpan.Slice(dstOffset - dst.BaseOffset, xCount * dst.Bpp));
}
}
else
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dst.BaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - src.BaseOffset;
for (int y = 0; y < yCount; y++)
{
src.Calculator.SetY(src.RegionY + y);
dst.Calculator.SetY(dst.RegionY + y);
for (int x = 0; x < xCount; x++)
{
int srcOffset = src.Calculator.GetOffset(src.RegionX + x);
int dstOffset = dst.Calculator.GetOffset(dst.RegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
}
}
}
}
/// <summary>
/// Sets texture pixel data to a constant value, while performing layout conversion if necessary.
/// </summary>
/// <typeparam name="T">Pixel type</typeparam>
/// <param name="dstSpan">Destination texture memory region</param>
/// <param name="dst">Destination texture parameters</param>
/// <param name="fillValue">Constant pixel value to be set</param>
private unsafe void Fill<T>(Span<byte> dstSpan, TextureParams dst, T fillValue) where T : unmanaged
{
int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount;
fixed (byte* dstPtr = dstSpan)
{
byte* dstBase = dstPtr - dst.BaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
for (int y = 0; y < yCount; y++)
{
dst.Calculator.SetY(dst.RegionY + y);
for (int x = 0; x < xCount; x++)
{
int dstOffset = dst.Calculator.GetOffset(dst.RegionX + x);
*(T*)(dstBase + dstOffset) = fillValue;
}
}
}
}
/// <summary>
/// Copies data from one texture to another, while performing layout conversion and component shuffling if necessary.
/// </summary>
/// <typeparam name="T">Pixel type</typeparam>
/// <param name="dstSpan">Destination texture memory region</param>
/// <param name="srcSpan">Source texture memory region</param>
/// <param name="dst">Destination texture parameters</param>
/// <param name="src">Source texture parameters</param>
private void CopyShuffle<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan, TextureParams dst, TextureParams src) where T : unmanaged
{
int dstComponents = (int)_state.State.SetRemapComponentsNumDstComponents + 1;
for (int i = 0; i < dstComponents; i++)
{
SetRemapComponentsDst componentsDst = i switch
{
0 => _state.State.SetRemapComponentsDstX,
1 => _state.State.SetRemapComponentsDstY,
2 => _state.State.SetRemapComponentsDstZ,
_ => _state.State.SetRemapComponentsDstW
};
switch (componentsDst)
{
case SetRemapComponentsDst.SrcX:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan, dst, src);
break;
case SetRemapComponentsDst.SrcY:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>()), dst, src);
break;
case SetRemapComponentsDst.SrcZ:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>() * 2), dst, src);
break;
case SetRemapComponentsDst.SrcW:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>() * 3), dst, src);
break;
case SetRemapComponentsDst.ConstA:
Fill<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), dst, Unsafe.As<uint, T>(ref _state.State.SetRemapConstA));
break;
case SetRemapComponentsDst.ConstB:
Fill<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), dst, Unsafe.As<uint, T>(ref _state.State.SetRemapConstB));
break;
}
}
}
/// <summary>
/// Copies block linear data with block linear GOBs to a block linear destination with linear GOBs.
/// </summary>

View File

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

View File

@ -449,7 +449,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (translatorContexts[i] != null)
{
translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute);
translatorContexts[i].SetLastInVertexPipeline(translatorContexts[5] != null);
translatorContexts[i].SetLastInVertexPipeline();
break;
}
}

View File

@ -569,7 +569,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.Stage == ShaderStage.Fragment)
{
for (int c = 0; c < 4; c++)
int attrOffset = AttributeConsts.UserAttributeBase + attr * 16;
int components = context.Info.GetTransformFeedbackOutputComponents(attrOffset);
if (components > 1)
{
string type = components switch
{
2 => "vec2",
3 => "vec3",
4 => "vec4",
_ => "float"
};
context.AppendLine($"layout (location = {attr}) in {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
@ -642,7 +658,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
int attrOffset = AttributeConsts.UserAttributeBase + attr * 16;
int components = context.Config.LastInPipeline ? context.Info.GetTransformFeedbackOutputComponents(attrOffset) : 1;
int components = context.Info.GetTransformFeedbackOutputComponents(attrOffset);
if (components > 1)
{
@ -664,22 +680,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"layout (location = {attr}{xfb}) out {type} {name};");
}
else
for (int c = components > 1 ? components : 0; c < 4; c++)
{
for (int c = 0; c < 4; c++)
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Info.GetTransformFeedbackOutput(attrOffset + c * 4);
if (tfOutput.Valid)
{
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Info.GetTransformFeedbackOutput(attrOffset + c * 4);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
}
}
else

View File

@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
((config.LastInVertexPipeline && isOutAttr) ||
(config.Stage == ShaderStage.Fragment && !isOutAttr)))
{
int components = config.LastInPipeline ? context.Info.GetTransformFeedbackOutputComponents(attrOffset) : 1;
int components = context.Info.GetTransformFeedbackOutputComponents(attrOffset);
string name = components > 1 ? $"{prefix}{(value >> 4)}" : $"{prefix}{(value >> 4)}_{swzMask}";
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))

View File

@ -341,7 +341,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
attrOffset = attr;
type = elemType;
if (Config.LastInPipeline && isOutAttr)
if (isOutAttr)
{
int components = Info.GetTransformFeedbackOutputComponents(attr);

View File

@ -673,7 +673,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int components = 1;
var type = attrInfo.Type & AggregateType.ElementTypeMask;
if (context.Config.LastInPipeline && isOutAttr)
if (isOutAttr)
{
components = context.Info.GetTransformFeedbackOutputComponents(attr);

View File

@ -65,7 +65,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
context.LeaveFunction();
}
if (config.TransformFeedbackEnabled && config.LastInVertexPipeline)
if (config.TransformFeedbackEnabled && (config.LastInVertexPipeline || config.Stage == ShaderStage.Fragment))
{
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{

View File

@ -17,7 +17,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderStage Stage { get; }
public bool GpPassthrough { get; }
public bool LastInPipeline { get; private set; }
public bool LastInVertexPipeline { get; private set; }
public bool HasLayerInputAttribute { get; private set; }
@ -145,7 +144,6 @@ namespace Ryujinx.Graphics.Shader.Translation
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
LastInPipeline = true;
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}
@ -253,13 +251,8 @@ namespace Ryujinx.Graphics.Shader.Translation
GpLayerInputAttribute = attr;
}
public void SetLastInVertexPipeline(bool hasFragment)
public void SetLastInVertexPipeline()
{
if (!hasFragment)
{
LastInPipeline = true;
}
LastInVertexPipeline = true;
}
@ -331,8 +324,6 @@ namespace Ryujinx.Graphics.Shader.Translation
config._perPatchAttributeLocations = locationsMap;
}
LastInPipeline = false;
// We don't consider geometry shaders using the geometry shader passthrough feature
// as being the last because when this feature is used, it can't actually modify any of the outputs,
// so the stage that comes before it is the last one that can do modifications.

View File

@ -143,9 +143,9 @@ namespace Ryujinx.Graphics.Shader.Translation
_config.SetGeometryShaderLayerInputAttribute(attr);
}
public void SetLastInVertexPipeline(bool hasFragment)
public void SetLastInVertexPipeline()
{
_config.SetLastInVertexPipeline(hasFragment);
_config.SetLastInVertexPipeline();
}
public ShaderProgram Translate(TranslatorContext other = null)

View File

@ -27,98 +27,103 @@ namespace Ryujinx.HLE.HOS.Ipc
public IpcMessage()
{
PtrBuff = new List<IpcPtrBuffDesc>();
SendBuff = new List<IpcBuffDesc>();
ReceiveBuff = new List<IpcBuffDesc>();
ExchangeBuff = new List<IpcBuffDesc>();
RecvListBuff = new List<IpcRecvListBuffDesc>();
PtrBuff = new List<IpcPtrBuffDesc>(0);
SendBuff = new List<IpcBuffDesc>(0);
ReceiveBuff = new List<IpcBuffDesc>(0);
ExchangeBuff = new List<IpcBuffDesc>(0);
RecvListBuff = new List<IpcRecvListBuffDesc>(0);
ObjectIds = new List<int>();
ObjectIds = new List<int>(0);
}
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr) : this()
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
{
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data))
{
BinaryReader reader = new BinaryReader(ms);
Initialize(reader, cmdPtr);
}
}
int word0 = reader.ReadInt32();
int word1 = reader.ReadInt32();
private void Initialize(BinaryReader reader, long cmdPtr)
{
int word0 = reader.ReadInt32();
int word1 = reader.ReadInt32();
Type = (IpcMessageType)(word0 & 0xffff);
Type = (IpcMessageType)(word0 & 0xffff);
int ptrBuffCount = (word0 >> 16) & 0xf;
int sendBuffCount = (word0 >> 20) & 0xf;
int recvBuffCount = (word0 >> 24) & 0xf;
int xchgBuffCount = (word0 >> 28) & 0xf;
int ptrBuffCount = (word0 >> 16) & 0xf;
int sendBuffCount = (word0 >> 20) & 0xf;
int recvBuffCount = (word0 >> 24) & 0xf;
int xchgBuffCount = (word0 >> 28) & 0xf;
int rawDataSize = (word1 >> 0) & 0x3ff;
int recvListFlags = (word1 >> 10) & 0xf;
bool hndDescEnable = ((word1 >> 31) & 0x1) != 0;
int rawDataSize = (word1 >> 0) & 0x3ff;
int recvListFlags = (word1 >> 10) & 0xf;
bool hndDescEnable = ((word1 >> 31) & 0x1) != 0;
if (hndDescEnable)
{
HandleDesc = new IpcHandleDesc(reader);
}
for (int index = 0; index < ptrBuffCount; index++)
{
PtrBuff.Add(new IpcPtrBuffDesc(reader));
}
void ReadBuff(List<IpcBuffDesc> buff, int count)
{
for (int index = 0; index < count; index++)
if (hndDescEnable)
{
buff.Add(new IpcBuffDesc(reader));
HandleDesc = new IpcHandleDesc(reader);
}
}
ReadBuff(SendBuff, sendBuffCount);
ReadBuff(ReceiveBuff, recvBuffCount);
ReadBuff(ExchangeBuff, xchgBuffCount);
PtrBuff = new List<IpcPtrBuffDesc>(ptrBuffCount);
rawDataSize *= 4;
for (int index = 0; index < ptrBuffCount; index++)
{
PtrBuff.Add(new IpcPtrBuffDesc(reader));
}
long recvListPos = reader.BaseStream.Position + rawDataSize;
static List<IpcBuffDesc> ReadBuff(BinaryReader reader, int count)
{
List<IpcBuffDesc> buff = new List<IpcBuffDesc>(count);
for (int index = 0; index < count; index++)
{
buff.Add(new IpcBuffDesc(reader));
}
return buff;
}
SendBuff = ReadBuff(reader, sendBuffCount);
ReceiveBuff = ReadBuff(reader, recvBuffCount);
ExchangeBuff = ReadBuff(reader, xchgBuffCount);
rawDataSize *= 4;
long recvListPos = reader.BaseStream.Position + rawDataSize;
// Only CMIF has the padding requirements.
if (Type < IpcMessageType.TipcCloseSession)
{
long pad0 = GetPadSize16(reader.BaseStream.Position + cmdPtr);
if (rawDataSize != 0)
{
rawDataSize -= (int)pad0;
if (rawDataSize != 0)
{
rawDataSize -= (int)pad0;
}
reader.BaseStream.Seek(pad0, SeekOrigin.Current);
}
reader.BaseStream.Seek(pad0, SeekOrigin.Current);
}
int recvListCount = recvListFlags - 2;
int recvListCount = recvListFlags - 2;
if (recvListCount == 0)
{
recvListCount = 1;
}
else if (recvListCount < 0)
{
recvListCount = 0;
}
if (recvListCount == 0)
{
recvListCount = 1;
}
else if (recvListCount < 0)
{
recvListCount = 0;
}
RawData = reader.ReadBytes(rawDataSize);
RawData = reader.ReadBytes(rawDataSize);
reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin);
reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin);
RecvListBuff = new List<IpcRecvListBuffDesc>(recvListCount);
for (int index = 0; index < recvListCount; index++)
{
RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64()));
for (int index = 0; index < recvListCount; index++)
{
RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64()));
}
ObjectIds = new List<int>(0);
}
}

View File

@ -71,7 +71,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
{
lock (_context.CriticalSection.Lock)
{
_waitingObjects.RemoveAll(x => x.Object == schedulerObj);
for (int index = _waitingObjects.Count - 1; index >= 0; index--)
{
if (_waitingObjects[index].Object == schedulerObj)
{
_waitingObjects.RemoveAt(index);
}
}
}
}
@ -105,16 +111,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
}
else
{
while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks <= next.TimePoint)
while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks < next.TimePoint)
{
// Our time is close - don't let SpinWait go off and potentially Thread.Sleep().
if (spinWait.NextSpinWillYield)
{
Thread.Yield();
spinWait.Reset();
}
spinWait.SpinOnce();
else
{
spinWait.SpinOnce();
}
}
spinWait.Reset();

View File

@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
using System.Buffers;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
@ -553,7 +554,9 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
KProcess currentProcess = KernelStatic.GetCurrentProcess();
KSynchronizationObject[] syncObjs = handles.Length == 0 ? Array.Empty<KSynchronizationObject>() : new KSynchronizationObject[handles.Length];
KSynchronizationObject[] syncObjsArray = ArrayPool<KSynchronizationObject>.Shared.Rent(handles.Length);
Span<KSynchronizationObject> syncObjs = syncObjsArray.AsSpan(0, handles.Length);
for (int index = 0; index < handles.Length; index++)
{
@ -606,6 +609,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
}
ArrayPool<KSynchronizationObject>.Shared.Return(syncObjsArray);
return result;
}

View File

@ -1,6 +1,7 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.Horizon.Common;
using System;
using System.Buffers;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@ -59,7 +60,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
else
{
LinkedListNode<KThread>[] syncNodes = syncObjs.Length == 0 ? Array.Empty<LinkedListNode<KThread>>() : new LinkedListNode<KThread>[syncObjs.Length];
LinkedListNode<KThread>[] syncNodesArray = ArrayPool<LinkedListNode<KThread>>.Shared.Rent(syncObjs.Length);
Span<LinkedListNode<KThread>> syncNodes = syncNodesArray.AsSpan(0, syncObjs.Length);
for (int index = 0; index < syncObjs.Length; index++)
{
@ -101,6 +104,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
handleIndex = index;
}
}
ArrayPool<LinkedListNode<KThread>>.Shared.Return(syncNodesArray);
}
_context.CriticalSection.Leave();

View File

@ -1,4 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
@ -68,25 +69,29 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
Memory<byte> output = new byte[outputSize];
Memory<byte> performanceOutput = new byte[performanceOutputSize];
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
using (IMemoryOwner<byte> outputOwner = ByteMemoryPool.Shared.RentCleared(outputSize))
using (IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Shared.RentCleared(performanceOutputSize))
{
context.Memory.Write(outputPosition, output.Span);
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
}
Memory<byte> output = outputOwner.Memory;
Memory<byte> performanceOutput = performanceOutputOwner.Memory;
return result;
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write(outputPosition, output.Span);
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
}
return result;
}
}
[CommandCmif(5)]

View File

@ -76,6 +76,8 @@ namespace Ryujinx.HLE.HOS.Services
context.RequestData.BaseStream.Seek(0x10 + dataPayloadSize, SeekOrigin.Begin);
context.Request.ObjectIds.EnsureCapacity(inputObjCount);
for (int index = 0; index < inputObjCount; index++)
{
context.Request.ObjectIds.Add(context.RequestData.ReadInt32());

View File

@ -217,6 +217,8 @@ namespace Ryujinx.HLE.HOS.Services
if (noReceive)
{
response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count);
for (int i = 0; i < request.RecvListBuff.Count; i++)
{
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2));

View File

@ -1,7 +1,9 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
using System.Buffers;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
@ -83,16 +85,19 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
Span<byte> outputParcel = new Span<byte>(new byte[replySize]);
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
if (result == ResultCode.Success)
using (IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.Shared.RentCleared(replySize))
{
context.Memory.Write(replyPos, outputParcel);
}
Span<byte> outputParcel = outputParcelOwner.Memory.Span;
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
return result;
if (result == ResultCode.Success)
{
context.Memory.Write(replyPos, outputParcel);
}
return result;
}
}
protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type);

View File

@ -12,17 +12,7 @@ namespace Ryujinx.Input.SDL2
{
private bool HasConfiguration => _configuration != null;
private class ButtonMappingEntry
{
public readonly GamepadButtonInputId To;
public readonly GamepadButtonInputId From;
public ButtonMappingEntry(GamepadButtonInputId to, GamepadButtonInputId from)
{
To = to;
From = from;
}
}
private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From);
private StandardControllerInputConfig _configuration;
@ -85,7 +75,7 @@ namespace Ryujinx.Input.SDL2
public SDL2Gamepad(IntPtr gamepadHandle, string driverId)
{
_gamepadHandle = gamepadHandle;
_buttonsUserMapping = new List<ButtonMappingEntry>();
_buttonsUserMapping = new List<ButtonMappingEntry>(20);
Name = SDL_GameControllerName(_gamepadHandle);
Id = driverId;

View File

@ -96,6 +96,8 @@ namespace Ryujinx
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"Ryujinx Console {Version}";
// NOTE: GTK3 doesn't init X11 in a multi threaded way.
// This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads).
if (OperatingSystem.IsLinux())

View File

@ -1,6 +1,7 @@
using Gtk;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Models.Amiibo;
@ -12,7 +13,6 @@ using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AmiiboApi = Ryujinx.Ui.Common.Models.Amiibo.AmiiboApi;
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
@ -57,7 +57,7 @@ namespace Ryujinx.Ui.Windows
_httpClient = new HttpClient()
{
Timeout = TimeSpan.FromMilliseconds(5000)
Timeout = TimeSpan.FromSeconds(30)
};
Directory.CreateDirectory(System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
@ -93,8 +93,10 @@ namespace Ryujinx.Ui.Windows
{
amiiboJsonString = await DownloadAmiiboJson();
}
catch
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data: {ex}");
ShowInfoDialog();
Close();
@ -183,8 +185,10 @@ namespace Ryujinx.Ui.Windows
return false;
}
catch
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to check for amiibo updates: {ex}");
ShowInfoDialog();
return false;
@ -208,6 +212,8 @@ namespace Ryujinx.Ui.Windows
}
else
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}");
GtkDialog.CreateInfoDialog($"Amiibo API", "An error occured while fetching information from the API.");
Close();
@ -233,6 +239,10 @@ namespace Ryujinx.Ui.Windows
_amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, Gdk.InterpType.Bilinear);
}
else
{
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
}
}
private void ShowInfoDialog()
@ -374,4 +384,4 @@ namespace Ryujinx.Ui.Windows
base.Dispose(disposing);
}
}
}
}