diff --git a/Directory.Packages.props b/Directory.Packages.props index 455735fc4..c3af18cee 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,14 +13,14 @@ - + - + diff --git a/docs/README.md b/docs/README.md index 2213086f6..a22da3c7c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,8 +33,3 @@ Project Docs ================= To be added. Many project files will contain basic XML docs for key functions and classes in the meantime. - -Other Information -================= - -- N/A diff --git a/src/ARMeilleure/Instructions/InstEmitAlu32.cs b/src/ARMeilleure/Instructions/InstEmitAlu32.cs index 3a5e71bcc..028ffbeb1 100644 --- a/src/ARMeilleure/Instructions/InstEmitAlu32.cs +++ b/src/ARMeilleure/Instructions/InstEmitAlu32.cs @@ -19,6 +19,12 @@ namespace ARMeilleure.Instructions Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); + if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12) + { + // For ADR, PC is always 4 bytes aligned, even in Thumb mode. + n = context.BitwiseAnd(n, Const(~3u)); + } + Operand res = context.Add(n, m); if (ShouldSetFlags(context)) @@ -467,6 +473,12 @@ namespace ARMeilleure.Instructions Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); + if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12) + { + // For ADR, PC is always 4 bytes aligned, even in Thumb mode. + n = context.BitwiseAnd(n, Const(~3u)); + } + Operand res = context.Subtract(n, m); if (ShouldSetFlags(context)) diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs index 543aab023..13d9fac68 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions } else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) { - Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true); + // RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here. + + Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Rcpss, res); + res = EmitSse41Round32Exp8OpF(context, res, scalar: true); context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); } @@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions } else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) { - Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false); + // RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here. + + Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Rcpps, res); + res = EmitSse41Round32Exp8OpF(context, res, scalar: false); if (op.RegisterSize == RegisterSize.Simd64) { diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 6f6dfcadf..58f065342 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs index 05dd2162a..b95e5bed1 100644 --- a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs +++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; namespace Ryujinx.Audio.Backends.Common { @@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common private readonly object _lock = new(); - private byte[] _buffer; + private IMemoryOwner _bufferOwner; + private Memory _buffer; private int _size; private int _headOffset; private int _tailOffset; @@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) { - _buffer = new byte[initialCapacity]; + _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity); + _buffer = _bufferOwner.Memory; } public void Clear() @@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common public void Clear(int size) { + if (size == 0) + { + return; + } + lock (_lock) { if (size > _size) @@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common size = _size; } - if (size == 0) - { - return; - } - _headOffset = (_headOffset + size) % _buffer.Length; _size -= size; @@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common private void SetCapacityLocked(int capacity) { - byte[] buffer = new byte[capacity]; + IMemoryOwner newBufferOwner = ByteMemoryPool.RentCleared(capacity); + Memory newBuffer = newBufferOwner.Memory; if (_size > 0) { if (_headOffset < _tailOffset) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); + _buffer.Slice(_headOffset, _size).CopyTo(newBuffer); } else { - Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); - Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); + _buffer[_headOffset..].CopyTo(newBuffer); + _buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]); } } - _buffer = buffer; + _bufferOwner.Dispose(); + + _bufferOwner = newBufferOwner; + _buffer = newBuffer; _headOffset = 0; _tailOffset = _size; } - - public void Write(T[] buffer, int index, int count) + public void Write(ReadOnlySpan buffer, int index, int count) { if (count == 0) { @@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common if (tailLength >= count) { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } else { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); - Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); + buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]); + buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span); } } else { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } _size += count; @@ -117,8 +124,13 @@ namespace Ryujinx.Audio.Backends.Common } } - public int Read(T[] buffer, int index, int count) + public int Read(Span buffer, int index, int count) { + if (count == 0) + { + return 0; + } + lock (_lock) { if (count > _size) @@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common count = _size; } - if (count == 0) - { - return 0; - } - if (_headOffset < _tailOffset) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { @@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common if (tailLength >= count) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); - Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); + _buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]); + _buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]); } } diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs index b0963c935..3b8d15dc5 100644 --- a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common public ulong Flags; /// - /// Represents an error during . + /// Represents an error during . /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ErrorInfo diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs index 7efe3b02b..98b224ebf 100644 --- a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs +++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Common { /// - /// Update data header used for input and output of . + /// Update data header used for input and output of . /// public struct UpdateDataHeader { diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs index 5a0565dc6..72438be0e 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter /// /// Output information for behaviour. /// - /// This is used to report errors to the user during processing. + /// This is used to report errors to the user during processing. [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BehaviourErrorInfoOutStatus { diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs index 7bb8ae5ba..9b56f5cbd 100644 --- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server } } - public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlyMemory input) + public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlySequence input) { lock (_lock) { @@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server return result; } - result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); + PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + result = stateUpdater.UpdateVoices(_voiceContext, poolMapper); if (result != ResultCode.Success) { return result; } - result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); + result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper); if (result != ResultCode.Success) { @@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server return result; } - result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); + result = stateUpdater.UpdateSinks(_sinkContext, poolMapper); if (result != ResultCode.Success) { diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index 3297b5d9f..099d8f561 100644 --- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Diagnostics; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; @@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server } /// - /// Check if the audio renderer should trust the user destination count in . + /// Check if the audio renderer should trust the user destination count in . /// /// True if the audio renderer should trust the user destination count. public bool IsSplitterBugFixed() diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs index 57ca266f4..74a9baff2 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs index a9716db2a..77d9b5c29 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// /// The user parameter. /// Returns true if the sent by the user matches the internal . - public bool IsTypeValid(ref T parameter) where T : unmanaged, IEffectInParameter + public bool IsTypeValid(in T parameter) where T : unmanaged, IEffectInParameter { return parameter.Type == TargetEffectType; } @@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// Update the internal common parameters from a user parameter. /// /// The user parameter. - protected void UpdateParameterBase(ref T parameter) where T : unmanaged, IEffectInParameter + protected void UpdateParameterBase(in T parameter) where T : unmanaged, IEffectInParameter { MixId = parameter.MixId; ProcessingOrder = parameter.ProcessingOrder; @@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// /// Initialize the given result state. /// - /// The state to initalize + /// The state to initialize public virtual void InitializeResultState(ref EffectResultState state) { } /// @@ -155,9 +155,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// The possible that was generated. /// The user parameter. /// The mapper to use. - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); updateErrorInfo = new ErrorInfo(); } @@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// The possible that was generated. /// The user parameter. /// The mapper to use. - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); updateErrorInfo = new ErrorInfo(); } diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs index b987f7c85..3b3e1021c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BiquadFilter; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs index d6cb9cfa3..5d82b5ae8 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BufferMix; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs index 5be4b4ed5..6917222f0 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs @@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs index 826c32cb0..eff60e7da 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised. updateErrorInfo = new BehaviourParameter.ErrorInfo(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs index 43cabb7db..9db1ce465 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref DelayParameter delayParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; @@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (delayParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.Status; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs index 3e2f7326d..d9b3d5666 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; updateErrorInfo = new BehaviourParameter.ErrorInfo(); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = limiterParameter; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs index f9d7f4943..4b13cfec6 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; @@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (reverbParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.ParameterStatus; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs index 6fdf8fc23..aa6e67448 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; @@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (reverbParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.Status; diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs index 391b80f8d..f67d0c124 100644 --- a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs @@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool /// Input user parameter. /// Output user parameter. /// Returns the of the operations performed. - public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) { MemoryPoolUserState inputState = inParameter.State; diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs index 88ae44831..b90574da9 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// The input parameter of the mix. /// The splitter context. /// Return true, new connections were done on the adjacency matrix. - private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) + private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext) { bool hasNewConnections; @@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// The splitter context. /// The behaviour context. /// Return true if the mix was changed. - public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) { bool isDirty; @@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix if (behaviourContext.IsSplitterSupported()) { - isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); + isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext); } else { diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs index d36c5e260..8c65e09bc 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs @@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// /// The user parameter. /// Return true, if the sent by the user match the internal . - public bool IsTypeValid(ref SinkInParameter parameter) + public bool IsTypeValid(in SinkInParameter parameter) { return parameter.Type == TargetSinkType; } @@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// Update the internal common parameters from user parameter. /// /// The user parameter. - protected void UpdateStandardParameter(ref SinkInParameter parameter) + protected void UpdateStandardParameter(in SinkInParameter parameter) { if (IsUsed != parameter.IsUsed) { @@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// The user parameter. /// The user output status. /// The mapper to use. - public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); errorInfo = new ErrorInfo(); } diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs index 097757988..f2751cf29 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink public override SinkType TargetSinkType => SinkType.CircularBuffer; - public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { errorInfo = new BehaviourParameter.ErrorInfo(); outStatus = new SinkOutStatus(); - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; if (parameter.IsUsed != IsUsed || ShouldSkip) { - UpdateStandardParameter(ref parameter); + UpdateStandardParameter(in parameter); if (parameter.IsUsed) { diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs index e03fe11d4..afe2d4b1b 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs @@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink public override SinkType TargetSinkType => SinkType.Device; - public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; if (parameter.IsUsed != IsUsed) { - UpdateStandardParameter(ref parameter); + UpdateStandardParameter(in parameter); Parameter = inputDeviceParameter; } else diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs index e408692ab..3efa783c3 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -2,10 +2,11 @@ using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common; +using Ryujinx.Common.Extensions; using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Server.Splitter { @@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter private Memory _splitterDestinations; /// - /// If set to true, trust the user destination count in . + /// If set to true, trust the user destination count in . /// public bool IsBugFixed { get; private set; } @@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// /// The storage. /// The storage. - /// If set to true, trust the user destination count in . + /// If set to true, trust the user destination count in . private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed) { _splitters = splitters; @@ -148,11 +149,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// /// The splitter header. /// The raw data after the splitter header. - private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader input) { for (int i = 0; i < inputHeader.SplitterCount; i++) { - SplitterInParameter parameter = MemoryMarshal.Read(input); + ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy(out _); Debug.Assert(parameter.IsMagicValid()); @@ -162,10 +163,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { ref SplitterState splitter = ref GetState(parameter.Id); - splitter.Update(this, ref parameter, input[Unsafe.SizeOf()..]); + splitter.Update(this, in parameter, ref input); } - input = input[(0x1C + parameter.DestinationCount * 4)..]; + // NOTE: there are 12 bytes of unused/unknown data after the destination IDs array. + input.Advance(0xC); + } + else + { + input.Rewind(Unsafe.SizeOf()); + break; } } } @@ -175,11 +182,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// /// The splitter header. /// The raw data after the splitter header. - private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader input) { for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) { - SplitterDestinationInParameter parameter = MemoryMarshal.Read(input); + ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy(out _); Debug.Assert(parameter.IsMagicValid()); @@ -191,8 +198,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter destination.Update(parameter); } - - input = input[Unsafe.SizeOf()..]; + } + else + { + input.Rewind(Unsafe.SizeOf()); + break; } } } @@ -201,36 +211,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Update splitter from user parameters. /// /// The input raw user data. - /// The total consumed size. /// Return true if the update was successful. - public bool Update(ReadOnlySpan input, out int consumedSize) + public bool Update(ref SequenceReader input) { if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) { - consumedSize = 0; - return true; } - int originalSize = input.Length; - - SplitterInParameterHeader header = SpanIOHelper.Read(ref input); + ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy(out _); if (header.IsMagicValid()) { ClearAllNewConnectionFlag(); - UpdateState(ref header, ref input); - UpdateData(ref header, ref input); + UpdateState(in header, ref input); + UpdateData(in header, ref input); - consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); + input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10)); return true; } + else + { + input.Rewind(Unsafe.SizeOf()); - consumedSize = 0; - - return false; + return false; + } } /// diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs index e08ee9ea7..944f092d2 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -1,4 +1,5 @@ using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Extensions; using System; using System.Buffers; using System.Diagnostics; @@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// The splitter context. /// The user parameter. /// The raw input data after the . - public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan input) + public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader input) { ClearLinks(); @@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter if (destinationCount > 0) { - ReadOnlySpan destinationIds = MemoryMarshal.Cast(input); + input.ReadLittleEndian(out int destinationId); - Memory destination = context.GetDestinationMemory(destinationIds[0]); + Memory destination = context.GetDestinationMemory(destinationId); SetDestination(ref destination.Span[0]); @@ -149,13 +150,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter for (int i = 1; i < destinationCount; i++) { - Memory nextDestination = context.GetDestinationMemory(destinationIds[i]); + input.ReadLittleEndian(out destinationId); + + Memory nextDestination = context.GetDestinationMemory(destinationId); destination.Span[0].Link(ref nextDestination.Span[0]); destination = nextDestination; } } + if (destinationCount < parameter.DestinationCount) + { + input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int)); + } + Debug.Assert(parameter.Id == Id); if (parameter.Id == Id) diff --git a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs index 22eebc7cc..f8d87f2d1 100644 --- a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs +++ b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -9,41 +9,40 @@ using Ryujinx.Audio.Renderer.Server.Sink; using Ryujinx.Audio.Renderer.Server.Splitter; using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Extensions; using Ryujinx.Common.Logging; using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; namespace Ryujinx.Audio.Renderer.Server { - public class StateUpdater + public ref struct StateUpdater { - private readonly ReadOnlyMemory _inputOrigin; + private SequenceReader _inputReader; + private readonly ReadOnlyMemory _outputOrigin; - private ReadOnlyMemory _input; private Memory _output; private readonly uint _processHandle; private BehaviourContext _behaviourContext; - private UpdateDataHeader _inputHeader; + private readonly ref readonly UpdateDataHeader _inputHeader; private readonly Memory _outputHeader; - private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; - public StateUpdater(ReadOnlyMemory input, Memory output, uint processHandle, BehaviourContext behaviourContext) + public StateUpdater(ReadOnlySequence input, Memory output, uint processHandle, BehaviourContext behaviourContext) { - _input = input; - _inputOrigin = _input; + _inputReader = new SequenceReader(input); _output = output; _outputOrigin = _output; _processHandle = processHandle; _behaviourContext = behaviourContext; - _inputHeader = SpanIOHelper.Read(ref _input); + _inputHeader = ref _inputReader.GetRefOrRefToCopy(out _); _outputHeader = SpanMemoryManager.Cast(_output[..Unsafe.SizeOf()]); OutputHeader.Initialize(_behaviourContext.UserRevision); @@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server public ResultCode UpdateBehaviourContext() { - BehaviourParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) { @@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server foreach (ref MemoryPoolState memoryPool in memoryPools) { - MemoryPoolInParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; - PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus); + PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus); if (updateResult != PoolMapper.UpdateResult.Success && updateResult != PoolMapper.UpdateResult.MapError && @@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server for (int i = 0; i < context.GetCount(); i++) { - VoiceChannelResourceInParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref VoiceChannelResource resource = ref context.GetChannelResource(i); @@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - public ResultCode UpdateVoices(VoiceContext context, Memory memoryPools) + public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) { @@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.VoicesSize].Span); - - _input = _input[(int)_inputHeader.VoicesSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; // First make everything not in use. for (int i = 0; i < context.GetCount(); i++) @@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server // Start processing for (int i = 0; i < context.GetCount(); i++) { - VoiceInParameter parameter = parameters[i]; + ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); voiceUpdateStates.Fill(Memory.Empty); @@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server currentVoiceState.Initialize(); } - currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext); + currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext); if (updateParameterError.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateParameterError); } - currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext); + currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext); foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) { @@ -198,7 +193,7 @@ namespace Ryujinx.Audio.Renderer.Server } } - currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates); + currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates); } } @@ -211,10 +206,12 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize); + return ResultCode.Success; } - private static void ResetEffect(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + private static void ResetEffect(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { effect.ForceUnmapBuffers(mapper); @@ -234,17 +231,17 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (_behaviourContext.IsEffectInfoVersion2Supported()) { - return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); + return UpdateEffectsVersion2(context, isAudioRendererActive, mapper); } - return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); + return UpdateEffectsVersion1(context, isAudioRendererActive, mapper); } - public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { @@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.EffectsSize].Span); - - _input = _input[(int)_inputHeader.EffectsSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - EffectInParameterVersion2 parameter = parameters[i]; + ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); - if (!effect.IsTypeValid(ref parameter)) + if (!effect.IsTypeValid(in parameter)) { - ResetEffect(ref effect, ref parameter, mapper); + ResetEffect(ref effect, in parameter, mapper); } - effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + return ResultCode.Success; } - public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) { @@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.EffectsSize].Span); - - _input = _input[(int)_inputHeader.EffectsSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - EffectInParameterVersion1 parameter = parameters[i]; + ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); - if (!effect.IsTypeValid(ref parameter)) + if (!effect.IsTypeValid(in parameter)) { - ResetEffect(ref effect, ref parameter, mapper); + ResetEffect(ref effect, in parameter, mapper); } - effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + return ResultCode.Success; } public ResultCode UpdateSplitter(SplitterContext context) { - if (context.Update(_input.Span, out int consumedSize)) + if (context.Update(ref _inputReader)) { - _input = _input[consumedSize..]; - return ResultCode.Success; } return ResultCode.InvalidUpdateInfo; } - private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan parameters) + private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader parameters) { uint maxMixStateCount = mixContext.GetCount(); uint totalRequiredMixBufferCount = 0; for (int i = 0; i < inputMixCount; i++) { - if (parameters[i].IsUsed) + ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy(out _); + + if (parameter.IsUsed) { - if (parameters[i].DestinationMixId != Constants.UnusedMixId && - parameters[i].DestinationMixId > maxMixStateCount && - parameters[i].MixId != Constants.FinalMixId) + if (parameter.DestinationMixId != Constants.UnusedMixId && + parameter.DestinationMixId > maxMixStateCount && + parameter.MixId != Constants.FinalMixId) { return true; } - totalRequiredMixBufferCount += parameters[i].BufferCount; + totalRequiredMixBufferCount += parameter.BufferCount; } } @@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) { - MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast(_input.Span)[0]; + ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy(out _); mixCount = parameter.MixCount; @@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.InvalidUpdateInfo; } - if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) - { - _input = _input[Unsafe.SizeOf()..]; - } + long initialInputConsumed = _inputReader.Consumed; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Span[..(int)inputMixSize]); + int parameterCount = (int)inputMixSize / Unsafe.SizeOf(); - _input = _input[(int)inputMixSize..]; - - if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters)) + if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader)) { return ResultCode.InvalidUpdateInfo; } bool isMixContextDirty = false; - for (int i = 0; i < parameters.Length; i++) + for (int i = 0; i < parameterCount; i++) { - MixParameter parameter = parameters[i]; + ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); int mixId = i; @@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server if (mix.IsUsed) { - isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext); + isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext); } } @@ -473,10 +461,12 @@ namespace Ryujinx.Audio.Renderer.Server } } + _inputReader.SetConsumed(initialInputConsumed + inputMixSize); + return ResultCode.Success; } - private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter) + private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter) { sink.CleanUp(); @@ -489,10 +479,8 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public ResultCode UpdateSinks(SinkContext context, Memory memoryPools) + public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper) { - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); - if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) { return ResultCode.InvalidUpdateInfo; @@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.SinksSize].Span); - - _input = _input[(int)_inputHeader.SinksSize..]; + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - SinkInParameter parameter = parameters[i]; + ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; ref BaseSink sink = ref context.GetSink(i); - if (!sink.IsTypeValid(ref parameter)) + if (!sink.IsTypeValid(in parameter)) { - ResetSink(ref sink, ref parameter); + ResetSink(ref sink, in parameter); } - sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper); + sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize); + return ResultCode.Success; } @@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.InvalidUpdateInfo; } - PerformanceInParameter parameter = SpanIOHelper.Read(ref _input); + ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; @@ -585,9 +573,9 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - public ResultCode CheckConsumedSize() + public readonly ResultCode CheckConsumedSize() { - int consumedInputSize = _inputOrigin.Length - _input.Length; + long consumedInputSize = _inputReader.Consumed; int consumedOutputSize = _outputOrigin.Length - _output.Length; if (consumedInputSize != _inputHeader.TotalSize) diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs index 225f7d31b..040c70e6c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// /// The user parameter. /// Return true, if the server voice information needs to be updated. - private readonly bool ShouldUpdateParameters(ref VoiceInParameter parameter) + private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter) { if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) { @@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The user parameter. /// The mapper to use. /// The behaviour context. - public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) + public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext) { InUse = parameter.InUse; Id = parameter.Id; @@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice VoiceDropFlag = false; } - if (ShouldUpdateParameters(ref parameter)) + if (ShouldUpdateParameters(in parameter)) { DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); } @@ -380,7 +380,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The given user output. /// The user parameter. /// The voice states associated to the . - public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) + public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) { #if DEBUG // Sanity check in debug mode of the internal state @@ -426,7 +426,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The voice states associated to the . /// The mapper to use. /// The behaviour context. - public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + public void UpdateWaveBuffers( + out ErrorInfo[] errorInfos, + in VoiceInParameter parameter, + ReadOnlySpan> voiceUpdateStates, + PoolMapper mapper, + ref BehaviourContext behaviourContext) { errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; @@ -444,7 +449,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { - UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); + UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext); } } @@ -458,7 +463,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// If set to true, the server side wavebuffer is considered valid. /// The mapper to use. /// The behaviour context. - private void UpdateWaveBuffer(Span errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + private void UpdateWaveBuffer( + Span errorInfos, + ref WaveBuffer waveBuffer, + ref WaveBufferInternal inputWaveBuffer, + SampleFormat sampleFormat, + bool isValid, + PoolMapper mapper, + ref BehaviourContext behaviourContext) { if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) { diff --git a/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs new file mode 100644 index 000000000..5403c87c0 --- /dev/null +++ b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs @@ -0,0 +1,181 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Extensions +{ + public static class SequenceReaderExtensions + { + /// + /// Dumps the entire to a file, restoring its previous location afterward. + /// Useful for debugging purposes. + /// + /// The to write to a file + /// The path and name of the file to create and dump to + public static void DumpToFile(this ref SequenceReader reader, string fileFullName) + { + var initialConsumed = reader.Consumed; + + reader.Rewind(initialConsumed); + + using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None)) + { + while (reader.End == false) + { + var span = reader.CurrentSpan; + fileStream.Write(span); + reader.Advance(span.Length); + } + } + + reader.SetConsumed(initialConsumed); + } + + /// + /// Returns a reference to the desired value. This ref should always be used. The argument passed in should never be used, as this is only used for storage if the value + /// must be copied from multiple segments held by the . + /// + /// Type to get + /// The to read from + /// A location used as storage if (and only if) the value to be read spans multiple segments + /// A reference to the desired value, either directly to memory in the , or to if it has been used for copying the value in to + /// + /// DO NOT use after calling this method, as it will only + /// contain a value if the value couldn't be referenced directly because it spans multiple segments. + /// To discourage use, it is recommended to to call this method like the following: + /// + /// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _); + /// + /// + /// The does not contain enough data to read a value of type + public static ref readonly T GetRefOrRefToCopy(this scoped ref SequenceReader reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged + { + int lengthRequired = Unsafe.SizeOf(); + + ReadOnlySpan span = reader.UnreadSpan; + if (lengthRequired <= span.Length) + { + reader.Advance(lengthRequired); + + copyDestinationIfRequiredDoNotUse = default; + + ReadOnlySpan spanOfT = MemoryMarshal.Cast(span); + + return ref spanOfT[0]; + } + else + { + copyDestinationIfRequiredDoNotUse = default; + + Span valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1); + + Span valueBytesSpan = MemoryMarshal.AsBytes(valueSpan); + + if (!reader.TryCopyTo(valueBytesSpan)) + { + throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value."); + } + + reader.Advance(lengthRequired); + + return ref valueSpan[0]; + } + } + + /// + /// Reads an as little endian. + /// + /// The to read from + /// A location to receive the read value + /// Thrown if there wasn't enough data for an + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadLittleEndian(this ref SequenceReader reader, out int value) + { + if (!reader.TryReadLittleEndian(out value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); + } + } + + /// + /// Reads the desired unmanaged value by copying it to the specified . + /// + /// Type to read + /// The to read from + /// The target that will receive the read value + /// The does not contain enough data to read a value of type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadUnmanaged(this ref SequenceReader reader, out T value) where T : unmanaged + { + if (!reader.TryReadUnmanaged(out value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); + } + } + + /// + /// Sets the reader's position as bytes consumed. + /// + /// The to set the position + /// The number of bytes consumed + public static void SetConsumed(ref this SequenceReader reader, long consumed) + { + reader.Rewind(reader.Consumed); + reader.Advance(consumed); + } + + /// + /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary + /// structs - see remarks for full details. + /// + /// Type to read + /// + /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to + /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit + /// overloads such as + /// + /// + /// True if successful. will be default if failed (due to lack of space). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool TryReadUnmanaged(ref this SequenceReader reader, out T value) where T : unmanaged + { + ReadOnlySpan span = reader.UnreadSpan; + + if (span.Length < sizeof(T)) + { + return TryReadUnmanagedMultiSegment(ref reader, out value); + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); + + reader.Advance(sizeof(T)); + + return true; + } + + private static unsafe bool TryReadUnmanagedMultiSegment(ref SequenceReader reader, out T value) where T : unmanaged + { + Debug.Assert(reader.UnreadSpan.Length < sizeof(T)); + + // Not enough data in the current segment, try to peek for the data we need. + T buffer = default; + + Span tempSpan = new Span(&buffer, sizeof(T)); + + if (!reader.TryCopyTo(tempSpan)) + { + value = default; + return false; + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); + + reader.Advance(sizeof(T)); + + return true; + } + } +} diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs index df3f8dc93..05fb29ac7 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs @@ -4,7 +4,7 @@ using System.Threading; namespace Ryujinx.Common.Memory { - public sealed partial class ByteMemoryPool + public partial class ByteMemoryPool { /// /// Represents a that wraps an array rented from diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs index 071f56b13..6fd6a98aa 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs @@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory /// /// Provides a pool of re-usable byte array instances. /// - public sealed partial class ByteMemoryPool + public static partial class ByteMemoryPool { - private static readonly ByteMemoryPool _shared = new(); - - /// - /// Constructs a instance. Private to force access through - /// the instance. - /// - private ByteMemoryPool() - { - // No implementation - } - - /// - /// Retrieves a shared instance. - /// - public static ByteMemoryPool Shared => _shared; - /// /// Returns the maximum buffer size supported by this pool. /// @@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory return buffer; } + /// + /// Copies into a newly rented byte memory buffer. + /// + /// The byte buffer to copy + /// A wrapping the rented memory with copied to it + public static IMemoryOwner RentCopy(ReadOnlySpan buffer) + { + var copy = RentImpl(buffer.Length); + + buffer.CopyTo(copy.Memory.Span); + + return copy; + } + private static ByteMemoryPoolBuffer RentImpl(int length) { if ((uint)length > Array.MaxLength) diff --git a/src/Ryujinx.Common/Memory/SpanOrArray.cs b/src/Ryujinx.Common/Memory/SpanOrArray.cs deleted file mode 100644 index 269ac02fd..000000000 --- a/src/Ryujinx.Common/Memory/SpanOrArray.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; - -namespace Ryujinx.Common.Memory -{ - /// - /// A struct that can represent both a Span and Array. - /// This is useful to keep the Array representation when possible to avoid copies. - /// - /// Element Type - public readonly ref struct SpanOrArray where T : unmanaged - { - public readonly T[] Array; - public readonly ReadOnlySpan Span; - - /// - /// Create a new SpanOrArray from an array. - /// - /// Array to store - public SpanOrArray(T[] array) - { - Array = array; - Span = ReadOnlySpan.Empty; - } - - /// - /// Create a new SpanOrArray from a readonly span. - /// - /// Span to store - public SpanOrArray(ReadOnlySpan span) - { - Array = null; - Span = span; - } - - /// - /// Return the contained array, or convert the span if necessary. - /// - /// An array containing the data - public T[] ToArray() - { - return Array ?? Span.ToArray(); - } - - /// - /// Return a ReadOnlySpan from either the array or ReadOnlySpan. - /// - /// A ReadOnlySpan containing the data - public ReadOnlySpan AsSpan() - { - return Array ?? Span; - } - - /// - /// Cast an array to a SpanOrArray. - /// - /// Source array - public static implicit operator SpanOrArray(T[] array) - { - return new SpanOrArray(array); - } - - /// - /// Cast a ReadOnlySpan to a SpanOrArray. - /// - /// Source ReadOnlySpan - public static implicit operator SpanOrArray(ReadOnlySpan span) - { - return new SpanOrArray(span); - } - - /// - /// Cast a Span to a SpanOrArray. - /// - /// Source Span - public static implicit operator SpanOrArray(Span span) - { - return new SpanOrArray(span); - } - - /// - /// Cast a SpanOrArray to a ReadOnlySpan - /// - /// Source SpanOrArray - public static implicit operator ReadOnlySpan(SpanOrArray spanOrArray) - { - return spanOrArray.AsSpan(); - } - } -} diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs index a4facc2e3..e22571c96 100644 --- a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs +++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Utilities; using System; +using System.Buffers; using System.IO; using System.Linq; using System.Reflection; @@ -41,6 +42,22 @@ namespace Ryujinx.Common return StreamUtils.StreamToBytes(stream); } + public static IMemoryOwner ReadFileToRentedMemory(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadFileToRentedMemory(assembly, path); + } + + public static IMemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + + return stream is null + ? null + : StreamUtils.StreamToRentedMemory(stream); + } + public async static Task ReadAsync(Assembly assembly, string filename) { using var stream = GetStream(assembly, filename); diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs index 7a20c98e9..74b6af5ec 100644 --- a/src/Ryujinx.Common/Utilities/StreamUtils.cs +++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs @@ -1,4 +1,6 @@ +using Microsoft.IO; using Ryujinx.Common.Memory; +using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities { public static byte[] StreamToBytes(Stream input) { - using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + return output.ToArray(); + } - input.CopyTo(stream); + public static IMemoryOwner StreamToRentedMemory(Stream input) + { + if (input is MemoryStream inputMemoryStream) + { + return MemoryStreamToRentedMemory(inputMemoryStream); + } + else if (input.CanSeek) + { + long bytesExpected = input.Length; - return stream.ToArray(); + IMemoryOwner ownedMemory = ByteMemoryPool.Rent(bytesExpected); + + var destSpan = ownedMemory.Memory.Span; + + int totalBytesRead = 0; + + while (totalBytesRead < bytesExpected) + { + int bytesRead = input.Read(destSpan[totalBytesRead..]); + + if (bytesRead == 0) + { + ownedMemory.Dispose(); + + throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}."); + } + + totalBytesRead += bytesRead; + } + + return ownedMemory; + } + else + { + // If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner. + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + + return MemoryStreamToRentedMemory(output); + } } public static async Task StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) @@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities return stream.ToArray(); } + + private static IMemoryOwner MemoryStreamToRentedMemory(MemoryStream input) + { + input.Position = 0; + + IMemoryOwner ownedMemory = ByteMemoryPool.Rent(input.Length); + + // Discard the return value because we assume reading a MemoryStream always succeeds completely. + _ = input.Read(ownedMemory.Memory.Span); + + return ownedMemory; + } + + private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input) + { + RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream(); + + input.CopyTo(stream); + + return stream; + } } } diff --git a/src/Ryujinx.Cpu/AddressSpace.cs b/src/Ryujinx.Cpu/AddressSpace.cs index beea14bee..6664ed134 100644 --- a/src/Ryujinx.Cpu/AddressSpace.cs +++ b/src/Ryujinx.Cpu/AddressSpace.cs @@ -1,5 +1,3 @@ -using Ryujinx.Common; -using Ryujinx.Common.Collections; using Ryujinx.Memory; using System; @@ -7,175 +5,23 @@ namespace Ryujinx.Cpu { public class AddressSpace : IDisposable { - private const int DefaultBlockAlignment = 1 << 20; - - private enum MappingType : byte - { - None, - Private, - Shared, - } - - private class Mapping : IntrusiveRedBlackTreeNode, IComparable - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - public ulong EndAddress => Address + Size; - public MappingType Type { get; private set; } - - public Mapping(ulong address, ulong size, MappingType type) - { - Address = address; - Size = size; - Type = type; - } - - public Mapping Split(ulong splitAddress) - { - ulong leftSize = splitAddress - Address; - ulong rightSize = EndAddress - splitAddress; - - Mapping left = new(Address, leftSize, Type); - - Address = splitAddress; - Size = rightSize; - - return left; - } - - public void UpdateState(MappingType newType) - { - Type = newType; - } - - public void Extend(ulong sizeDelta) - { - Size += sizeDelta; - } - - public int CompareTo(Mapping other) - { - if (Address < other.Address) - { - return -1; - } - else if (Address <= other.EndAddress - 1UL) - { - return 0; - } - else - { - return 1; - } - } - } - - private class PrivateMapping : IntrusiveRedBlackTreeNode, IComparable - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - public ulong EndAddress => Address + Size; - public PrivateMemoryAllocation PrivateAllocation { get; private set; } - - public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation) - { - Address = address; - Size = size; - PrivateAllocation = privateAllocation; - } - - public PrivateMapping Split(ulong splitAddress) - { - ulong leftSize = splitAddress - Address; - ulong rightSize = EndAddress - splitAddress; - - (var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize); - - PrivateMapping left = new(Address, leftSize, leftAllocation); - - Address = splitAddress; - Size = rightSize; - - return left; - } - - public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation) - { - baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size); - mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size); - PrivateAllocation = newAllocation; - } - - public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock) - { - if (PrivateAllocation.IsValid) - { - baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size); - mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size); - PrivateAllocation.Dispose(); - } - - PrivateAllocation = default; - } - - public void Extend(ulong sizeDelta) - { - Size += sizeDelta; - } - - public int CompareTo(PrivateMapping other) - { - if (Address < other.Address) - { - return -1; - } - else if (Address <= other.EndAddress - 1UL) - { - return 0; - } - else - { - return 1; - } - } - } - private readonly MemoryBlock _backingMemory; - private readonly PrivateMemoryAllocator _privateMemoryAllocator; - private readonly IntrusiveRedBlackTree _mappingTree; - private readonly IntrusiveRedBlackTree _privateTree; - - private readonly object _treeLock; - - private readonly bool _supports4KBPages; public MemoryBlock Base { get; } public MemoryBlock Mirror { get; } public ulong AddressSpaceSize { get; } - public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize, bool supports4KBPages) + public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize) { - if (!supports4KBPages) - { - _privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap); - _mappingTree = new IntrusiveRedBlackTree(); - _privateTree = new IntrusiveRedBlackTree(); - _treeLock = new object(); - - _mappingTree.Add(new Mapping(0UL, addressSpaceSize, MappingType.None)); - _privateTree.Add(new PrivateMapping(0UL, addressSpaceSize, default)); - } - _backingMemory = backingMemory; - _supports4KBPages = supports4KBPages; Base = baseMemory; Mirror = mirrorMemory; AddressSpaceSize = addressSpaceSize; } - public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages, out AddressSpace addressSpace) + public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, out AddressSpace addressSpace) { addressSpace = null; @@ -193,7 +39,7 @@ namespace Ryujinx.Cpu { baseMemory = new MemoryBlock(addressSpaceSize, AsFlags); mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags); - addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize, supports4KBPages); + addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize); break; } @@ -209,289 +55,20 @@ namespace Ryujinx.Cpu public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) { - if (_supports4KBPages) - { - Base.MapView(_backingMemory, pa, va, size); - Mirror.MapView(_backingMemory, pa, va, size); - - return; - } - - lock (_treeLock) - { - ulong alignment = MemoryBlock.GetPageSize(); - bool isAligned = ((va | pa | size) & (alignment - 1)) == 0; - - if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned) - { - Update(va, pa, size, MappingType.Private); - } - else - { - // The update method assumes that shared mappings are already aligned. - - if (!flags.HasFlag(MemoryMapFlags.Private)) - { - if ((va & (alignment - 1)) != (pa & (alignment - 1))) - { - throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned."); - } - - ulong endAddress = va + size; - va = BitUtils.AlignDown(va, alignment); - pa = BitUtils.AlignDown(pa, alignment); - size = BitUtils.AlignUp(endAddress, alignment) - va; - } - - Update(va, pa, size, MappingType.Shared); - } - } + Base.MapView(_backingMemory, pa, va, size); + Mirror.MapView(_backingMemory, pa, va, size); } public void Unmap(ulong va, ulong size) { - if (_supports4KBPages) - { - Base.UnmapView(_backingMemory, va, size); - Mirror.UnmapView(_backingMemory, va, size); - - return; - } - - lock (_treeLock) - { - Update(va, 0UL, size, MappingType.None); - } - } - - private void Update(ulong va, ulong pa, ulong size, MappingType type) - { - Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None)); - - Update(map, va, pa, size, type); - } - - private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type) - { - ulong endAddress = va + size; - - for (; map != null; map = map.Successor) - { - if (map.Address < va) - { - _mappingTree.Add(map.Split(va)); - } - - if (map.EndAddress > endAddress) - { - Mapping newMap = map.Split(endAddress); - _mappingTree.Add(newMap); - map = newMap; - } - - switch (type) - { - case MappingType.None: - if (map.Type == MappingType.Shared) - { - ulong startOffset = map.Address - va; - ulong mapVa = va + startOffset; - ulong mapSize = Math.Min(size - startOffset, map.Size); - ulong mapEndAddress = mapVa + mapSize; - ulong alignment = MemoryBlock.GetPageSize(); - - mapVa = BitUtils.AlignDown(mapVa, alignment); - mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment); - - mapSize = mapEndAddress - mapVa; - - Base.UnmapView(_backingMemory, mapVa, mapSize); - Mirror.UnmapView(_backingMemory, mapVa, mapSize); - } - else - { - UnmapPrivate(va, size); - } - break; - case MappingType.Private: - if (map.Type == MappingType.Shared) - { - throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}."); - } - else - { - MapPrivate(va, size); - } - break; - case MappingType.Shared: - if (map.Type != MappingType.None) - { - throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}."); - } - else - { - ulong startOffset = map.Address - va; - ulong mapPa = pa + startOffset; - ulong mapVa = va + startOffset; - ulong mapSize = Math.Min(size - startOffset, map.Size); - - Base.MapView(_backingMemory, mapPa, mapVa, mapSize); - Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize); - } - break; - } - - map.UpdateState(type); - map = TryCoalesce(map); - - if (map.EndAddress >= endAddress) - { - break; - } - } - - return map; - } - - private Mapping TryCoalesce(Mapping map) - { - Mapping previousMap = map.Predecessor; - Mapping nextMap = map.Successor; - - if (previousMap != null && CanCoalesce(previousMap, map)) - { - previousMap.Extend(map.Size); - _mappingTree.Remove(map); - map = previousMap; - } - - if (nextMap != null && CanCoalesce(map, nextMap)) - { - map.Extend(nextMap.Size); - _mappingTree.Remove(nextMap); - } - - return map; - } - - private static bool CanCoalesce(Mapping left, Mapping right) - { - return left.Type == right.Type; - } - - private void MapPrivate(ulong va, ulong size) - { - ulong endAddress = va + size; - - ulong alignment = MemoryBlock.GetPageSize(); - - // Expand the range outwards based on page size to ensure that at least the requested region is mapped. - ulong vaAligned = BitUtils.AlignDown(va, alignment); - ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment); - - PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default)); - - for (; map != null; map = map.Successor) - { - if (!map.PrivateAllocation.IsValid) - { - if (map.Address < vaAligned) - { - _privateTree.Add(map.Split(vaAligned)); - } - - if (map.EndAddress > endAddressAligned) - { - PrivateMapping newMap = map.Split(endAddressAligned); - _privateTree.Add(newMap); - map = newMap; - } - - map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize())); - } - - if (map.EndAddress >= endAddressAligned) - { - break; - } - } - } - - private void UnmapPrivate(ulong va, ulong size) - { - ulong endAddress = va + size; - - ulong alignment = MemoryBlock.GetPageSize(); - - // Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use. - ulong vaAligned = BitUtils.AlignUp(va, alignment); - ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment); - - if (endAddressAligned <= vaAligned) - { - return; - } - - PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default)); - - for (; map != null; map = map.Successor) - { - if (map.PrivateAllocation.IsValid) - { - if (map.Address < vaAligned) - { - _privateTree.Add(map.Split(vaAligned)); - } - - if (map.EndAddress > endAddressAligned) - { - PrivateMapping newMap = map.Split(endAddressAligned); - _privateTree.Add(newMap); - map = newMap; - } - - map.Unmap(Base, Mirror); - map = TryCoalesce(map); - } - - if (map.EndAddress >= endAddressAligned) - { - break; - } - } - } - - private PrivateMapping TryCoalesce(PrivateMapping map) - { - PrivateMapping previousMap = map.Predecessor; - PrivateMapping nextMap = map.Successor; - - if (previousMap != null && CanCoalesce(previousMap, map)) - { - previousMap.Extend(map.Size); - _privateTree.Remove(map); - map = previousMap; - } - - if (nextMap != null && CanCoalesce(map, nextMap)) - { - map.Extend(nextMap.Size); - _privateTree.Remove(nextMap); - } - - return map; - } - - private static bool CanCoalesce(PrivateMapping left, PrivateMapping right) - { - return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid; + Base.UnmapView(_backingMemory, va, size); + Mirror.UnmapView(_backingMemory, va, size); } public void Dispose() { GC.SuppressFinalize(this); - _privateMemoryAllocator?.Dispose(); Base.Dispose(); Mirror.Dispose(); } diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 80f7c8a1f..abdddb31c 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -3,10 +3,10 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv @@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.AppleHv /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// [SupportedOSPlatform("macos")] - public class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private readonly InvalidAccessHandler _invalidAccessHandler; @@ -28,7 +28,7 @@ namespace Ryujinx.Cpu.AppleHv private readonly ManagedPageFlags _pages; - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; public int AddressSpaceBits { get; } @@ -96,12 +96,6 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -126,20 +120,11 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - /// - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -152,7 +137,6 @@ namespace Ryujinx.Cpu.AppleHv } } - /// public override void Read(ulong va, Span data) { try @@ -168,101 +152,11 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - /// - public void Write(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - SignalMemoryTracking(va, (ulong)data.Length, true); - - WriteImpl(va, data); - } - - /// - public void WriteUntracked(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - WriteImpl(va, data); - } - - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return false; - } - - SignalMemoryTracking(va, (ulong)data.Length, false); - - if (IsContiguousAndMapped(va, data.Length)) - { - var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length); - - bool changed = !data.SequenceEqual(target); - - if (changed) - { - data.CopyTo(target); - } - - return changed; - } - else - { - WriteImpl(va, data); - - return true; - } - } - - private void WriteImpl(ulong va, ReadOnlySpan data) + public override void Write(ulong va, ReadOnlySpan data) { try { - AssertValidAddressAndSize(va, (ulong)data.Length); - - if (IsContiguousAndMapped(va, data.Length)) - { - data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); - } - else - { - int offset = 0, size; - - if ((va & PageMask) != 0) - { - ulong pa = GetPhysicalAddressChecked(va); - - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - data[..size].CopyTo(_backingMemory.GetSpan(pa, size)); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - ulong pa = GetPhysicalAddressChecked(va + (ulong)offset); - - size = Math.Min(data.Length - offset, PageSize); - - data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size)); - } - } + base.Write(va, data); } catch (InvalidMemoryRegionException) { @@ -273,61 +167,38 @@ namespace Ryujinx.Cpu.AppleHv } } - /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public override void WriteUntracked(ulong va, ReadOnlySpan data) { - if (size == 0) + try { - return ReadOnlySpan.Empty; + base.WriteUntracked(va, data); } - - if (tracked) + catch (InvalidMemoryRegionException) { - SignalMemoryTracking(va, (ulong)size, false); - } - - if (IsContiguousAndMapped(va, size)) - { - return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); - } - else - { - Span data = new byte[size]; - - base.Read(va, data); - - return data; + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } } } - /// - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) { - if (size == 0) + try { - return new WritableRegion(null, va, Memory.Empty); + return base.GetReadOnlySequence(va, size, tracked); } - - if (tracked) + catch (InvalidMemoryRegionException) { - SignalMemoryTracking(va, (ulong)size, true); - } + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } - if (IsContiguousAndMapped(va, size)) - { - return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); - } - else - { - Memory memory = new byte[size]; - - base.Read(va, memory.Span); - - return new WritableRegion(this, va, memory); + return ReadOnlySequence.Empty; } } - /// public ref T GetRef(ulong va) where T : unmanaged { if (!IsContiguous(va, Unsafe.SizeOf())) @@ -340,9 +211,8 @@ namespace Ryujinx.Cpu.AppleHv return ref _backingMemory.GetRef(GetPhysicalAddressChecked(va)); } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { return ValidateAddress(va) && _pages.IsMapped(va); } @@ -355,39 +225,6 @@ namespace Ryujinx.Cpu.AppleHv return _pages.IsRangeMapped(va, size); } - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguous(ulong va, int size) - { - if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) - { - return false; - } - - int pages = GetPagesCount(va, (uint)size, out va); - - for (int page = 0; page < pages - 1; page++) - { - if (!ValidateAddress(va + PageSize)) - { - return false; - } - - if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) - { - return false; - } - - va += PageSize; - } - - return true; - } - /// public IEnumerable GetHostRegions(ulong va, ulong size) { @@ -464,11 +301,10 @@ namespace Ryujinx.Cpu.AppleHv return regions; } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -481,24 +317,6 @@ namespace Ryujinx.Cpu.AppleHv _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, ulong size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - - /// public void Reprotect(ulong va, ulong size, MemoryPermission protection) { // TODO @@ -535,7 +353,7 @@ namespace Ryujinx.Cpu.AppleHv return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - private ulong GetPhysicalAddressChecked(ulong va) + private nuint GetPhysicalAddressChecked(ulong va) { if (!IsMapped(va)) { @@ -545,9 +363,9 @@ namespace Ryujinx.Cpu.AppleHv return GetPhysicalAddressInternal(va); } - private ulong GetPhysicalAddressInternal(ulong va) + private nuint GetPhysicalAddressInternal(ulong va) { - return _pageTable.Read(va) + (va & PageMask); + return (nuint)(_pageTable.Read(va) + (va & PageMask)); } /// @@ -558,10 +376,17 @@ namespace Ryujinx.Cpu.AppleHv _addressSpace.Dispose(); } - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _backingMemory.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) + protected override nuint TranslateVirtualAddressChecked(ulong va) => GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => GetPhysicalAddressInternal(va); + } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index c87c8b8cc..6f594ec2f 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -3,6 +3,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -14,7 +15,7 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager. /// - public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private const int PteSize = 8; @@ -24,7 +25,7 @@ namespace Ryujinx.Cpu.Jit private readonly InvalidAccessHandler _invalidAccessHandler; /// - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; /// /// Address space width in bits. @@ -97,12 +98,6 @@ namespace Ryujinx.Cpu.Jit Tracking.Map(oVa, size); } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -128,20 +123,11 @@ namespace Ryujinx.Cpu.Jit } } - /// - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - /// - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -190,117 +176,11 @@ namespace Ryujinx.Cpu.Jit } } - /// - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - /// - public void Write(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - SignalMemoryTracking(va, (ulong)data.Length, true); - - WriteImpl(va, data); - } - - /// - public void WriteGuest(ulong va, T value) where T : unmanaged - { - Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); - - SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); - - WriteImpl(va, data); - } - - /// - public void WriteUntracked(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - WriteImpl(va, data); - } - - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return false; - } - - SignalMemoryTracking(va, (ulong)data.Length, false); - - if (IsContiguousAndMapped(va, data.Length)) - { - var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length); - - bool changed = !data.SequenceEqual(target); - - if (changed) - { - data.CopyTo(target); - } - - return changed; - } - else - { - WriteImpl(va, data); - - return true; - } - } - - /// - /// Writes data to CPU mapped memory. - /// - /// Virtual address to write the data into - /// Data to be written - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteImpl(ulong va, ReadOnlySpan data) + public override void Write(ulong va, ReadOnlySpan data) { try { - AssertValidAddressAndSize(va, (ulong)data.Length); - - if (IsContiguousAndMapped(va, data.Length)) - { - data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); - } - else - { - int offset = 0, size; - - if ((va & PageMask) != 0) - { - ulong pa = GetPhysicalAddressInternal(va); - - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - data[..size].CopyTo(_backingMemory.GetSpan(pa, size)); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); - - size = Math.Min(data.Length - offset, PageSize); - - data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size)); - } - } + base.Write(va, data); } catch (InvalidMemoryRegionException) { @@ -312,60 +192,47 @@ namespace Ryujinx.Cpu.Jit } /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public void WriteGuest(ulong va, T value) where T : unmanaged { - if (size == 0) + Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); + + SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); + + Write(va, data); + } + + public override void WriteUntracked(ulong va, ReadOnlySpan data) + { + try { - return ReadOnlySpan.Empty; + base.WriteUntracked(va, data); } - - if (tracked) + catch (InvalidMemoryRegionException) { - SignalMemoryTracking(va, (ulong)size, false); - } - - if (IsContiguousAndMapped(va, size)) - { - return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); - } - else - { - Span data = new byte[size]; - - base.Read(va, data); - - return data; + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } } } - /// - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) { - if (size == 0) + try { - return new WritableRegion(null, va, Memory.Empty); + return base.GetReadOnlySequence(va, size, tracked); } - - if (IsContiguousAndMapped(va, size)) + catch (InvalidMemoryRegionException) { - if (tracked) + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) { - SignalMemoryTracking(va, (ulong)size, true); + throw; } - return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); - } - else - { - Memory memory = new byte[size]; - - GetSpan(va, size).CopyTo(memory.Span); - - return new WritableRegion(this, va, memory, tracked); + return ReadOnlySequence.Empty; } } - /// public ref T GetRef(ulong va) where T : unmanaged { if (!IsContiguous(va, Unsafe.SizeOf())) @@ -378,56 +245,6 @@ namespace Ryujinx.Cpu.Jit return ref _backingMemory.GetRef(GetPhysicalAddressInternal(va)); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, uint size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguous(ulong va, int size) - { - if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) - { - return false; - } - - int pages = GetPagesCount(va, (uint)size, out va); - - for (int page = 0; page < pages - 1; page++) - { - if (!ValidateAddress(va + PageSize)) - { - return false; - } - - if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) - { - return false; - } - - va += PageSize; - } - - return true; - } - /// public IEnumerable GetHostRegions(ulong va, ulong size) { @@ -532,9 +349,8 @@ namespace Ryujinx.Cpu.Jit return true; } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { if (!ValidateAddress(va)) { @@ -544,9 +360,9 @@ namespace Ryujinx.Cpu.Jit return _pageTable.Read((va / PageSize) * PteSize) != 0; } - private ulong GetPhysicalAddressInternal(ulong va) + private nuint GetPhysicalAddressInternal(ulong va) { - return PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); + return (nuint)(PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask)); } /// @@ -643,9 +459,7 @@ namespace Ryujinx.Cpu.Jit { ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); - long pte; - - pte = Volatile.Read(ref pageRef); + long pte = Volatile.Read(ref pageRef); if ((pte & tag) != 0) { @@ -663,7 +477,7 @@ namespace Ryujinx.Cpu.Jit } /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId); } @@ -683,10 +497,16 @@ namespace Ryujinx.Cpu.Jit /// protected override void Destroy() => _pageTable.Dispose(); - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _backingMemory.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetPhysicalAddressInternal(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) => GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index f410d02e9..4639ab913 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -3,6 +3,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -12,7 +13,7 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private readonly InvalidAccessHandler _invalidAccessHandler; private readonly bool _unsafeMode; @@ -26,7 +27,7 @@ namespace Ryujinx.Cpu.Jit private readonly ManagedPageFlags _pages; /// - public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; + public bool UsesPrivateAllocations => false; public int AddressSpaceBits { get; } @@ -96,12 +97,6 @@ namespace Ryujinx.Cpu.Jit Tracking.Map(va, size); } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -138,8 +133,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public T Read(ulong va) where T : unmanaged + public override T Read(ulong va) { try { @@ -158,14 +152,11 @@ namespace Ryujinx.Cpu.Jit } } - /// - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -178,7 +169,6 @@ namespace Ryujinx.Cpu.Jit } } - /// public override void Read(ulong va, Span data) { try @@ -196,9 +186,7 @@ namespace Ryujinx.Cpu.Jit } } - - /// - public void Write(ulong va, T value) where T : unmanaged + public override void Write(ulong va, T value) { try { @@ -215,8 +203,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public void Write(ulong va, ReadOnlySpan data) + public override void Write(ulong va, ReadOnlySpan data) { try { @@ -233,8 +220,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public void WriteUntracked(ulong va, ReadOnlySpan data) + public override void WriteUntracked(ulong va, ReadOnlySpan data) { try { @@ -251,8 +237,7 @@ namespace Ryujinx.Cpu.Jit } } - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) { try { @@ -279,8 +264,21 @@ namespace Ryujinx.Cpu.Jit } } - /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, write: false); + } + else + { + AssertMapped(va, (ulong)size); + } + + return new ReadOnlySequence(_addressSpace.Mirror.GetMemory(va, size)); + } + + public override ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { if (tracked) { @@ -294,8 +292,7 @@ namespace Ryujinx.Cpu.Jit return _addressSpace.Mirror.GetSpan(va, size); } - /// - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (tracked) { @@ -309,7 +306,6 @@ namespace Ryujinx.Cpu.Jit return _addressSpace.Mirror.GetWritableRegion(va, size); } - /// public ref T GetRef(ulong va) where T : unmanaged { SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); @@ -317,9 +313,8 @@ namespace Ryujinx.Cpu.Jit return ref _addressSpace.Mirror.GetRef(va); } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { return ValidateAddress(va) && _pages.IsMapped(va); } @@ -390,11 +385,10 @@ namespace Ryujinx.Cpu.Jit return _pageTable.Read(va) + (va & PageMask); } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -407,23 +401,6 @@ namespace Ryujinx.Cpu.Jit _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, ulong size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - /// public void Reprotect(ulong va, ulong size, MemoryPermission protection) { @@ -470,10 +447,16 @@ namespace Ryujinx.Cpu.Jit _memoryEh.Dispose(); } - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _addressSpace.Mirror.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _addressSpace.Mirror.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) - => va; + protected override nuint TranslateVirtualAddressChecked(ulong va) + => (nuint)GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => (nuint)GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index 18404bcc7..0acb57be4 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -1,21 +1,22 @@ using ARMeilleure.Memory; +using Ryujinx.Common.Memory; using Ryujinx.Cpu.Jit.HostTracked; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Cpu.Jit { /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IWritableBlock, IMemoryManager, IVirtualMemoryManagerTracked + public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked { private readonly InvalidAccessHandler _invalidAccessHandler; private readonly bool _unsafeMode; @@ -34,7 +35,7 @@ namespace Ryujinx.Cpu.Jit protected override ulong AddressSpaceSize { get; } /// - public bool Supports4KBPages => false; + public bool UsesPrivateAllocations => true; public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; @@ -100,12 +101,6 @@ namespace Ryujinx.Cpu.Jit Tracking.Map(va, size); } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) - { - throw new NotSupportedException(); - } - /// public void Unmap(ulong va, ulong size) { @@ -120,18 +115,11 @@ namespace Ryujinx.Cpu.Jit _nativePageTable.Unmap(va, size); } - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - public T ReadTracked(ulong va) where T : unmanaged + public override T ReadTracked(ulong va) { try { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - - return Read(va); + return base.ReadTracked(va); } catch (InvalidMemoryRegionException) { @@ -145,38 +133,39 @@ namespace Ryujinx.Cpu.Jit } public override void Read(ulong va, Span data) - { - ReadImpl(va, data); - } - - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - public void Write(ulong va, ReadOnlySpan data) { if (data.Length == 0) { return; } - SignalMemoryTracking(va, (ulong)data.Length, true); - - WriteImpl(va, data); - } - - public void WriteUntracked(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) + try { - return; - } + AssertValidAddressAndSize(va, (ulong)data.Length); - WriteImpl(va, data); + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } } - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) { if (data.Length == 0) { @@ -206,35 +195,7 @@ namespace Ryujinx.Cpu.Jit } } - private void WriteImpl(ulong va, ReadOnlySpan data) - { - try - { - AssertValidAddressAndSize(va, (ulong)data.Length); - - ulong endVa = va + (ulong)data.Length; - int offset = 0; - - while (va < endVa) - { - (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); - - data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); - - va += copySize; - offset += (int)copySize; - } - } - catch (InvalidMemoryRegionException) - { - if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) - { - throw; - } - } - } - - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + public override ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { if (size == 0) { @@ -254,13 +215,13 @@ namespace Ryujinx.Cpu.Jit { Span data = new byte[size]; - ReadImpl(va, data); + Read(va, data); return data; } } - public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (size == 0) { @@ -278,11 +239,11 @@ namespace Ryujinx.Cpu.Jit } else { - Memory memory = new byte[size]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); - ReadImpl(va, memory.Span); + Read(va, memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory); + return new WritableRegion(this, va, memoryOwner); } } @@ -299,7 +260,7 @@ namespace Ryujinx.Cpu.Jit } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { return ValidateAddress(va) && _pages.IsMapped(va); } @@ -311,8 +272,6 @@ namespace Ryujinx.Cpu.Jit return _pages.IsRangeMapped(va, size); } - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset) { if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range)) @@ -491,44 +450,11 @@ namespace Ryujinx.Cpu.Jit return regions; } - private void ReadImpl(ulong va, Span data) - { - if (data.Length == 0) - { - return; - } - - try - { - AssertValidAddressAndSize(va, (ulong)data.Length); - - ulong endVa = va + (ulong)data.Length; - int offset = 0; - - while (va < endVa) - { - (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); - - memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); - - va += copySize; - offset += (int)copySize; - } - } - catch (InvalidMemoryRegionException) - { - if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) - { - throw; - } - } - } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -543,23 +469,6 @@ namespace Ryujinx.Cpu.Jit _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } - /// - /// Computes the number of pages in a virtual address range. - /// - /// Virtual address of the range - /// Size of the range - /// The virtual address of the beginning of the first page - /// This function does not differentiate between allocated and unallocated pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetPagesCount(ulong va, ulong size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { return Tracking.BeginTracking(address, size, id, flags); @@ -618,10 +527,44 @@ namespace Ryujinx.Cpu.Jit _nativePageTable.Dispose(); } - protected override Span GetPhysicalAddressSpan(ulong pa, int size) + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) => _backingMemory.GetSpan(pa, size); - protected override ulong TranslateVirtualAddressForRead(ulong va) - => GetPhysicalAddressInternal(va); + protected override void WriteImpl(ulong va, ReadOnlySpan data) + { + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => (nuint)GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => (nuint)GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs index c2d8cfb1a..3c7b33805 100644 --- a/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs +++ b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs @@ -1,13 +1,10 @@ using Ryujinx.Memory; using System.Diagnostics; -using System.Numerics; using System.Threading; namespace Ryujinx.Cpu { - public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted - where TVirtual : IBinaryInteger - where TPhysical : IBinaryInteger + public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted { private int _referenceCount; diff --git a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs index d64ed3095..fc075a264 100644 --- a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs +++ b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs @@ -1,5 +1,7 @@ +using Ryujinx.Common.Memory; using Ryujinx.Memory; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -143,11 +145,11 @@ namespace Ryujinx.Graphics.Device } else { - Memory memory = new byte[size]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); - GetSpan(va, size).CopyTo(memory.Span); + GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory, tracked: true); + return new WritableRegion(this, va, memoryOwner, tracked: true); } } diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs new file mode 100644 index 000000000..30cff50b1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public interface IImageArray + { + void SetFormats(int index, Format[] imageFormats); + void SetImages(int index, ITexture[] images); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index 3ba084aa5..9efb9e3e8 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.GAL void SetIndexBuffer(BufferRange buffer, IndexType type); void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); + void SetImageArray(ShaderStage stage, int binding, IImageArray array); void SetLineParameters(float width, bool smooth); @@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.GAL void SetStorageBuffers(ReadOnlySpan buffers); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); + void SetTextureArray(ShaderStage stage, int binding, ITextureArray array); void SetTransformFeedbackBuffers(ReadOnlySpan buffers); void SetUniformBuffers(ReadOnlySpan buffers); diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs index 3bf56465e..a3466e396 100644 --- a/src/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs @@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.GAL BufferHandle CreateBuffer(nint pointer, int size); BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers); + IImageArray CreateImageArray(int size, bool isBuffer); + IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); ISampler CreateSampler(SamplerCreateInfo info); ITexture CreateTexture(TextureCreateInfo info); + ITextureArray CreateTextureArray(int size, bool isBuffer); + bool PrepareHostMapping(nint address, ulong size); void CreateSync(ulong id, bool strict); diff --git a/src/Ryujinx.Graphics.GAL/ITexture.cs b/src/Ryujinx.Graphics.GAL/ITexture.cs index 5a4623a66..2d9c6b799 100644 --- a/src/Ryujinx.Graphics.GAL/ITexture.cs +++ b/src/Ryujinx.Graphics.GAL/ITexture.cs @@ -1,4 +1,4 @@ -using Ryujinx.Common.Memory; +using System.Buffers; namespace Ryujinx.Graphics.GAL { @@ -17,10 +17,34 @@ namespace Ryujinx.Graphics.GAL PinnedSpan GetData(); PinnedSpan GetData(int layer, int level); - void SetData(SpanOrArray data); - void SetData(SpanOrArray data, int layer, int level); - void SetData(SpanOrArray data, int layer, int level, Rectangle region); + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + void SetData(IMemoryOwner data); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + /// Target layer + /// Target level + void SetData(IMemoryOwner data, int layer, int level); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + /// Target layer + /// Target level + /// Target sub-region of the texture to update + void SetData(IMemoryOwner data, int layer, int level, Rectangle region); + void SetStorage(BufferRange buffer); + void Release(); } } diff --git a/src/Ryujinx.Graphics.GAL/ITextureArray.cs b/src/Ryujinx.Graphics.GAL/ITextureArray.cs new file mode 100644 index 000000000..35c2116b5 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ITextureArray.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public interface ITextureArray + { + void SetSamplers(int index, ISampler[] samplers); + void SetTextures(int index, ITexture[] textures); + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index 5bf3d3283..fd2919be4 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -1,10 +1,12 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands; using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; +using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray; using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray; using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; using System; using System.Linq; @@ -46,10 +48,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.CreateBufferAccess); Register(CommandType.CreateBufferSparse); Register(CommandType.CreateHostBuffer); + Register(CommandType.CreateImageArray); Register(CommandType.CreateProgram); Register(CommandType.CreateSampler); Register(CommandType.CreateSync); Register(CommandType.CreateTexture); + Register(CommandType.CreateTextureArray); Register(CommandType.GetCapabilities); Register(CommandType.PreFrame); Register(CommandType.ReportCounter); @@ -63,6 +67,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.CounterEventDispose); Register(CommandType.CounterEventFlush); + Register(CommandType.ImageArraySetFormats); + Register(CommandType.ImageArraySetImages); + Register(CommandType.ProgramDispose); Register(CommandType.ProgramGetBinary); Register(CommandType.ProgramCheckLink); @@ -82,6 +89,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.TextureSetDataSliceRegion); Register(CommandType.TextureSetStorage); + Register(CommandType.TextureArraySetSamplers); + Register(CommandType.TextureArraySetTextures); + Register(CommandType.WindowPresent); Register(CommandType.Barrier); @@ -114,6 +124,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.SetTransformFeedbackBuffers); Register(CommandType.SetUniformBuffers); Register(CommandType.SetImage); + Register(CommandType.SetImageArray); Register(CommandType.SetIndexBuffer); Register(CommandType.SetLineParameters); Register(CommandType.SetLogicOpState); @@ -130,6 +141,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.SetScissor); Register(CommandType.SetStencilTest); Register(CommandType.SetTextureAndSampler); + Register(CommandType.SetTextureArray); Register(CommandType.SetUserClipDistance); Register(CommandType.SetVertexAttribs); Register(CommandType.SetVertexBuffers); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index 6be639253..a5e7336cd 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -7,10 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading CreateBufferAccess, CreateBufferSparse, CreateHostBuffer, + CreateImageArray, CreateProgram, CreateSampler, CreateSync, CreateTexture, + CreateTextureArray, GetCapabilities, Unused, PreFrame, @@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading CounterEventDispose, CounterEventFlush, + ImageArraySetFormats, + ImageArraySetImages, + ProgramDispose, ProgramGetBinary, ProgramCheckLink, @@ -44,6 +49,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading TextureSetDataSliceRegion, TextureSetStorage, + TextureArraySetSamplers, + TextureArraySetTextures, + WindowPresent, Barrier, @@ -76,6 +84,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading SetTransformFeedbackBuffers, SetUniformBuffers, SetImage, + SetImageArray, SetIndexBuffer, SetLineParameters, SetLogicOpState, @@ -92,6 +101,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading SetScissor, SetStencilTest, SetTextureAndSampler, + SetTextureArray, SetUserClipDistance, SetVertexAttribs, SetVertexBuffers, diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs new file mode 100644 index 000000000..8e3ba88a8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArraySetFormats; + private TableRef _imageArray; + private int _index; + private TableRef _imageFormats; + + public void Set(TableRef imageArray, int index, TableRef imageFormats) + { + _imageArray = imageArray; + _index = index; + _imageFormats = imageFormats; + } + + public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedImageArray imageArray = command._imageArray.Get(threaded); + imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs new file mode 100644 index 000000000..cc28d585c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArraySetImagesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArraySetImages; + private TableRef _imageArray; + private int _index; + private TableRef _images; + + public void Set(TableRef imageArray, int index, TableRef images) + { + _imageArray = imageArray; + _index = index; + _images = images; + } + + public static void Run(ref ImageArraySetImagesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedImageArray imageArray = command._imageArray.Get(threaded); + imageArray.Base.SetImages(command._index, command._images.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs new file mode 100644 index 000000000..1c3fb8120 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateImageArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateImageArray; + private TableRef _imageArray; + private int _size; + private bool _isBuffer; + + public void Set(TableRef imageArray, int size, bool isBuffer) + { + _imageArray = imageArray; + _size = size; + _isBuffer = isBuffer; + } + + public static void Run(ref CreateImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._imageArray.Get(threaded).Base = renderer.CreateImageArray(command._size, command._isBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs new file mode 100644 index 000000000..9bd891e68 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateTextureArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateTextureArray; + private TableRef _textureArray; + private int _size; + private bool _isBuffer; + + public void Set(TableRef textureArray, int size, bool isBuffer) + { + _textureArray = textureArray; + _size = size; + _isBuffer = isBuffer; + } + + public static void Run(ref CreateTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._textureArray.Get(threaded).Base = renderer.CreateTextureArray(command._size, command._isBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs new file mode 100644 index 000000000..b8d3c7ac5 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetImageArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetImageArray; + private ShaderStage _stage; + private int _binding; + private TableRef _array; + + public void Set(ShaderStage stage, int binding, TableRef array) + { + _stage = stage; + _binding = binding; + _array = array; + } + + public static void Run(ref SetImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetImageArray(command._stage, command._binding, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs new file mode 100644 index 000000000..45e28aa65 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTextureArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetTextureArray; + private ShaderStage _stage; + private int _binding; + private TableRef _array; + + public void Set(ShaderStage stage, int binding, TableRef array) + { + _stage = stage; + _binding = binding; + _array = array; + } + + public static void Run(ref SetTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetTextureArray(command._stage, command._binding, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs index 36feeeba7..3aba004df 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs @@ -1,6 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { @@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { public readonly CommandType CommandType => CommandType.TextureSetData; private TableRef _texture; - private TableRef _data; + private TableRef> _data; - public void Set(TableRef texture, TableRef data) + public void Set(TableRef texture, TableRef> data) { _texture = texture; _data = data; @@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) { ThreadedTexture texture = command._texture.Get(threaded); - texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded))); + texture.Base.SetData(command._data.Get(threaded)); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs index c50bfe089..7ad709a75 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs @@ -1,6 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { @@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { public readonly CommandType CommandType => CommandType.TextureSetDataSlice; private TableRef _texture; - private TableRef _data; + private TableRef> _data; private int _layer; private int _level; - public void Set(TableRef texture, TableRef data, int layer, int level) + public void Set(TableRef texture, TableRef> data, int layer, int level) { _texture = texture; _data = data; @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) { ThreadedTexture texture = command._texture.Get(threaded); - texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded)), command._layer, command._level); + texture.Base.SetData(command._data.Get(threaded), command._layer, command._level); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs index 4c80d9bc3..c211931bc 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs @@ -1,6 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { @@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture { public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion; private TableRef _texture; - private TableRef _data; + private TableRef> _data; private int _layer; private int _level; private Rectangle _region; - public void Set(TableRef texture, TableRef data, int layer, int level, Rectangle region) + public void Set(TableRef texture, TableRef> data, int layer, int level, Rectangle region) { _texture = texture; _data = data; @@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer) { ThreadedTexture texture = command._texture.Get(threaded); - texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded)), command._layer, command._level, command._region); + texture.Base.SetData(command._data.Get(threaded), command._layer, command._level, command._region); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs new file mode 100644 index 000000000..204ee32da --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArraySetSamplersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArraySetSamplers; + private TableRef _textureArray; + private int _index; + private TableRef _samplers; + + public void Set(TableRef textureArray, int index, TableRef samplers) + { + _textureArray = textureArray; + _index = index; + _samplers = samplers; + } + + public static void Run(ref TextureArraySetSamplersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTextureArray textureArray = command._textureArray.Get(threaded); + textureArray.Base.SetSamplers(command._index, command._samplers.Get(threaded).Select(sampler => ((ThreadedSampler)sampler)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs new file mode 100644 index 000000000..cc94d1b6d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArraySetTexturesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArraySetTextures; + private TableRef _textureArray; + private int _index; + private TableRef _textures; + + public void Set(TableRef textureArray, int index, TableRef textures) + { + _textureArray = textureArray; + _index = index; + _textures = textures; + } + + public static void Run(ref TextureArraySetTexturesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTextureArray textureArray = command._textureArray.Get(threaded); + textureArray.Base.SetTextures(command._index, command._textures.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs new file mode 100644 index 000000000..d26ee1fbd --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs @@ -0,0 +1,36 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a image array. + /// + class ThreadedImageArray : IImageArray + { + private readonly ThreadedRenderer _renderer; + public IImageArray Base; + + public ThreadedImageArray(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void SetFormats(int index, Format[] imageFormats) + { + _renderer.New().Set(Ref(this), index, Ref(imageFormats)); + _renderer.QueueCommand(); + } + + public void SetImages(int index, ITexture[] images) + { + _renderer.New().Set(Ref(this), index, Ref(images)); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs index 9ad9e6454..80003b844 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -1,6 +1,6 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Resources { @@ -110,21 +110,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources _renderer.QueueCommand(); } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - _renderer.New().Set(Ref(this), Ref(data.ToArray())); + _renderer.New().Set(Ref(this), Ref(data)); _renderer.QueueCommand(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { - _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level); + _renderer.New().Set(Ref(this), Ref(data), layer, level); _renderer.QueueCommand(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { - _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level, region); + _renderer.New().Set(Ref(this), Ref(data), layer, level, region); _renderer.QueueCommand(); } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs new file mode 100644 index 000000000..82405a1f6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs @@ -0,0 +1,37 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a texture and sampler array. + /// + class ThreadedTextureArray : ITextureArray + { + private readonly ThreadedRenderer _renderer; + public ITextureArray Base; + + public ThreadedTextureArray(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void SetSamplers(int index, ISampler[] samplers) + { + _renderer.New().Set(Ref(this), index, Ref(samplers.ToArray())); + _renderer.QueueCommand(); + } + + public void SetTextures(int index, ITexture[] textures) + { + _renderer.New().Set(Ref(this), index, Ref(textures.ToArray())); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index ad50bddf4..697894eb5 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -183,6 +183,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + _renderer.New().Set(stage, binding, Ref(array)); + _renderer.QueueCommand(); + } + public void SetIndexBuffer(BufferRange buffer, IndexType type) { _renderer.New().Set(buffer, type); @@ -285,6 +291,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + _renderer.New().Set(stage, binding, Ref(array)); + _renderer.QueueCommand(); + } + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { _renderer.New().Set(_renderer.CopySpan(buffers)); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 830fbf2d9..5e17bcd2c 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -299,6 +299,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading return handle; } + public IImageArray CreateImageArray(int size, bool isBuffer) + { + var imageArray = new ThreadedImageArray(this); + New().Set(Ref(imageArray), size, isBuffer); + QueueCommand(); + + return imageArray; + } + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) { var program = new ThreadedProgram(this); @@ -349,6 +358,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading return texture; } } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + var textureArray = new ThreadedTextureArray(this); + New().Set(Ref(textureArray), size, isBuffer); + QueueCommand(); + + return textureArray; + } public void DeleteBuffer(BufferHandle buffer) { diff --git a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs index 84bca5b41..998c046f1 100644 --- a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs +++ b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs @@ -71,19 +71,21 @@ namespace Ryujinx.Graphics.GAL public readonly struct ResourceUsage : IEquatable { public int Binding { get; } + public int ArrayLength { get; } public ResourceType Type { get; } public ResourceStages Stages { get; } - public ResourceUsage(int binding, ResourceType type, ResourceStages stages) + public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages) { Binding = binding; + ArrayLength = arrayLength; Type = type; Stages = stages; } public override int GetHashCode() { - return HashCode.Combine(Binding, Type, Stages); + return HashCode.Combine(Binding, ArrayLength, Type, Stages); } public override bool Equals(object obj) @@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.GAL public bool Equals(ResourceUsage other) { - return Binding == other.Binding && Type == other.Type && Stages == other.Stages; + return Binding == other.Binding && ArrayLength == other.ArrayLength && Type == other.Type && Stages == other.Stages; } public static bool operator ==(ResourceUsage left, ResourceUsage right) diff --git a/src/Ryujinx.Graphics.Gpu/Constants.cs b/src/Ryujinx.Graphics.Gpu/Constants.cs index c553d988e..23b9be5ca 100644 --- a/src/Ryujinx.Graphics.Gpu/Constants.cs +++ b/src/Ryujinx.Graphics.Gpu/Constants.cs @@ -89,5 +89,10 @@ namespace Ryujinx.Graphics.Gpu /// Maximum size that an storage buffer is assumed to have when the correct size is unknown. /// public const ulong MaxUnknownStorageSize = 0x100000; + + /// + /// Size of a bindless texture handle as exposed by guest graphics APIs. + /// + public const int TextureHandleSizeInBytes = sizeof(ulong); } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index de395d574..218db15cf 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -308,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma if (target != null) { - byte[] data; + IMemoryOwner data; if (srcLinear) { data = LayoutConverter.ConvertLinearStridedToLinear( diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs index 37d74fdd3..93e43ce3c 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs @@ -1,4 +1,5 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Texture; using System; @@ -198,7 +199,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory if (target != null) { target.SynchronizeMemory(); - target.SetData(data, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); + var dataCopy = ByteMemoryPool.RentCopy(data); + target.SetData(dataCopy, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); target.SignalModified(); return; diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs index 492c6ee60..7bff1c4b8 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Texture target value public static Target GetTarget(SamplerType type) { - type &= ~(SamplerType.Indexed | SamplerType.Shadow); + type &= ~SamplerType.Shadow; switch (type) { diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 732ec5d4c..5e66a3b54 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -107,8 +107,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (texture.CacheNode != _textures.Last) { _textures.Remove(texture.CacheNode); - - texture.CacheNode = _textures.AddLast(texture); + _textures.AddLast(texture.CacheNode); } if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion) diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs index 6ede01971..e12fedc74 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -111,6 +111,21 @@ namespace Ryujinx.Graphics.Gpu.Image /// The GPU resource with the given ID public abstract T1 Get(int id); + /// + /// Gets the cached item with the given ID, or null if there is no cached item for the specified ID. + /// + /// ID of the item. This is effectively a zero-based index + /// The cached item with the given ID + public T1 GetCachedItem(int id) + { + if (!IsValidId(id)) + { + return default; + } + + return Items[id]; + } + /// /// Checks if a given ID is valid and inside the range of the pool. /// @@ -197,6 +212,23 @@ namespace Ryujinx.Graphics.Gpu.Image return false; } + /// + /// Checks if the pool was modified by comparing the current with a cached one. + /// + /// Cached modified sequence number + /// True if the pool was modified, false otherwise + public bool WasModified(ref int sequenceNumber) + { + if (sequenceNumber != ModifiedSequenceNumber) + { + sequenceNumber = ModifiedSequenceNumber; + + return true; + } + + return false; + } + protected abstract void InvalidateRangeImpl(ulong address, ulong size); protected abstract void Delete(T1 item); diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index f1615b388..e67caea81 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1,5 +1,4 @@ using Ryujinx.Common.Logging; -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -7,6 +6,7 @@ using Ryujinx.Graphics.Texture.Astc; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -573,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Discards all data for this texture. - /// This clears all dirty flags, modified flags, and pending copies from other textures. + /// This clears all dirty flags and pending copies from other textures. /// It should be used if the texture data will be fully overwritten by the next use. /// public void DiscardData() @@ -661,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image } } - SpanOrArray result = ConvertToHostCompatibleFormat(data); + IMemoryOwner result = ConvertToHostCompatibleFormat(data); if (ScaleFactor != 1f && AllowScaledSetData()) { @@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Uploads new texture data to the host GPU. /// /// New data - public void SetData(SpanOrArray data) + public void SetData(IMemoryOwner data) { BlacklistScale(); @@ -703,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// New data /// Target layer /// Target level - public void SetData(SpanOrArray data, int layer, int level) + public void SetData(IMemoryOwner data, int layer, int level) { BlacklistScale(); @@ -721,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Target layer /// Target level /// Target sub-region of the texture to update - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { BlacklistScale(); @@ -739,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Mip level to convert /// True to convert a single slice /// Converted data - public SpanOrArray ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) + public IMemoryOwner ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) { int width = Info.Width; int height = Info.Height; @@ -754,11 +754,11 @@ namespace Ryujinx.Graphics.Gpu.Image int sliceDepth = single ? 1 : depth; - SpanOrArray result; + IMemoryOwner linear; if (Info.IsLinear) { - result = LayoutConverter.ConvertLinearStridedToLinear( + linear = LayoutConverter.ConvertLinearStridedToLinear( width, height, Info.FormatInfo.BlockWidth, @@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image } else { - result = LayoutConverter.ConvertBlockLinearToLinear( + linear = LayoutConverter.ConvertBlockLinearToLinear( width, height, depth, @@ -787,33 +787,41 @@ namespace Ryujinx.Graphics.Gpu.Image data); } + IMemoryOwner result = linear; + // Handle compressed cases not supported by the host: // - ASTC is usually not supported on desktop cards. // - BC4/BC5 is not supported on 3D textures. if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) { - if (!AstcDecoder.TryDecodeToRgba8P( - result.ToArray(), - Info.FormatInfo.BlockWidth, - Info.FormatInfo.BlockHeight, - width, - height, - sliceDepth, - levels, - layers, - out byte[] decoded)) + using (result) { - string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; + if (!AstcDecoder.TryDecodeToRgba8P( + result.Memory, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + width, + height, + sliceDepth, + levels, + layers, + out IMemoryOwner decoded)) + { + string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; - Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); + Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); + } + + if (GraphicsConfig.EnableTextureRecompression) + { + using (decoded) + { + return BCnEncoder.EncodeBC7(decoded.Memory, width, height, sliceDepth, levels, layers); + } + } + + return decoded; } - - if (GraphicsConfig.EnableTextureRecompression) - { - decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers); - } - - result = decoded; } else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2()) { @@ -821,16 +829,22 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.Etc2RgbaSrgb: case Format.Etc2RgbaUnorm: - result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Etc2RgbPtaSrgb: case Format.Etc2RgbPtaUnorm: - result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Etc2RgbSrgb: case Format.Etc2RgbUnorm: - result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers); + } } } else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) @@ -839,48 +853,75 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.Bc1RgbaSrgb: case Format.Bc1RgbaUnorm: - result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Bc2Srgb: case Format.Bc2Unorm: - result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Bc3Srgb: case Format.Bc3Unorm: - result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers); + } case Format.Bc4Snorm: case Format.Bc4Unorm: - result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); - break; + using (result) + { + return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); + } case Format.Bc5Snorm: case Format.Bc5Unorm: - result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); - break; + using (result) + { + return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); + } case Format.Bc6HSfloat: case Format.Bc6HUfloat: - result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); - break; + using (result) + { + return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); + } case Format.Bc7Srgb: case Format.Bc7Unorm: - result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers); - break; + using (result) + { + return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers); + } } } else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) { - result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width); - - if (!_context.Capabilities.SupportsR4G4B4A4Format) + using (result) { - result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); + var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width); + + if (_context.Capabilities.SupportsR4G4B4A4Format) + { + return converted; + } + else + { + using (converted) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width); + } + } } } else if (Format == Format.R4G4B4A4Unorm) { if (!_context.Capabilities.SupportsR4G4B4A4Format) { - result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); + using (result) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); + } } } else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked()) @@ -889,19 +930,27 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.B5G6R5Unorm: case Format.R5G6B5Unorm: - result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width); - break; + using (result) + { + return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width); + } case Format.B5G5R5A1Unorm: case Format.R5G5B5X1Unorm: case Format.R5G5B5A1Unorm: - result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm); - break; + using (result) + { + return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm); + } case Format.A1B5G5R5Unorm: - result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width); - break; + using (result) + { + return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width); + } case Format.R4G4B4A4Unorm: - result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); - break; + using (result) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs index 606842d6d..12a457dbc 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -24,6 +24,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// public int Binding { get; } + /// + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array. + /// + public int ArrayLength { get; } + /// /// Constant buffer slot with the texture handle. /// @@ -45,14 +50,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// The shader sampler target type /// Format of the image as declared on the shader /// The shader texture binding point + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array /// Constant buffer slot where the texture handle is located /// The shader texture handle (read index into the texture constant buffer) /// The texture's usage flags, indicating how it is used in the shader - public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags) + public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) { Target = target; Format = format; Binding = binding; + ArrayLength = arrayLength; CbufSlot = cbufSlot; Handle = handle; Flags = flags; @@ -63,10 +70,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// The shader sampler target type /// The shader texture binding point + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array /// Constant buffer slot where the texture handle is located /// The shader texture handle (read index into the texture constant buffer) /// The texture's usage flags, indicating how it is used in the shader - public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags) + public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags) { } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs new file mode 100644 index 000000000..4645317c4 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -0,0 +1,730 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture bindings array cache. + /// + class TextureBindingsArrayCache + { + /// + /// Minimum timestamp delta until texture array can be removed from the cache. + /// + private const int MinDeltaForRemoval = 20000; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly bool _isCompute; + + /// + /// Array cache entry key. + /// + private readonly struct CacheEntryKey : IEquatable + { + /// + /// Whether the entry is for an image. + /// + public readonly bool IsImage; + + /// + /// Texture or image target type. + /// + public readonly Target Target; + + /// + /// Word offset of the first handle on the constant buffer. + /// + public readonly int HandleIndex; + + /// + /// Number of entries of the array. + /// + public readonly int ArrayLength; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + private readonly BufferBounds _textureBufferBounds; + + /// + /// Creates a new array cache entry. + /// + /// Whether the entry is for an image + /// Binding information for the array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + /// Constant buffer bounds with the texture handles + public CacheEntryKey( + bool isImage, + TextureBindingInfo bindingInfo, + TexturePool texturePool, + SamplerPool samplerPool, + ref BufferBounds textureBufferBounds) + { + IsImage = isImage; + Target = bindingInfo.Target; + HandleIndex = bindingInfo.Handle; + ArrayLength = bindingInfo.ArrayLength; + + _texturePool = texturePool; + _samplerPool = samplerPool; + + _textureBufferBounds = textureBufferBounds; + } + + /// + /// Checks if the texture and sampler pools matches the cached pools. + /// + /// Texture pool instance + /// Sampler pool instance + /// True if the pools match, false otherwise + private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool) + { + return _texturePool == texturePool && _samplerPool == samplerPool; + } + + /// + /// Checks if the cached constant buffer address and size matches. + /// + /// New buffer address and size + /// True if the address and size matches, false otherwise + private bool MatchesBufferBounds(BufferBounds textureBufferBounds) + { + return _textureBufferBounds.Equals(textureBufferBounds); + } + + public bool Equals(CacheEntryKey other) + { + return IsImage == other.IsImage && + Target == other.Target && + HandleIndex == other.HandleIndex && + ArrayLength == other.ArrayLength && + MatchesPools(other._texturePool, other._samplerPool) && + MatchesBufferBounds(other._textureBufferBounds); + } + + public override bool Equals(object obj) + { + return obj is CacheEntryKey other && Equals(other); + } + + public override int GetHashCode() + { + return _textureBufferBounds.Range.GetHashCode(); + } + } + + /// + /// Array cache entry. + /// + private class CacheEntry + { + /// + /// Key for this entry on the cache. + /// + public readonly CacheEntryKey Key; + + /// + /// Linked list node used on the texture bindings array cache. + /// + public LinkedListNode CacheNode; + + /// + /// Timestamp set on the last use of the array by the cache. + /// + public int CacheTimestamp; + + /// + /// All cached textures, along with their invalidated sequence number as value. + /// + public readonly Dictionary Textures; + + /// + /// All pool texture IDs along with their textures. + /// + public readonly Dictionary TextureIds; + + /// + /// All pool sampler IDs along with their samplers. + /// + public readonly Dictionary SamplerIds; + + /// + /// Backend texture array if the entry is for a texture, otherwise null. + /// + public readonly ITextureArray TextureArray; + + /// + /// Backend image array if the entry is for an image, otherwise null. + /// + public readonly IImageArray ImageArray; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + private int _texturePoolSequence; + private int _samplerPoolSequence; + + private int[] _cachedTextureBuffer; + private int[] _cachedSamplerBuffer; + + private int _lastSequenceNumber; + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool) + { + Key = key; + Textures = new Dictionary(); + TextureIds = new Dictionary(); + SamplerIds = new Dictionary(); + + _texturePool = texturePool; + _samplerPool = samplerPool; + + _lastSequenceNumber = -1; + } + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend texture array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool) + { + TextureArray = array; + } + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend image array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool) + { + ImageArray = array; + } + + /// + /// Synchronizes memory for all textures in the array. + /// + /// Indicates if the texture may be modified by the access + /// Indicates if the texture should be blacklisted for scaling + public void SynchronizeMemory(bool isStore, bool blacklistScale) + { + foreach (Texture texture in Textures.Keys) + { + texture.SynchronizeMemory(); + + if (isStore) + { + texture.SignalModified(); + } + + if (blacklistScale && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + } + + /// + /// Clears all cached texture instances. + /// + public void Reset() + { + Textures.Clear(); + TextureIds.Clear(); + SamplerIds.Clear(); + } + + /// + /// Updates the cached constant buffer data. + /// + /// Constant buffer data with the texture handles (and sampler handles, if they are combined) + /// Constant buffer data with the sampler handles + /// Whether and comes from different buffers + public void UpdateData(ReadOnlySpan cachedTextureBuffer, ReadOnlySpan cachedSamplerBuffer, bool separateSamplerBuffer) + { + _cachedTextureBuffer = cachedTextureBuffer.ToArray(); + _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer; + } + + /// + /// Checks if any texture has been deleted since the last call to this method. + /// + /// True if one or more textures have been deleted, false otherwise + public bool ValidateTextures() + { + foreach ((Texture texture, int invalidatedSequence) in Textures) + { + if (texture.InvalidatedSequence != invalidatedSequence) + { + return false; + } + } + + return true; + } + + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pools might have been modified, false otherwise + public bool PoolsModified() + { + bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence); + bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence); + + // If both pools were not modified since the last check, we have nothing else to check. + if (!texturePoolModified && !samplerPoolModified) + { + return false; + } + + // If the pools were modified, let's check if any of the entries we care about changed. + + // Check if any of our cached textures changed on the pool. + foreach ((int textureId, Texture texture) in TextureIds) + { + if (_texturePool.GetCachedItem(textureId) != texture) + { + return true; + } + } + + // Check if any of our cached samplers changed on the pool. + foreach ((int samplerId, Sampler sampler) in SamplerIds) + { + if (_samplerPool.GetCachedItem(samplerId) != sampler) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the sequence number matches the one used on the last call to this method. + /// + /// Current sequence number + /// True if the sequence numbers match, false otherwise + public bool MatchesSequenceNumber(int currentSequenceNumber) + { + if (_lastSequenceNumber == currentSequenceNumber) + { + return true; + } + + _lastSequenceNumber = currentSequenceNumber; + + return false; + } + + /// + /// Checks if the buffer data matches the cached data. + /// + /// New texture buffer data + /// New sampler buffer data + /// Whether and comes from different buffers + /// Word offset of the sampler constant buffer handle that is used + /// True if the data matches, false otherwise + public bool MatchesBufferData( + ReadOnlySpan cachedTextureBuffer, + ReadOnlySpan cachedSamplerBuffer, + bool separateSamplerBuffer, + int samplerWordOffset) + { + if (_cachedTextureBuffer != null && cachedTextureBuffer.Length > _cachedTextureBuffer.Length) + { + cachedTextureBuffer = cachedTextureBuffer[.._cachedTextureBuffer.Length]; + } + + if (!_cachedTextureBuffer.AsSpan().SequenceEqual(cachedTextureBuffer)) + { + return false; + } + + if (separateSamplerBuffer) + { + if (_cachedSamplerBuffer == null || + _cachedSamplerBuffer.Length <= samplerWordOffset || + cachedSamplerBuffer.Length <= samplerWordOffset) + { + return false; + } + + int oldValue = _cachedSamplerBuffer[samplerWordOffset]; + int newValue = cachedSamplerBuffer[samplerWordOffset]; + + return oldValue == newValue; + } + + return true; + } + } + + private readonly Dictionary _cache; + private readonly LinkedList _lruCache; + + private int _currentTimestamp; + + /// + /// Creates a new instance of the texture bindings array cache. + /// + /// GPU context + /// GPU channel + /// Whether the bindings will be used for compute or graphics pipelines + public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute) + { + _context = context; + _channel = channel; + _isCompute = isCompute; + _cache = new Dictionary(); + _lruCache = new LinkedList(); + } + + /// + /// Updates a texture array bindings and textures. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Sampler handles source + /// Array binding information + public void UpdateTextureArray( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + SamplerIndex samplerIndex, + TextureBindingInfo bindingInfo) + { + Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo); + } + + /// + /// Updates a image array bindings and textures. + /// + /// Texture pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Array binding information + public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo) + { + Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo); + } + + /// + /// Updates a texture or image array bindings and textures. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Whether the array is a image or texture array + /// Sampler handles source + /// Array binding information + private void Update( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + bool isImage, + SamplerIndex samplerIndex, + TextureBindingInfo bindingInfo) + { + (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex); + + bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex; + + ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); + ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + + CacheEntry entry = GetOrAddEntry( + texturePool, + samplerPool, + bindingInfo, + isImage, + ref textureBufferBounds, + out bool isNewEntry); + + bool poolsModified = entry.PoolsModified(); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported); + + ReadOnlySpan cachedTextureBuffer; + ReadOnlySpan cachedSamplerBuffer; + + if (!poolsModified && !isNewEntry && entry.ValidateTextures()) + { + if (entry.MatchesSequenceNumber(_context.SequenceNumber)) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + + return; + } + + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range)); + + if (separateSamplerBuffer) + { + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range)); + } + else + { + cachedSamplerBuffer = cachedTextureBuffer; + } + + (_, int samplerWordOffset, _) = TextureHandle.UnpackOffsets(bindingInfo.Handle); + + if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset)) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + + return; + } + } + else + { + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range)); + + if (separateSamplerBuffer) + { + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range)); + } + else + { + cachedSamplerBuffer = cachedTextureBuffer; + } + } + + if (!isNewEntry) + { + entry.Reset(); + } + + entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer); + + Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null; + ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; + ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; + + for (int index = 0; index < bindingInfo.ArrayLength; index++) + { + int handleIndex = bindingInfo.Handle + index * (Constants.TextureHandleSizeInBytes / sizeof(int)); + int packedId = TextureHandle.ReadPackedId(handleIndex, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); + int samplerId; + + if (samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = TextureHandle.UnpackSamplerId(packedId); + } + + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture); + + if (texture != null) + { + entry.Textures[texture] = texture.InvalidatedSequence; + + if (isStore) + { + texture.SignalModified(); + } + + if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + + Sampler sampler = samplerPool?.Get(samplerId); + + entry.TextureIds[textureId] = texture; + entry.SamplerIds[samplerId] = sampler; + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ISampler hostSampler = sampler?.GetHostSampler(texture); + + Format format = bindingInfo.Format; + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); + } + else + { + _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); + } + } + else if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + formats[index] = format; + textures[index] = hostTexture; + } + else + { + samplers[index] = hostSampler; + textures[index] = hostTexture; + } + } + + if (isImage) + { + entry.ImageArray.SetFormats(0, formats); + entry.ImageArray.SetImages(0, textures); + + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + entry.TextureArray.SetSamplers(0, samplers); + entry.TextureArray.SetTextures(0, textures); + + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + } + + /// + /// Gets a cached texture entry, or creates a new one if not found. + /// + /// Texture pool + /// Sampler pool + /// Array binding information + /// Whether the array is a image or texture array + /// Constant buffer bounds with the texture handles + /// Whether a new entry was created, or an existing one was returned + /// Cache entry + private CacheEntry GetOrAddEntry( + TexturePool texturePool, + SamplerPool samplerPool, + TextureBindingInfo bindingInfo, + bool isImage, + ref BufferBounds textureBufferBounds, + out bool isNew) + { + CacheEntryKey key = new CacheEntryKey( + isImage, + bindingInfo, + texturePool, + samplerPool, + ref textureBufferBounds); + + isNew = !_cache.TryGetValue(key, out CacheEntry entry); + + if (isNew) + { + int arrayLength = bindingInfo.ArrayLength; + + if (isImage) + { + IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + } + else + { + ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + } + } + + if (entry.CacheNode != null) + { + _lruCache.Remove(entry.CacheNode); + _lruCache.AddLast(entry.CacheNode); + } + else + { + entry.CacheNode = _lruCache.AddLast(entry); + } + + entry.CacheTimestamp = ++_currentTimestamp; + + RemoveLeastUsedEntries(); + + return entry; + } + + /// + /// Remove entries from the cache that have not been used for some time. + /// + private void RemoveLeastUsedEntries() + { + LinkedListNode nextNode = _lruCache.First; + + while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval) + { + LinkedListNode toRemove = nextNode; + nextNode = nextNode.Next; + _cache.Remove(toRemove.Value.Key); + _lruCache.Remove(toRemove); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index ef5d0deaa..3c10c95e0 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; + private readonly TextureBindingsArrayCache _arrayBindingsCache; + private TexturePool _cachedTexturePool; private SamplerPool _cachedSamplerPool; @@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image private TextureState[] _textureState; private TextureState[] _imageState; + private int[] _textureCounts; + private int _texturePoolSequence; private int _samplerPoolSequence; @@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.Gpu.Image _isCompute = isCompute; + _arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute); + int stages = isCompute ? 1 : Constants.ShaderStages; _textureBindings = new TextureBindingInfo[stages][]; @@ -95,9 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image for (int stage = 0; stage < stages; stage++) { - _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; - _imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; + _textureBindings[stage] = Array.Empty(); + _imageBindings[stage] = Array.Empty(); } + + _textureCounts = Array.Empty(); } /// @@ -109,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textureBindings = bindings.TextureBindings; _imageBindings = bindings.ImageBindings; + _textureCounts = bindings.TextureCounts; + SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); } @@ -401,27 +411,6 @@ namespace Ryujinx.Graphics.Gpu.Image } } -#pragma warning disable IDE0051 // Remove unused private member - /// - /// Counts the total number of texture bindings used by all shader stages. - /// - /// The total amount of textures used - private int GetTextureBindingsCount() - { - int count = 0; - - foreach (TextureBindingInfo[] textureInfo in _textureBindings) - { - if (textureInfo != null) - { - count += textureInfo.Length; - } - } - - return count; - } -#pragma warning restore IDE0051 - /// /// Ensures that the texture bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. @@ -465,6 +454,13 @@ namespace Ryujinx.Graphics.Gpu.Image TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureUsageFlags usageFlags = bindingInfo.Flags; + if (bindingInfo.ArrayLength > 1) + { + _arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); + + continue; + } + (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); @@ -582,7 +578,7 @@ namespace Ryujinx.Graphics.Gpu.Image } // Scales for images appear after the texture ones. - int baseScaleIndex = _textureBindings[stageIndex].Length; + int baseScaleIndex = _textureCounts[stageIndex]; int cachedTextureBufferIndex = -1; int cachedSamplerBufferIndex = -1; @@ -595,6 +591,14 @@ namespace Ryujinx.Graphics.Gpu.Image { TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureUsageFlags usageFlags = bindingInfo.Flags; + + if (bindingInfo.ArrayLength > 1) + { + _arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); + + continue; + } + int scaleIndex = baseScaleIndex + index; (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); @@ -620,7 +624,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (isStore) { - cachedTexture?.SignalModified(); + cachedTexture.SignalModified(); } Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index 5b930fa47..3cdeac9c5 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -247,6 +247,10 @@ namespace Ryujinx.Graphics.Gpu.Image { return TextureMatchQuality.FormatAlias; } + else if (lhs.FormatInfo.Format == Format.D32FloatS8Uint && rhs.FormatInfo.Format == Format.R32G32Float) + { + return TextureMatchQuality.FormatAlias; + } } return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 34a9a9c75..de9c47c97 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -1,4 +1,3 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -6,6 +5,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -282,7 +282,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Discards all data for a given texture. - /// This clears all dirty flags, modified flags, and pending copies from other textures. + /// This clears all dirty flags and pending copies from other textures. /// /// The texture being discarded public void DiscardData(Texture texture) @@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image ReadOnlySpan data = dataSpan[(offset - spanBase)..]; - SpanOrArray result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); + IMemoryOwner result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index d345ec2db..0af6b7ca8 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -182,11 +182,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Discards all data for this handle. - /// This clears all dirty flags, modified flags, and pending copies from other handles. + /// This clears all dirty flags and pending copies from other handles. /// public void DiscardData() { - Modified = false; DeferredCopy = null; foreach (RegionHandle handle in Handles) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs index aed3268ae..cf783ef2f 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs @@ -1,12 +1,13 @@ using Ryujinx.Graphics.Shader; using Ryujinx.Memory.Range; +using System; namespace Ryujinx.Graphics.Gpu.Memory { /// /// Memory range used for buffers. /// - readonly struct BufferBounds + readonly struct BufferBounds : IEquatable { /// /// Physical memory ranges where the buffer is mapped. @@ -33,5 +34,25 @@ namespace Ryujinx.Graphics.Gpu.Memory Range = range; Flags = flags; } + + public override bool Equals(object obj) + { + return obj is BufferBounds bounds && Equals(bounds); + } + + public bool Equals(BufferBounds bounds) + { + return Range == bounds.Range && Flags == bounds.Flags; + } + + public bool Equals(ref BufferBounds bounds) + { + return Range == bounds.Range && Flags == bounds.Flags; + } + + public override int GetHashCode() + { + return HashCode.Combine(Range, Flags); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 1f02b9d7f..8f2201e0a 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly VertexBuffer[] _vertexBuffers; private readonly BufferBounds[] _transformFeedbackBuffers; private readonly List _bufferTextures; + private readonly List> _bufferTextureArrays; + private readonly List> _bufferImageArrays; private readonly BufferAssignment[] _ranges; /// @@ -140,11 +142,12 @@ namespace Ryujinx.Graphics.Gpu.Memory } _bufferTextures = new List(); + _bufferTextureArrays = new List>(); + _bufferImageArrays = new List>(); _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; } - /// /// Sets the memory range with the index buffer data, to be used for subsequent draw calls. /// @@ -418,6 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Memory return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address; } + /// + /// Gets the size of the compute uniform buffer currently bound at the given index. + /// + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public int GetComputeUniformBufferSize(int index) + { + return (int)_cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Size; + } + /// /// Gets the address of the graphics uniform buffer currently bound at the given index. /// @@ -429,6 +442,17 @@ namespace Ryujinx.Graphics.Gpu.Memory return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address; } + /// + /// Gets the size of the graphics uniform buffer currently bound at the given index. + /// + /// Index of the shader stage + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public int GetGraphicsUniformBufferSize(int stage, int index) + { + return (int)_gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Size; + } + /// /// Gets the bounds of the uniform buffer currently bound at the given index. /// @@ -459,7 +483,7 @@ namespace Ryujinx.Graphics.Gpu.Memory BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true); BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false); - CommitBufferTextureBindings(); + CommitBufferTextureBindings(bufferCache); // Force rebind after doing compute work. Rebind(); @@ -470,14 +494,15 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Commit any queued buffer texture bindings. /// - private void CommitBufferTextureBindings() + /// Buffer cache + private void CommitBufferTextureBindings(BufferCache bufferCache) { if (_bufferTextures.Count > 0) { foreach (var binding in _bufferTextures) { var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); - var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Range, isStore); + var range = bufferCache.GetBufferRange(binding.Range, isStore); binding.Texture.SetStorage(range); // The texture must be rebound to use the new storage if it was updated. @@ -494,6 +519,33 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures.Clear(); } + + if (_bufferTextureArrays.Count > 0 || _bufferImageArrays.Count > 0) + { + ITexture[] textureArray = new ITexture[1]; + + foreach (var binding in _bufferTextureArrays) + { + var range = bufferCache.GetBufferRange(binding.Range); + binding.Texture.SetStorage(range); + + textureArray[0] = binding.Texture; + binding.Array.SetTextures(binding.Index, textureArray); + } + + foreach (var binding in _bufferImageArrays) + { + var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + var range = bufferCache.GetBufferRange(binding.Range, isStore); + binding.Texture.SetStorage(range); + + textureArray[0] = binding.Texture; + binding.Array.SetImages(binding.Index, textureArray); + } + + _bufferTextureArrays.Clear(); + _bufferImageArrays.Clear(); + } } /// @@ -676,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory UpdateBuffers(_gpUniformBuffers); } - CommitBufferTextureBindings(); + CommitBufferTextureBindings(bufferCache); _rebind = false; @@ -828,6 +880,50 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); } + /// + /// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings. + /// + /// Texture array where the element will be inserted + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Index of the binding on the array + /// Format of the buffer texture + public void SetBufferTextureStorage( + ITextureArray array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); + + _bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format)); + } + + /// + /// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings. + /// + /// Image array where the element will be inserted + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Index of the binding on the array + /// Format of the buffer texture + public void SetBufferTextureStorage( + IImageArray array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); + + _bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format)); + } + /// /// Force all bound textures and images to be rebound the next time CommitBindings is called. /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs new file mode 100644 index 000000000..fa79e4f92 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs @@ -0,0 +1,66 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Memory.Range; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A buffer binding to apply to a buffer texture array element. + /// + readonly struct BufferTextureArrayBinding + { + /// + /// Backend texture or image array. + /// + public T Array { get; } + + /// + /// The buffer texture. + /// + public ITexture Texture { get; } + + /// + /// Physical ranges of memory where the buffer texture data is located. + /// + public MultiRange Range { get; } + + /// + /// The image or sampler binding info for the buffer texture. + /// + public TextureBindingInfo BindingInfo { get; } + + /// + /// Index of the binding on the array. + /// + public int Index { get; } + + /// + /// The image format for the binding. + /// + public Format Format { get; } + + /// + /// Create a new buffer texture binding. + /// + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info + /// Index of the binding on the array + /// Binding format + public BufferTextureArrayBinding( + T array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + Array = array; + Texture = texture; + Range = range; + BindingInfo = bindingInfo; + Index = index; + Format = format; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 30f87813f..0b6c78fac 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -1,6 +1,8 @@ +using Ryujinx.Common.Memory; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -240,11 +242,11 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - Memory memory = new byte[size]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); - GetSpan(va, size).CopyTo(memory.Span); + GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory, tracked); + return new WritableRegion(this, va, memoryOwner, tracked); } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index ce970fab7..4d09c3aab 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Memory; using Ryujinx.Cpu; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Gpu.Image; @@ -6,6 +7,7 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -190,7 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - Memory memory = new byte[range.GetSize()]; + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(range.GetSize()); + + Memory memory = memoryOwner.Memory; int offset = 0; for (int i = 0; i < range.Count; i++) @@ -204,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Memory offset += size; } - return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked); + return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memoryOwner, tracked); } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index 4e1cb4e12..6e36753e8 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public BufferDescriptor[][] ConstantBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; } + public int[] TextureCounts { get; } + public int MaxTextureBinding { get; } public int MaxImageBinding { get; } @@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader ConstantBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][]; + TextureCounts = new int[stageCount]; + int maxTextureBinding = -1; int maxImageBinding = -1; int offset = isCompute ? 0 : 1; @@ -59,13 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Shader var result = new TextureBindingInfo( target, descriptor.Binding, + descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); - if (descriptor.Binding > maxTextureBinding) + if (descriptor.ArrayLength <= 1) { - maxTextureBinding = descriptor.Binding; + if (descriptor.Binding > maxTextureBinding) + { + maxTextureBinding = descriptor.Binding; + } + + TextureCounts[i]++; } return result; @@ -80,11 +90,12 @@ namespace Ryujinx.Graphics.Gpu.Shader target, format, descriptor.Binding, + descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); - if (descriptor.Binding > maxImageBinding) + if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding) { maxImageBinding = descriptor.Binding; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index de6432bc1..681838a9b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// The constant buffer 1 data of the shader /// Shader specialization state of the cached shader /// Shader specialization state of the recompiled shader + /// Resource counts shared across all shader stages /// Shader stage index public DiskCacheGpuAccessor( GpuContext context, @@ -108,6 +109,27 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters; } + /// + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); + } + + /// + public int QueryTextureArrayLengthFromBuffer(int slot) + { + if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot); + _newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + /// public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { @@ -116,13 +138,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return ConvertToTextureFormat(format, formatSrgb); } - /// - public SamplerType QuerySamplerType(int handle, int cbufSlot) - { - _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); - return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); - } - /// public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 5036186ba..b6a277a2a 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -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 = 6462; + private const uint CodeGenVersion = 6489; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs index ba23f70ee..d5abb9e55 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs @@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// InvalidCb1DataLength, + /// + /// The cache is missing the length of a texture array used by the shader. + /// + MissingTextureArrayLength, + /// /// The cache is missing the descriptor of a texture used by the shader. /// @@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache DiskCacheLoadResult.Success => "No error.", DiskCacheLoadResult.NoAccess => "Could not access the cache file.", DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.", + DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.", DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.", DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.", DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.", diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 95763f31d..1d22ab933 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader public ReadOnlySpan GetCode(ulong address, int minimumSize) { int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff)); + return MemoryMarshal.Cast(_channel.MemoryManager.GetSpan(address, size)); } @@ -119,6 +120,27 @@ namespace Ryujinx.Graphics.Gpu.Shader return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; } + /// + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); + } + + /// + public int QueryTextureArrayLengthFromBuffer(int slot) + { + int size = _compute + ? _channel.BufferManager.GetComputeUniformBufferSize(slot) + : _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot); + + int arrayLength = size / Constants.TextureHandleSizeInBytes; + + _state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + //// public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { @@ -127,13 +149,6 @@ namespace Ryujinx.Graphics.Gpu.Shader return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb()); } - /// - public SamplerType QuerySamplerType(int handle, int cbufSlot) - { - _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); - return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); - } - /// public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index a5b31363b..06e5edf1e 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader private int _reservedTextures; private int _reservedImages; + private int _staticTexturesCount; + private int _staticImagesCount; + /// /// Creates a new GPU accessor. /// @@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _reservedImages = rrc.ReservedImages; } - public int QueryBindingConstantBuffer(int index) + public int CreateConstantBufferBinding(int index) { int binding; @@ -64,7 +67,39 @@ namespace Ryujinx.Graphics.Gpu.Shader return binding + _reservedConstantBuffers; } - public int QueryBindingStorageBuffer(int index) + public int CreateImageBinding(int count, bool isBuffer) + { + int binding; + + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + if (count == 1) + { + int index = _staticImagesCount++; + + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumImagesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++; + } + } + else + { + binding = _resourceCounts.ImagesCount; + + _resourceCounts.ImagesCount += count; + } + + return binding + _reservedImages; + } + + public int CreateStorageBufferBinding(int index) { int binding; @@ -80,48 +115,38 @@ namespace Ryujinx.Graphics.Gpu.Shader return binding + _reservedStorageBuffers; } - public int QueryBindingTexture(int index, bool isBuffer) + public int CreateTextureBinding(int count, bool isBuffer) { int binding; if (_context.Capabilities.Api == TargetApi.Vulkan) { - if (isBuffer) + if (count == 1) { - index += (int)_context.Capabilities.MaximumTexturesPerStage; - } + int index = _staticTexturesCount++; - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumTexturesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++; + } } else { - binding = _resourceCounts.TexturesCount++; + binding = _resourceCounts.TexturesCount; + + _resourceCounts.TexturesCount += count; } return binding + _reservedTextures; } - public int QueryBindingImage(int index, bool isBuffer) - { - int binding; - - if (_context.Capabilities.Api == TargetApi.Vulkan) - { - if (isBuffer) - { - index += (int)_context.Capabilities.MaximumImagesPerStage; - } - - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); - } - else - { - binding = _resourceCounts.ImagesCount++; - } - - return binding + _reservedImages; - } - private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) { if ((uint)index >= maxPerStage) @@ -148,6 +173,16 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } + private static uint GetDynamicBaseIndexDual(uint maxPerStage) + { + return GetDynamicBaseIndex(maxPerStage) * 2; + } + + private static uint GetDynamicBaseIndex(uint maxPerStage) + { + return maxPerStage * Constants.ShaderStages; + } + public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index c2258026c..ea8f164f1 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); + AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false); + AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true); + AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false); AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true); AddUsage(info.Textures, stages, TextureSetIndex, isImage: false); @@ -169,6 +172,30 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDescriptor(stages, type2, setIndex, binding + count, count); } + /// + /// Adds all array descriptors (those with an array length greater than one). + /// + /// Textures to be added + /// Stages where the textures are used + /// Descriptor set index where the textures will be bound + /// True for images, false for textures + private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, int setIndex, bool isImage) + { + foreach (TextureDescriptor texture in textures) + { + if (texture.ArrayLength > 1) + { + bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; + + ResourceType type = isBuffer + ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) + : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); + + _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); + } + } + } + /// /// Adds buffer usage information to the list of usages. /// @@ -181,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { for (int index = 0; index < count; index++) { - _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages)); } } @@ -198,6 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { _resourceUsages[setIndex].Add(new ResourceUsage( buffer.Binding, + 1, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, stages)); } @@ -220,10 +248,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); - _resourceUsages[setIndex].Add(new ResourceUsage( - texture.Binding, - type, - stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 1477b7382..c90a0b8f4 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { PrimitiveTopology = 1 << 1, TransformFeedback = 1 << 3, + TextureArrayFromBuffer = 1 << 4, } private QueriedStateFlags _queriedState; @@ -153,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } private readonly Dictionary> _textureSpecialization; + private readonly Dictionary _textureArraySpecialization; private KeyValuePair>[] _allTextures; private Box[][] _textureByBinding; private Box[][] _imageByBinding; @@ -163,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader private ShaderSpecializationState() { _textureSpecialization = new Dictionary>(); + _textureArraySpecialization = new Dictionary(); } /// @@ -323,6 +326,19 @@ namespace Ryujinx.Graphics.Gpu.Shader state.Value.CoordNormalized = coordNormalized; } + /// + /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Number of elements in the texture array + public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length) + { + _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromBuffer; + } + /// /// Indicates that the format of a given texture was used during the shader translation process. /// @@ -379,6 +395,17 @@ namespace Ryujinx.Graphics.Gpu.Shader return GetTextureSpecState(stageIndex, handle, cbufSlot) != null; } + /// + /// Checks if a given texture array (from constant buffer) was registerd on this specialization state. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot) + { + return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + } + /// /// Gets the recorded format of a given texture. /// @@ -413,6 +440,17 @@ namespace Ryujinx.Graphics.Gpu.Shader return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; } + /// + /// Gets the recorded length of a given texture array (from constant buffer). + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) + { + return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + } + /// /// Gets texture specialization state for a given texture, or create a new one if not present. /// @@ -548,6 +586,12 @@ namespace Ryujinx.Graphics.Gpu.Shader return Matches(channel, ref poolState, checkTextures, isCompute: false); } + /// + /// Converts special vertex attribute groups to their generic equivalents, for comparison purposes. + /// + /// GPU channel + /// Vertex attribute type + /// Filtered attribute private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) { type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); @@ -838,6 +882,22 @@ namespace Ryujinx.Graphics.Gpu.Shader specState._textureSpecialization[textureKey] = textureState; } + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + TextureKey textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArraySpecialization[textureKey] = length; + } + } + return specState; } @@ -902,6 +962,21 @@ namespace Ryujinx.Graphics.Gpu.Shader dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + count = (ushort)_textureArraySpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArraySpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } } } } diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs index 46dda13f2..a6c5e4aca 100644 --- a/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs @@ -33,7 +33,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa public int Quality { - get => _quality; set + get => _quality; + set { _quality = Math.Clamp(value, 0, _qualities.Length - 1); } @@ -150,8 +151,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa _areaTexture = new TextureStorage(_renderer, areaInfo); _searchTexture = new TextureStorage(_renderer, searchInfo); - var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); - var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); + var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); + var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); var areaView = _areaTexture.CreateDefaultView(); var searchView = _searchTexture.CreateDefaultView(); diff --git a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs index c4bbf7456..434f25900 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs @@ -1,4 +1,6 @@ +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -8,9 +10,11 @@ namespace Ryujinx.Graphics.OpenGL.Image { static class FormatConverter { - public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan data) + public unsafe static IMemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data) { - byte[] output = new byte[data.Length]; + IMemoryOwner outputMemory = ByteMemoryPool.Rent(data.Length); + + Span output = outputMemory.Memory.Span; int start = 0; @@ -74,7 +78,7 @@ namespace Ryujinx.Graphics.OpenGL.Image outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8); } - return output; + return outputMemory; } public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan data) diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs new file mode 100644 index 000000000..1c5acedf3 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs @@ -0,0 +1,67 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class ImageArray : IImageArray + { + private record struct TextureRef + { + public int Handle; + public Format Format; + } + + private readonly TextureRef[] _images; + + public ImageArray(int size) + { + _images = new TextureRef[size]; + } + + public void SetFormats(int index, GAL.Format[] imageFormats) + { + for (int i = 0; i < imageFormats.Length; i++) + { + _images[index + i].Format = imageFormats[i]; + } + } + + public void SetImages(int index, ITexture[] images) + { + for (int i = 0; i < images.Length; i++) + { + ITexture image = images[i]; + + if (image is TextureBase imageBase) + { + _images[index + i].Handle = imageBase.Handle; + } + else + { + _images[index + i].Handle = 0; + } + } + } + + public void Bind(int baseBinding) + { + for (int i = 0; i < _images.Length; i++) + { + if (_images[i].Handle == 0) + { + GL.BindImageTexture(baseBinding + i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + } + else + { + SizedInternalFormat format = FormatTable.GetImageFormat(_images[i].Format); + + if (format != 0) + { + GL.BindImageTexture(baseBinding + i, _images[i].Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs new file mode 100644 index 000000000..d70b0a008 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureArray : ITextureArray + { + private record struct TextureRef + { + public TextureBase Texture; + public Sampler Sampler; + } + + private readonly TextureRef[] _textureRefs; + + public TextureArray(int size) + { + _textureRefs = new TextureRef[size]; + } + + public void SetSamplers(int index, ISampler[] samplers) + { + for (int i = 0; i < samplers.Length; i++) + { + _textureRefs[index + i].Sampler = samplers[i] as Sampler; + } + } + + public void SetTextures(int index, ITexture[] textures) + { + for (int i = 0; i < textures.Length; i++) + { + _textureRefs[index + i].Texture = textures[i] as TextureBase; + } + } + + public void Bind(int baseBinding) + { + for (int i = 0; i < _textureRefs.Length; i++) + { + if (_textureRefs[i].Texture != null) + { + _textureRefs[i].Texture.Bind(baseBinding + i); + _textureRefs[i].Sampler?.Bind(baseBinding + i); + } + else + { + TextureBase.ClearBinding(baseBinding + i); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index f140b276a..a8196541a 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -1,7 +1,7 @@ using OpenTK.Graphics.OpenGL; -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using System; +using System.Buffers; namespace Ryujinx.Graphics.OpenGL.Image { @@ -54,19 +54,24 @@ namespace Ryujinx.Graphics.OpenGL.Image throw new NotImplementedException(); } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - var dataSpan = data.AsSpan(); + var dataSpan = data.Memory.Span; Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]); + + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { throw new NotSupportedException(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { throw new NotSupportedException(); } diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 7f1b1c382..8a18e6132 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -1,8 +1,8 @@ using OpenTK.Graphics.OpenGL; using Ryujinx.Common; -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using System; +using System.Buffers; using System.Diagnostics; namespace Ryujinx.Graphics.OpenGL.Image @@ -448,70 +448,59 @@ namespace Ryujinx.Graphics.OpenGL.Image } } - public void SetData(SpanOrArray data) + public void SetData(IMemoryOwner data) { - var dataSpan = data.AsSpan(); - - if (Format == Format.S8UintD24Unorm) + using (data = EnsureDataFormat(data)) { - dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); - } - - unsafe - { - fixed (byte* ptr = dataSpan) + unsafe { - ReadFrom((IntPtr)ptr, dataSpan.Length); + var dataSpan = data.Memory.Span; + fixed (byte* ptr = dataSpan) + { + ReadFrom((IntPtr)ptr, dataSpan.Length); + } } } } - public void SetData(SpanOrArray data, int layer, int level) + public void SetData(IMemoryOwner data, int layer, int level) { - var dataSpan = data.AsSpan(); - - if (Format == Format.S8UintD24Unorm) + using (data = EnsureDataFormat(data)) { - dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); - } - - unsafe - { - fixed (byte* ptr = dataSpan) + unsafe { - int width = Math.Max(Info.Width >> level, 1); - int height = Math.Max(Info.Height >> level, 1); + fixed (byte* ptr = data.Memory.Span) + { + int width = Math.Max(Info.Width >> level, 1); + int height = Math.Max(Info.Height >> level, 1); - ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); + ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); + } } } } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { - var dataSpan = data.AsSpan(); - - if (Format == Format.S8UintD24Unorm) + using (data = EnsureDataFormat(data)) { - dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); - } + int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight); - int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); - int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight); - - unsafe - { - fixed (byte* ptr = dataSpan) + unsafe { - ReadFrom2D( - (IntPtr)ptr, - layer, - level, - region.X, - region.Y, - region.Width, - region.Height, - BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); + fixed (byte* ptr = data.Memory.Span) + { + ReadFrom2D( + (IntPtr)ptr, + layer, + level, + region.X, + region.Y, + region.Width, + region.Height, + BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); + } } } } @@ -533,6 +522,19 @@ namespace Ryujinx.Graphics.OpenGL.Image ReadFrom2D(data, layer, level, x, y, width, height, mipSize); } + private IMemoryOwner EnsureDataFormat(IMemoryOwner data) + { + if (Format == Format.S8UintD24Unorm) + { + using (data) + { + return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span); + } + } + + return data; + } + private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize) { TextureTarget target = Target.Convert(); diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index eabcb3c10..a945cbf20 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -90,6 +90,11 @@ namespace Ryujinx.Graphics.OpenGL throw new NotSupportedException(); } + public IImageArray CreateImageArray(int size, bool isBuffer) + { + return new ImageArray(size); + } + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) { return new Program(shaders, info.FragmentOutputMap); @@ -112,6 +117,11 @@ namespace Ryujinx.Graphics.OpenGL } } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + return new TextureArray(size); + } + public void DeleteBuffer(BufferHandle buffer) { PersistentBuffers.Unmap(buffer); diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index 0757fcd99..6d066bb67 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -958,6 +958,11 @@ namespace Ryujinx.Graphics.OpenGL } } + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + (array as ImageArray).Bind(binding); + } + public void SetIndexBuffer(BufferRange buffer, IndexType type) { _elementsType = type.Convert(); @@ -1302,6 +1307,10 @@ namespace Ryujinx.Graphics.OpenGL } } + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + (array as TextureArray).Bind(binding); + } public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 500de71f6..763487dac 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -339,24 +339,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl private static void DeclareSamplers(CodeGenContext context, IEnumerable definitions) { - int arraySize = 0; - foreach (var definition in definitions) { - string indexExpr = string.Empty; + string arrayDecl = string.Empty; - if (definition.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength > 1) { - if (arraySize == 0) - { - arraySize = ResourceManager.SamplerArraySize; - } - else if (--arraySize != 0) - { - continue; - } - - indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]"; + arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]"; + } + else if (definition.ArrayLength == 0) + { + arrayDecl = "[]"; } string samplerTypeName = definition.Type.ToGlslSamplerType(); @@ -368,30 +361,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl layout = $", set = {definition.Set}"; } - context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};"); + context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{arrayDecl};"); } } private static void DeclareImages(CodeGenContext context, IEnumerable definitions) { - int arraySize = 0; - foreach (var definition in definitions) { - string indexExpr = string.Empty; + string arrayDecl = string.Empty; - if (definition.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength > 1) { - if (arraySize == 0) - { - arraySize = ResourceManager.SamplerArraySize; - } - else if (--arraySize != 0) - { - continue; - } - - indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]"; + arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]"; + } + else if (definition.ArrayLength == 0) + { + arrayDecl = "[]"; } string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType()); @@ -413,7 +399,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl layout = $", set = {definition.Set}{layout}"; } - context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};"); + context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{arrayDecl};"); } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs index eb0cb92db..9e7f64b0e 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AggregateType type = GetSrcVarType(operation.Inst, 0); - string srcExpr = GetSoureExpr(context, src, type); + string srcExpr = GetSourceExpr(context, src, type); string zero; if (type == AggregateType.FP64) @@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++) { - builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}"); + builder.Append($", {GetSourceExpr(context, operation.GetSource(argIndex), dstType)}"); } } else @@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AggregateType dstType = GetSrcVarType(inst, argIndex); - builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType)); + builder.Append(GetSourceExpr(context, operation.GetSource(argIndex), dstType)); } } @@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions // Return may optionally have a return value (and in this case it is unary). if (inst == Instruction.Return && operation.SourcesCount != 0) { - return $"{op} {GetSoureExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; + return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; } int arity = (int)(info.Type & InstType.ArityMask); @@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { IAstNode src = operation.GetSource(index); - string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index)); bool isLhs = arity == 2 && index == 0; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs index 6cc7048bd..000d7f797 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AggregateType dstType = GetSrcVarType(operation.Inst, 0); - string arg = GetSoureExpr(context, operation.GetSource(0), dstType); + string arg = GetSourceExpr(context, operation.GetSource(0), dstType); char component = "xyzw"[operation.Index]; if (context.HostCapabilities.SupportsShaderBallot) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs index 0618ba8a3..d5448856d 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs @@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions for (int i = 0; i < args.Length; i++) { - args[i] = GetSoureExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); + args[i] = GetSourceExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); } return $"{function.Name}({string.Join(", ", args)})"; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs index 5c2d16f4c..4b28f3878 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions return _infoTable[(int)(inst & Instruction.Mask)]; } - public static string GetSoureExpr(CodeGenContext context, IAstNode node, AggregateType dstType) + public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType) { return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType); } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index 2e90bd16d..b4773b819 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -14,35 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - switch (texOp.Inst) - { - case Instruction.ImageStore: - return "// imageStore(bindless)"; - case Instruction.ImageLoad: - AggregateType componentType = texOp.Format.GetComponentType(); - - NumberFormatter.TryFormat(0, componentType, out string imageConst); - - AggregateType outputType = texOp.GetVectorType(componentType); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})"; - } - - return imageConst; - default: - return NumberFormatter.FormatInt(0); - } - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; var texCallBuilder = new StringBuilder(); @@ -70,21 +42,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); } - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; string Src(AggregateType type) { - return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + return GetSourceExpr(context, texOp.GetSource(srcIndex++), type); } - string indexExpr = null; - - if (isIndexed) - { - indexExpr = Src(AggregateType.S32); - } - - string imageName = GetImageName(context.Properties, texOp, indexExpr); + string imageName = GetImageName(context, texOp, ref srcIndex); texCallBuilder.Append('('); texCallBuilder.Append(imageName); @@ -198,27 +163,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AstTextureOperation texOp = (AstTextureOperation)operation; int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = 0; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatFloat(0); - } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); - - int coordsIndex = isBindless || isIndexed ? 1 : 0; + string samplerName = GetSamplerName(context, texOp, ref coordsIndex); string coordsExpr; @@ -228,14 +175,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions for (int index = 0; index < coordsCount; index++) { - elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); + elems[index] = GetSourceExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); } coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; } else { - coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); + coordsExpr = GetSourceExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); } return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; @@ -250,7 +197,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; @@ -260,12 +206,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; - bool colorIsVector = isGather || !isShadow; - SamplerType type = texOp.Type & SamplerType.Mask; bool is2D = type == SamplerType.Texture2D; @@ -286,24 +229,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions hasLodLevel = false; } - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - string scalarValue = NumberFormatter.FormatFloat(0); - - if (colorIsVector) - { - AggregateType outputType = texOp.GetVectorType(AggregateType.FP32); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})"; - } - } - - return scalarValue; - } - string texCall = intCoords ? "texelFetch" : "texture"; if (isGather) @@ -328,21 +253,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCall += "Offsets"; } - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; string Src(AggregateType type) { - return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + return GetSourceExpr(context, texOp.GetSource(srcIndex++), type); } - string indexExpr = null; - - if (isIndexed) - { - indexExpr = Src(AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = GetSamplerName(context, texOp, ref srcIndex); texCall += "(" + samplerName; @@ -512,6 +430,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Append(Src(AggregateType.S32)); } + bool colorIsVector = isGather || !isShadow; + texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); return texCall; @@ -521,24 +441,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + int srcIndex = 0; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatInt(0); - } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = GetSamplerName(context, texOp, ref srcIndex); return $"textureSamples({samplerName})"; } @@ -547,24 +452,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + int srcIndex = 0; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatInt(0); - } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = GetSamplerName(context, texOp, ref srcIndex); if (texOp.Index == 3) { @@ -578,9 +468,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (hasLod) { - int lodSrcIndex = isBindless || isIndexed ? 1 : 0; - IAstNode lod = operation.GetSource(lodSrcIndex); - string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex)); + IAstNode lod = operation.GetSource(srcIndex); + string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, srcIndex)); texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; } @@ -697,12 +586,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (storageKind == StorageKind.Input) { - string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); + string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); varName = $"gl_in[{expr}].{varName}"; } else if (storageKind == StorageKind.Output) { - string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); + string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); varName = $"gl_out[{expr}].{varName}"; } } @@ -735,38 +624,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else { - varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]"; + varName += $"[{GetSourceExpr(context, src, AggregateType.S32)}]"; } } if (isStore) { varType &= AggregateType.ElementTypeMask; - varName = $"{varName} = {GetSoureExpr(context, operation.GetSource(srcIndex), varType)}"; + varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}"; } return varName; } - private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) + private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) { - string name = resourceDefinitions.Textures[texOp.Binding].Name; + TextureDefinition definition = context.Properties.Textures[texOp.Binding]; + string name = definition.Name; - if (texOp.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength != 1) { - name = $"{name}[{indexExpr}]"; + name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; } return name; } - private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) + private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) { - string name = resourceDefinitions.Images[texOp.Binding].Name; + TextureDefinition definition = context.Properties.Images[texOp.Binding]; + string name = definition.Name; - if (texOp.Type.HasFlag(SamplerType.Indexed)) + if (definition.ArrayLength != 1) { - name = $"{name}[{indexExpr}]"; + name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; } return name; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs index ad84c4850..4469785d2 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -13,8 +13,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IAstNode src0 = operation.GetSource(0); IAstNode src1 = operation.GetSource(1); - string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); - string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1)); return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))"; } @@ -24,8 +24,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IAstNode src0 = operation.GetSource(0); IAstNode src1 = operation.GetSource(1); - string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); - string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1)); return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; } @@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { IAstNode src = operation.GetSource(0); - string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0)); return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}"; } @@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { IAstNode src = operation.GetSource(0); - string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0)); return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs index 6d3859efd..b72b94d90 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs @@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { public static string Shuffle(CodeGenContext context, AstOperation operation) { - string value = GetSoureExpr(context, operation.GetSource(0), AggregateType.FP32); - string index = GetSoureExpr(context, operation.GetSource(1), AggregateType.U32); + string value = GetSourceExpr(context, operation.GetSource(0), AggregateType.FP32); + string index = GetSourceExpr(context, operation.GetSource(1), AggregateType.U32); if (context.HostCapabilities.SupportsShaderBallot) { diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs index 70174a5ba..a300c7750 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IAstNode vector = operation.GetSource(0); IAstNode index = operation.GetSource(1); - string vectorExpr = GetSoureExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); + string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant) { @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else { - string indexExpr = GetSoureExpr(context, index, GetSrcVarType(operation.Inst, 1)); + string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1)); return $"{vectorExpr}[{indexExpr}]"; } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index 53ecc4531..a350b089c 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -146,9 +146,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } else if (operation is AstTextureOperation texOp) { - if (texOp.Inst == Instruction.ImageLoad || - texOp.Inst == Instruction.ImageStore || - texOp.Inst == Instruction.ImageAtomic) + if (texOp.Inst.IsImage()) { return texOp.GetVectorType(texOp.Format.GetComponentType()); } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs index 17c3eefe3..2b1fdf44c 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -34,8 +34,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public Dictionary SharedMemories { get; } = new(); public Dictionary SamplersTypes { get; } = new(); - public Dictionary Samplers { get; } = new(); - public Dictionary Images { get; } = new(); + public Dictionary Samplers { get; } = new(); + public Dictionary Images { get; } = new(); public Dictionary Inputs { get; } = new(); public Dictionary Outputs { get; } = new(); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index b74824255..9633c522e 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -181,9 +181,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var sampledImageType = context.TypeSampledImage(imageType); var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); - var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant); + var sampledImageArrayPointerType = sampledImagePointerType; - context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable)); + if (sampler.ArrayLength == 0) + { + var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + else if (sampler.ArrayLength != 1) + { + var sampledImageArrayType = context.TypeArray(sampledImageType, context.Constant(context.TypeU32(), sampler.ArrayLength)); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + + var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant); + + context.Samplers.Add(sampler.Binding, new SamplerDeclaration( + imageType, + sampledImageType, + sampledImagePointerType, + sampledImageVariable, + sampler.ArrayLength != 1)); context.SamplersTypes.Add(sampler.Binding, sampler.Type); context.Name(sampledImageVariable, sampler.Name); @@ -211,9 +229,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv GetImageFormat(image.Format)); var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); - var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant); + var imageArrayPointerType = imagePointerType; - context.Images.Add(image.Binding, (imageType, imageVariable)); + if (image.ArrayLength == 0) + { + var imageArrayType = context.TypeRuntimeArray(imageType); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + else if (image.ArrayLength != 1) + { + var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArrayLength)); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + + var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant); + + context.Images.Add(image.Binding, new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1)); context.Name(imageVariable, image.Name); context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs new file mode 100644 index 000000000..1e0aee734 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs @@ -0,0 +1,20 @@ +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct ImageDeclaration + { + public readonly Instruction ImageType; + public readonly Instruction ImagePointerType; + public readonly Instruction Image; + public readonly bool IsIndexed; + + public ImageDeclaration(Instruction imageType, Instruction imagePointerType, Instruction image, bool isIndexed) + { + ImageType = imageType; + ImagePointerType = imagePointerType; + Image = image; + IsIndexed = isIndexed; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 601753cb0..409e466cd 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -591,34 +591,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - var componentType = texOp.Format.GetComponentType(); - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return new OperationResult(componentType, componentType switch - { - AggregateType.S32 => context.Constant(context.TypeS32(), 0), - AggregateType.U32 => context.Constant(context.TypeU32(), 0u), - _ => context.Constant(context.TypeFP32(), 0f), - }); - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + ImageDeclaration declaration = context.Images[texOp.Binding]; + SpvInstruction image = declaration.Image; + + SpvInstruction resultType = context.GetType(componentType); + SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(imagePointerType, image, textureIndex); } int coordsCount = texOp.Type.GetDimensions(); @@ -646,14 +640,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv SpvInstruction value = Src(componentType); - (var imageType, var imageVariable) = context.Images[texOp.Binding]; - - context.Load(imageType, imageVariable); - - SpvInstruction resultType = context.GetType(componentType); - SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); - - var pointer = context.ImageTexelPointer(imagePointerType, imageVariable, pCoords, context.Constant(context.TypeU32(), 0)); + var pointer = context.ImageTexelPointer(imagePointerType, image, pCoords, context.Constant(context.TypeU32(), 0)); var one = context.Constant(context.TypeU32(), 1); var zero = context.Constant(context.TypeU32(), 0); @@ -683,31 +670,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - var componentType = texOp.Format.GetComponentType(); - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return GetZeroOperationResult(context, texOp, componentType, isVector: true); - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + ImageDeclaration declaration = context.Images[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); } + image = context.Load(declaration.ImageType, image); + int coordsCount = texOp.Type.GetDimensions(); int pCount = coordsCount + (isArray ? 1 : 0); @@ -731,9 +716,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.S32); } - (var imageType, var imageVariable) = context.Images[texOp.Binding]; - - var image = context.Load(imageType, imageVariable); var imageComponentType = context.GetType(componentType); var swizzledResultType = texOp.GetVectorType(componentType); @@ -747,29 +729,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return OperationResult.Invalid; - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + ImageDeclaration declaration = context.Images[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); } + image = context.Load(declaration.ImageType, image); + int coordsCount = texOp.Type.GetDimensions(); int pCount = coordsCount + (isArray ? 1 : 0); @@ -818,10 +798,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); - (var imageType, var imageVariable) = context.Images[texOp.Binding]; - - var image = context.Load(imageType, imageVariable); - context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); return OperationResult.Invalid; @@ -854,16 +830,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); - } - int srcIndex = 0; SpvInstruction Src(AggregateType type) @@ -871,11 +837,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } + image = context.Load(declaration.SampledImageType, image); + int pCount = texOp.Type.GetDimensions(); SpvInstruction pCoords; @@ -897,10 +870,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.FP32); } - (_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - var resultType = context.TypeVector(context.TypeFP32(), 2); var packed = context.ImageQueryLod(resultType, image, pCoords); var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index); @@ -1182,7 +1151,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; @@ -1192,30 +1160,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; - bool colorIsVector = isGather || !isShadow; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector); - } - - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) { - Src(AggregateType.S32); + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } + image = context.Load(declaration.SampledImageType, image); + int coordsCount = texOp.Type.GetDimensions(); int pCount = coordsCount; @@ -1419,15 +1385,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv operandsList.Add(sample); } + bool colorIsVector = isGather || !isShadow; + var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32(); - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - if (intCoords) { - image = context.Image(imageType, image); + image = context.Image(declaration.ImageType, image); } var operands = operandsList.ToArray(); @@ -1485,25 +1449,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) + if (declaration.IsIndexed) { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); + SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0)); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - if (isIndexed) - { - context.GetS32(texOp.GetSource(0)); - } - - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - image = context.Image(imageType, image); + image = context.Load(declaration.SampledImageType, image); + image = context.Image(declaration.ImageType, image); SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); @@ -1514,25 +1471,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { AstTextureOperation texOp = (AstTextureOperation)operation; - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + SamplerDeclaration declaration = context.Samplers[texOp.Binding]; + SpvInstruction image = declaration.Image; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) + if (declaration.IsIndexed) { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); + SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0)); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - if (isIndexed) - { - context.GetS32(texOp.GetSource(0)); - } - - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); - image = context.Image(imageType, image); + image = context.Load(declaration.SampledImageType, image); + image = context.Image(declaration.ImageType, image); if (texOp.Index == 3) { @@ -1556,7 +1506,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (hasLod) { - int lodSrcIndex = isBindless || isIndexed ? 1 : 0; + int lodSrcIndex = declaration.IsIndexed ? 1 : 0; var lod = context.GetS32(operation.GetSource(lodSrcIndex)); result = context.ImageQuerySizeLod(resultType, image, lod); } @@ -1929,38 +1879,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return context.Load(context.GetType(varType), context.Inputs[ioDefinition]); } - private static OperationResult GetZeroOperationResult( - CodeGenContext context, - AstTextureOperation texOp, - AggregateType scalarType, - bool isVector) - { - var zero = scalarType switch - { - AggregateType.S32 => context.Constant(context.TypeS32(), 0), - AggregateType.U32 => context.Constant(context.TypeU32(), 0u), - _ => context.Constant(context.TypeFP32(), 0f), - }; - - if (isVector) - { - AggregateType outputType = texOp.GetVectorType(scalarType); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - int componentsCount = BitOperations.PopCount((uint)texOp.Index); - - SpvInstruction[] values = new SpvInstruction[componentsCount]; - - values.AsSpan().Fill(zero); - - return new OperationResult(outputType, context.ConstantComposite(context.GetType(outputType), values)); - } - } - - return new OperationResult(scalarType, zero); - } - private static SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask) { if ((swizzledResultType & AggregateType.ElementCountMask) != 0) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs new file mode 100644 index 000000000..9e0ecd794 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs @@ -0,0 +1,27 @@ +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct SamplerDeclaration + { + public readonly Instruction ImageType; + public readonly Instruction SampledImageType; + public readonly Instruction SampledImagePointerType; + public readonly Instruction Image; + public readonly bool IsIndexed; + + public SamplerDeclaration( + Instruction imageType, + Instruction sampledImageType, + Instruction sampledImagePointerType, + Instruction image, + bool isIndexed) + { + ImageType = imageType; + SampledImageType = sampledImageType; + SampledImagePointerType = sampledImagePointerType; + Image = image; + IsIndexed = isIndexed; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index df6d29dc5..99366ad67 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -26,47 +26,42 @@ namespace Ryujinx.Graphics.Shader /// Span of the memory location ReadOnlySpan GetCode(ulong address, int minimumSize); + /// + /// Gets the size in bytes of a bound constant buffer for the current shader stage. + /// + /// The number of the constant buffer to get the size from + /// Size in bytes + int QueryTextureArrayLengthFromBuffer(int slot); + /// /// Queries the binding number of a constant buffer. /// /// Constant buffer index /// Binding number - int QueryBindingConstantBuffer(int index) - { - return index + 1; - } + int CreateConstantBufferBinding(int index); + + /// + /// Queries the binding number of an image. + /// + /// For array of images, the number of elements of the array, otherwise it should be 1 + /// Indicates if the image is a buffer image + /// Binding number + int CreateImageBinding(int count, bool isBuffer); /// /// Queries the binding number of a storage buffer. /// /// Storage buffer index /// Binding number - int QueryBindingStorageBuffer(int index) - { - return index; - } + int CreateStorageBufferBinding(int index); /// /// Queries the binding number of a texture. /// - /// Texture index + /// For array of textures, the number of elements of the array, otherwise it should be 1 /// Indicates if the texture is a buffer texture /// Binding number - int QueryBindingTexture(int index, bool isBuffer) - { - return index; - } - - /// - /// Queries the binding number of an image. - /// - /// Image index - /// Indicates if the image is a buffer image - /// Binding number - int QueryBindingImage(int index, bool isBuffer) - { - return index; - } + int CreateTextureBinding(int count, bool isBuffer); /// /// Queries Local Size X for compute shaders. diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index e5695ebc2..8703e660e 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -161,5 +161,17 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation inst &= Instruction.Mask; return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; } + + public static bool IsImage(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageAtomic || inst == Instruction.ImageLoad || inst == Instruction.ImageStore; + } + + public static bool IsImageStore(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageAtomic || inst == Instruction.ImageStore; + } } } diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs index f5396a884..0c1b2a3f3 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -20,13 +20,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation } set { - if (value != null && value.Type == OperandType.LocalVariable) - { - value.AsgOp = this; - } - if (value != null) { + if (value.Type == OperandType.LocalVariable) + { + value.AsgOp = this; + } + _dests = new[] { value }; } else diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs index fa5550a64..1b82e2945 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -26,9 +26,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Binding = binding; } - public void TurnIntoIndexed(int binding) + public void TurnIntoArray(int binding) { - Type |= SamplerType.Indexed; Flags &= ~TextureFlags.Bindless; Binding = binding; } diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs index 85e97368f..66c748bf3 100644 --- a/src/Ryujinx.Graphics.Shader/SamplerType.cs +++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs @@ -16,9 +16,8 @@ namespace Ryujinx.Graphics.Shader Mask = 0xff, Array = 1 << 8, - Indexed = 1 << 9, - Multisample = 1 << 10, - Shadow = 1 << 11, + Multisample = 1 << 9, + Shadow = 1 << 10, } static class SamplerTypeExtensions @@ -36,6 +35,36 @@ namespace Ryujinx.Graphics.Shader }; } + public static string ToShortSamplerType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "1d", + SamplerType.TextureBuffer => "b", + SamplerType.Texture2D => "2d", + SamplerType.Texture3D => "3d", + SamplerType.TextureCube => "cube", + _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "ms"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "a"; + } + + if ((type & SamplerType.Shadow) != 0) + { + typeName += "s"; + } + + return typeName; + } + public static string ToGlslSamplerType(this SamplerType type) { string typeName = (type & SamplerType.Mask) switch diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs index e45c82854..bdd3a2ed1 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs @@ -4,15 +4,17 @@ namespace Ryujinx.Graphics.Shader { public int Set { get; } public int Binding { get; } + public int ArrayLength { get; } public string Name { get; } public SamplerType Type { get; } public TextureFormat Format { get; } public TextureUsageFlags Flags { get; } - public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) + public TextureDefinition(int set, int binding, int arrayLength, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) { Set = set; Binding = binding; + ArrayLength = arrayLength; Name = name; Type = type; Format = format; @@ -21,7 +23,7 @@ namespace Ryujinx.Graphics.Shader public TextureDefinition SetFlag(TextureUsageFlags flag) { - return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag); + return new TextureDefinition(Set, Binding, ArrayLength, Name, Type, Format, Flags | flag); } } } diff --git a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs index 1130b63b8..38834da72 100644 --- a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs +++ b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -11,16 +11,25 @@ namespace Ryujinx.Graphics.Shader public readonly int CbufSlot; public readonly int HandleIndex; + public readonly int ArrayLength; public readonly TextureUsageFlags Flags; - public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex, TextureUsageFlags flags) + public TextureDescriptor( + int binding, + SamplerType type, + TextureFormat format, + int cbufSlot, + int handleIndex, + int arrayLength, + TextureUsageFlags flags) { Binding = binding; Type = type; Format = format; CbufSlot = cbufSlot; HandleIndex = handleIndex; + ArrayLength = arrayLength; Flags = flags; } } diff --git a/src/Ryujinx.Graphics.Shader/TextureHandle.cs b/src/Ryujinx.Graphics.Shader/TextureHandle.cs index fc9ab2d67..7df9c8e47 100644 --- a/src/Ryujinx.Graphics.Shader/TextureHandle.cs +++ b/src/Ryujinx.Graphics.Shader/TextureHandle.cs @@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader { (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset); - int handle = cachedTextureBuffer.Length != 0 ? cachedTextureBuffer[textureWordOffset] : 0; + int handle = textureWordOffset < cachedTextureBuffer.Length ? cachedTextureBuffer[textureWordOffset] : 0; // The "wordOffset" (which is really the immediate value used on texture instructions on the shader) // is a 13-bit value. However, in order to also support separate samplers and textures (which uses @@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.Shader if (handleType != TextureHandleType.SeparateConstantSamplerHandle) { - samplerHandle = cachedSamplerBuffer.Length != 0 ? cachedSamplerBuffer[samplerWordOffset] : 0; + samplerHandle = samplerWordOffset < cachedSamplerBuffer.Length ? cachedSamplerBuffer[samplerWordOffset] : 0; } else { diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs index a88903274..ad955278f 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs @@ -15,8 +15,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations // - The handle is a constant buffer value. // - The handle is the result of a bitwise OR logical operation. // - Both sources of the OR operation comes from a constant buffer. - for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + LinkedListNode nextNode; + + for (LinkedListNode node = block.Operations.First; node != null; node = nextNode) { + nextNode = node.Next; + if (node.Value is not TextureOperation texOp) { continue; @@ -27,185 +31,207 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations continue; } - if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) + if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp)) { - Operand bindlessHandle = texOp.GetSource(0); + // If we can't do bindless elimination, remove the texture operation. + // Set any destination variables to zero. - // In some cases the compiler uses a shuffle operation to get the handle, - // for some textureGrad implementations. In those cases, we can skip the shuffle. - if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle) + for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) { - bindlessHandle = shuffleOp.GetSource(0); + block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0))); } - bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); + Utils.DeleteNode(node, texOp); + } + } + } - // Some instructions do not encode an accurate sampler type: - // - Most instructions uses the same type for 1D and Buffer. - // - Query instructions may not have any type. - // For those cases, we need to try getting the type from current GPU state, - // as long bindless elimination is successful and we know where the texture descriptor is located. - bool rewriteSamplerType = - texOp.Type == SamplerType.TextureBuffer || - texOp.Inst == Instruction.TextureQuerySamples || - texOp.Inst == Instruction.TextureQuerySize; + private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp) + { + if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) + { + Operand bindlessHandle = texOp.GetSource(0); - if (bindlessHandle.Type == OperandType.ConstantBuffer) + // In some cases the compiler uses a shuffle operation to get the handle, + // for some textureGrad implementations. In those cases, we can skip the shuffle. + if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle) + { + bindlessHandle = shuffleOp.GetSource(0); + } + + bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); + + // Some instructions do not encode an accurate sampler type: + // - Most instructions uses the same type for 1D and Buffer. + // - Query instructions may not have any type. + // For those cases, we need to try getting the type from current GPU state, + // as long bindless elimination is successful and we know where the texture descriptor is located. + bool rewriteSamplerType = + texOp.Type == SamplerType.TextureBuffer || + texOp.Inst == Instruction.TextureQuerySamples || + texOp.Inst == Instruction.TextureQuerySize; + + if (bindlessHandle.Type == OperandType.ConstantBuffer) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + bindlessHandle.GetCbufOffset(), + bindlessHandle.GetCbufSlot(), + rewriteSamplerType, + isImage: false); + + return true; + } + + if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp)) + { + return false; + } + + if (handleCombineOp.Inst != Instruction.BitwiseOr) + { + return false; + } + + Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block); + Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block); + + // For cases where we have a constant, ensure that the constant is always + // the second operand. + // Since this is a commutative operation, both are fine, + // and having a "canonical" representation simplifies some checks below. + if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant) + { + (src0, src1) = (src1, src0); + } + + TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle; + + // Try to match the following patterns: + // Masked pattern: + // - samplerHandle = samplerHandle & 0xFFF00000; + // - textureHandle = textureHandle & 0xFFFFF; + // - combinedHandle = samplerHandle | textureHandle; + // Where samplerHandle and textureHandle comes from a constant buffer. + // Shifted pattern: + // - samplerHandle = samplerId << 20; + // - combinedHandle = samplerHandle | textureHandle; + // Where samplerId and textureHandle comes from a constant buffer. + // Constant pattern: + // - combinedHandle = samplerHandleConstant | textureHandle; + // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer. + if (src0.AsgOp is Operation src0AsgOp) + { + if (src1.AsgOp is Operation src1AsgOp && + src0AsgOp.Inst == Instruction.BitwiseAnd && + src1AsgOp.Inst == Instruction.BitwiseAnd) { - SetHandle( - resourceManager, - gpuAccessor, - texOp, - bindlessHandle.GetCbufOffset(), - bindlessHandle.GetCbufSlot(), - rewriteSamplerType, - isImage: false); + src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); + src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); - continue; - } - - if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp)) - { - continue; - } - - if (handleCombineOp.Inst != Instruction.BitwiseOr) - { - continue; - } - - Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block); - Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block); - - // For cases where we have a constant, ensure that the constant is always - // the second operand. - // Since this is a commutative operation, both are fine, - // and having a "canonical" representation simplifies some checks below. - if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant) - { - (src0, src1) = (src1, src0); - } - - TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle; - - // Try to match the following patterns: - // Masked pattern: - // - samplerHandle = samplerHandle & 0xFFF00000; - // - textureHandle = textureHandle & 0xFFFFF; - // - combinedHandle = samplerHandle | textureHandle; - // Where samplerHandle and textureHandle comes from a constant buffer. - // Shifted pattern: - // - samplerHandle = samplerId << 20; - // - combinedHandle = samplerHandle | textureHandle; - // Where samplerId and textureHandle comes from a constant buffer. - // Constant pattern: - // - combinedHandle = samplerHandleConstant | textureHandle; - // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer. - if (src0.AsgOp is Operation src0AsgOp) - { - if (src1.AsgOp is Operation src1AsgOp && - src0AsgOp.Inst == Instruction.BitwiseAnd && - src1AsgOp.Inst == Instruction.BitwiseAnd) + // The OR operation is commutative, so we can also try to swap the operands to get a match. + if (src0 == null || src1 == null) { - src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); - src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); - - // The OR operation is commutative, so we can also try to swap the operands to get a match. - if (src0 == null || src1 == null) - { - src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF); - src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000); - } - - if (src0 == null || src1 == null) - { - continue; - } + src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF); + src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000); } - else if (src0AsgOp.Inst == Instruction.ShiftLeft) - { - Operand shift = src0AsgOp.GetSource(1); - if (shift.Type == OperandType.Constant && shift.Value == 20) - { - src0 = src1; - src1 = src0AsgOp.GetSource(0); - handleType = TextureHandleType.SeparateSamplerId; - } + if (src0 == null || src1 == null) + { + return false; } } - else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) + else if (src0AsgOp.Inst == Instruction.ShiftLeft) { - Operand shift = src1AsgOp.GetSource(1); + Operand shift = src0AsgOp.GetSource(1); if (shift.Type == OperandType.Constant && shift.Value == 20) { - src1 = src1AsgOp.GetSource(0); + src0 = src1; + src1 = src0AsgOp.GetSource(0); handleType = TextureHandleType.SeparateSamplerId; } } - else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) - { - handleType = TextureHandleType.SeparateConstantSamplerHandle; - } + } + else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) + { + Operand shift = src1AsgOp.GetSource(1); - if (src0.Type != OperandType.ConstantBuffer) + if (shift.Type == OperandType.Constant && shift.Value == 20) { - continue; - } - - if (handleType == TextureHandleType.SeparateConstantSamplerHandle) - { - SetHandle( - resourceManager, - gpuAccessor, - texOp, - TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType), - TextureHandle.PackSlots(src0.GetCbufSlot(), 0), - rewriteSamplerType, - isImage: false); - } - else if (src1.Type == OperandType.ConstantBuffer) - { - SetHandle( - resourceManager, - gpuAccessor, - texOp, - TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType), - TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()), - rewriteSamplerType, - isImage: false); + src1 = src1AsgOp.GetSource(0); + handleType = TextureHandleType.SeparateSamplerId; } } - else if (texOp.Inst == Instruction.ImageLoad || - texOp.Inst == Instruction.ImageStore || - texOp.Inst == Instruction.ImageAtomic) + else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) { - Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); + handleType = TextureHandleType.SeparateConstantSamplerHandle; + } - if (src0.Type == OperandType.ConstantBuffer) - { - int cbufOffset = src0.GetCbufOffset(); - int cbufSlot = src0.GetCbufSlot(); + if (src0.Type != OperandType.ConstantBuffer) + { + return false; + } - if (texOp.Format == TextureFormat.Unknown) - { - if (texOp.Inst == Instruction.ImageAtomic) - { - texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); - } - else - { - texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); - } - } + if (handleType == TextureHandleType.SeparateConstantSamplerHandle) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType), + TextureHandle.PackSlots(src0.GetCbufSlot(), 0), + rewriteSamplerType, + isImage: false); - bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; + return true; + } + else if (src1.Type == OperandType.ConstantBuffer) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType), + TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()), + rewriteSamplerType, + isImage: false); - SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); - } + return true; } } + else if (texOp.Inst.IsImage()) + { + Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); + + if (src0.Type == OperandType.ConstantBuffer) + { + int cbufOffset = src0.GetCbufOffset(); + int cbufSlot = src0.GetCbufSlot(); + + if (texOp.Format == TextureFormat.Unknown) + { + if (texOp.Inst == Instruction.ImageAtomic) + { + texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); + } + else + { + texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); + } + } + + bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; + + SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); + + return true; + } + } + + return false; } private static bool TryGetOperation(INode asgOp, out Operation outOperation) diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs new file mode 100644 index 000000000..7543d1c24 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs @@ -0,0 +1,236 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BindlessToArray + { + private const int NvnTextureBufferIndex = 2; + private const int HardcodedArrayLengthOgl = 4; + + // 1 and 0 elements are not considered arrays anymore. + private const int MinimumArrayLength = 2; + + public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) + { + continue; + } + + if (handleAsgOp.Inst != Instruction.Load || + handleAsgOp.StorageKind != StorageKind.ConstantBuffer || + handleAsgOp.SourcesCount != 4) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + if (ldcSrc0.Type != OperandType.Constant || + !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || + src0CbufSlot != NvnTextureBufferIndex) + { + continue; + } + + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + // We expect field index 0 to be accessed. + if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) + { + continue; + } + + Operand ldcSrc2 = handleAsgOp.GetSource(2); + + // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. + // Might be not worth fixing since if that doesn't kick in, the result will be no texture + // to access anyway which is also wrong. + // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. + // Eventually, this should be entirely removed in favor of a implementation that supports true bindless + // texture access. + if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) + { + continue; + } + + if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) + { + continue; + } + + if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) + { + continue; + } + + Operand addSrc1 = addOp.GetSource(1); + + if (addSrc1.Type != OperandType.Constant) + { + continue; + } + + TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl); + + Operand index = Local(); + + Operand source = addOp.GetSource(0); + + Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); + + block.Operations.AddBefore(node, shrBy3); + + texOp.SetSource(0, index); + } + } + + public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) + { + continue; + } + + int secondaryCbufSlot = 0; + int secondaryCbufOffset = 0; + bool hasSecondaryHandle = false; + + if (handleAsgOp.Inst == Instruction.BitwiseOr) + { + Operand src0 = handleAsgOp.GetSource(0); + Operand src1 = handleAsgOp.GetSource(1); + + if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation) + { + handleAsgOp = src1.AsgOp as Operation; + secondaryCbufSlot = src0.GetCbufSlot(); + secondaryCbufOffset = src0.GetCbufOffset(); + hasSecondaryHandle = true; + } + else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer) + { + handleAsgOp = src0.AsgOp as Operation; + secondaryCbufSlot = src1.GetCbufSlot(); + secondaryCbufOffset = src1.GetCbufOffset(); + hasSecondaryHandle = true; + } + } + + if (handleAsgOp.Inst != Instruction.Load || + handleAsgOp.StorageKind != StorageKind.ConstantBuffer || + handleAsgOp.SourcesCount != 4) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + if (ldcSrc0.Type != OperandType.Constant || + !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot)) + { + continue; + } + + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + // We expect field index 0 to be accessed. + if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) + { + continue; + } + + Operand ldcVecIndex = handleAsgOp.GetSource(2); + Operand ldcElemIndex = handleAsgOp.GetSource(3); + + if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable) + { + continue; + } + + int cbufSlot; + int handleIndex; + + if (hasSecondaryHandle) + { + cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot); + handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle); + } + else + { + cbufSlot = src0CbufSlot; + handleIndex = 0; + } + + int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot)); + + TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length); + + Operand vecIndex = Local(); + Operand elemIndex = Local(); + Operand index = Local(); + Operand indexMin = Local(); + + block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1))); + block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1))); + block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex)); + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1))); + + texOp.SetSource(0, indexMin); + } + } + + private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length) + { + int binding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags & ~TextureFlags.Bindless, + cbufSlot, + handleIndex, + length); + + texOp.TurnIntoArray(binding); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs deleted file mode 100644 index 2bd31fe1b..000000000 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using System.Collections.Generic; - -using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; - -namespace Ryujinx.Graphics.Shader.Translation.Optimizations -{ - static class BindlessToIndexed - { - private const int NvnTextureBufferIndex = 2; - - public static void RunPass(BasicBlock block, ResourceManager resourceManager) - { - // We can turn a bindless texture access into a indexed access, - // as long the following conditions are true: - // - The handle is loaded using a LDC instruction. - // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). - // - The load has a constant offset. - // The base offset of the array of handles on the constant buffer is the constant offset. - for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) - { - if (node.Value is not TextureOperation texOp) - { - continue; - } - - if ((texOp.Flags & TextureFlags.Bindless) == 0) - { - continue; - } - - if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) - { - continue; - } - - if (handleAsgOp.Inst != Instruction.Load || - handleAsgOp.StorageKind != StorageKind.ConstantBuffer || - handleAsgOp.SourcesCount != 4) - { - continue; - } - - Operand ldcSrc0 = handleAsgOp.GetSource(0); - - if (ldcSrc0.Type != OperandType.Constant || - !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || - src0CbufSlot != NvnTextureBufferIndex) - { - continue; - } - - Operand ldcSrc1 = handleAsgOp.GetSource(1); - - // We expect field index 0 to be accessed. - if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) - { - continue; - } - - Operand ldcSrc2 = handleAsgOp.GetSource(2); - - // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. - // Might be not worth fixing since if that doesn't kick in, the result will be no texture - // to access anyway which is also wrong. - // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. - // Eventually, this should be entirely removed in favor of a implementation that supports true bindless - // texture access. - if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) - { - continue; - } - - if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) - { - continue; - } - - if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) - { - continue; - } - - Operand addSrc1 = addOp.GetSource(1); - - if (addSrc1.Type != OperandType.Constant) - { - continue; - } - - TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4); - - Operand index = Local(); - - Operand source = addOp.GetSource(0); - - Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); - - block.Operations.AddBefore(node, shrBy3); - - texOp.SetSource(0, index); - } - } - - private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle) - { - int binding = resourceManager.GetTextureOrImageBinding( - texOp.Inst, - texOp.Type | SamplerType.Indexed, - texOp.Format, - texOp.Flags & ~TextureFlags.Bindless, - NvnTextureBufferIndex, - handle); - - texOp.TurnIntoIndexed(binding); - } - } -} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index ea06691ba..49eb3a89b 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -20,7 +20,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations // Those passes are looking for specific patterns and only needs to run once. for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) { - BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager); + if (context.TargetApi == TargetApi.OpenGL) + { + BindlessToArray.RunPassOgl(context.Blocks[blkIndex], context.ResourceManager); + } + else + { + BindlessToArray.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); + } + BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); // FragmentCoord only exists on fragment shaders, so we don't need to check other stages. diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs index 83332711f..e9fe0b1ee 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -14,9 +14,6 @@ namespace Ryujinx.Graphics.Shader.Translation private const int DefaultLocalMemorySize = 128; private const int DefaultSharedMemorySize = 4096; - // TODO: Non-hardcoded array size. - public const int SamplerArraySize = 4; - private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; private readonly IGpuAccessor _gpuAccessor; @@ -32,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly HashSet _usedConstantBufferBindings; - private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format); + private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, SamplerType Type, TextureFormat Format); private struct TextureMeta { @@ -152,7 +149,7 @@ namespace Ryujinx.Graphics.Shader.Translation int binding = _cbSlotToBindingMap[slot]; if (binding < 0) { - binding = _gpuAccessor.QueryBindingConstantBuffer(slot); + binding = _gpuAccessor.CreateConstantBufferBinding(slot); _cbSlotToBindingMap[slot] = binding; string slotNumber = slot.ToString(CultureInfo.InvariantCulture); AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); @@ -173,7 +170,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (binding < 0) { - binding = _gpuAccessor.QueryBindingStorageBuffer(slot); + binding = _gpuAccessor.CreateStorageBufferBinding(slot); _sbSlotToBindingMap[slot] = binding; string slotNumber = slot.ToString(CultureInfo.InvariantCulture); AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); @@ -227,11 +224,12 @@ namespace Ryujinx.Graphics.Shader.Translation TextureFormat format, TextureFlags flags, int cbufSlot, - int handle) + int handle, + int arrayLength = 1) { inst &= Instruction.Mask; - bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; - bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; + bool isImage = inst.IsImage(); + bool isWrite = inst.IsImageStore(); bool accurateType = !inst.IsTextureQuery(); bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize; bool coherent = flags.HasFlag(TextureFlags.Coherent); @@ -241,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Translation format = TextureFormat.Unknown; } - int binding = GetTextureOrImageBinding(cbufSlot, handle, type, format, isImage, intCoords, isWrite, accurateType, coherent); + int binding = GetTextureOrImageBinding(cbufSlot, handle, arrayLength, type, format, isImage, intCoords, isWrite, accurateType, coherent); _gpuAccessor.RegisterTexture(handle, cbufSlot); @@ -251,6 +249,7 @@ namespace Ryujinx.Graphics.Shader.Translation private int GetTextureOrImageBinding( int cbufSlot, int handle, + int arrayLength, SamplerType type, TextureFormat format, bool isImage, @@ -260,7 +259,6 @@ namespace Ryujinx.Graphics.Shader.Translation bool coherent) { var dimensions = type.GetDimensions(); - var isIndexed = type.HasFlag(SamplerType.Indexed); var dict = isImage ? _usedImages : _usedTextures; var usageFlags = TextureUsageFlags.None; @@ -269,7 +267,7 @@ namespace Ryujinx.Graphics.Shader.Translation { usageFlags |= TextureUsageFlags.NeedsScaleValue; - var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2; + var canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2; if (!canScale) { @@ -289,76 +287,75 @@ namespace Ryujinx.Graphics.Shader.Translation usageFlags |= TextureUsageFlags.ImageCoherent; } - int arraySize = isIndexed ? SamplerArraySize : 1; - int firstBinding = -1; - - for (int layer = 0; layer < arraySize; layer++) + // For array textures, we also want to use type as key, + // since we may have texture handles stores in the same buffer, but for textures with different types. + var keyType = arrayLength > 1 ? type : SamplerType.None; + var info = new TextureInfo(cbufSlot, handle, arrayLength, keyType, format); + var meta = new TextureMeta() { - var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format); - var meta = new TextureMeta() - { - AccurateType = accurateType, - Type = type, - UsageFlags = usageFlags, - }; + AccurateType = accurateType, + Type = type, + UsageFlags = usageFlags, + }; - int binding; + int binding; - if (dict.TryGetValue(info, out var existingMeta)) - { - dict[info] = MergeTextureMeta(meta, existingMeta); - binding = existingMeta.Binding; - } - else - { - bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; + if (dict.TryGetValue(info, out var existingMeta)) + { + dict[info] = MergeTextureMeta(meta, existingMeta); + binding = existingMeta.Binding; + } + else + { + bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; - binding = isImage - ? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) - : _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); + binding = isImage + ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer) + : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer); - meta.Binding = binding; + meta.Binding = binding; - dict.Add(info, meta); - } - - string nameSuffix; - - if (isImage) - { - nameSuffix = cbufSlot < 0 - ? $"i_tcb_{handle:X}_{format.ToGlslFormat()}" - : $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; - } - else - { - nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}"; - } - - var definition = new TextureDefinition( - isImage ? 3 : 2, - binding, - $"{_stagePrefix}_{nameSuffix}", - meta.Type, - info.Format, - meta.UsageFlags); - - if (isImage) - { - Properties.AddOrUpdateImage(definition); - } - else - { - Properties.AddOrUpdateTexture(definition); - } - - if (layer == 0) - { - firstBinding = binding; - } + dict.Add(info, meta); } - return firstBinding; + string nameSuffix; + string prefix = isImage ? "i" : "t"; + + if (arrayLength != 1 && type != SamplerType.None) + { + prefix += type.ToShortSamplerType(); + } + + if (isImage) + { + nameSuffix = cbufSlot < 0 + ? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}" + : $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; + } + else + { + nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}"; + } + + var definition = new TextureDefinition( + isImage ? 3 : 2, + binding, + arrayLength, + $"{_stagePrefix}_{nameSuffix}", + meta.Type, + info.Format, + meta.UsageFlags); + + if (isImage) + { + Properties.AddOrUpdateImage(definition); + } + else + { + Properties.AddOrUpdateTexture(definition); + } + + return binding; } private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) @@ -399,8 +396,7 @@ namespace Ryujinx.Graphics.Shader.Translation selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; var dimensions = type.GetDimensions(); - var isIndexed = type.HasFlag(SamplerType.Indexed); - var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2; + var canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2; if (!canScale) { @@ -468,34 +464,61 @@ namespace Ryujinx.Graphics.Shader.Translation return descriptors; } - public TextureDescriptor[] GetTextureDescriptors() + public TextureDescriptor[] GetTextureDescriptors(bool includeArrays = true) { - return GetDescriptors(_usedTextures, _usedTextures.Count); + return GetDescriptors(_usedTextures, includeArrays); } - public TextureDescriptor[] GetImageDescriptors() + public TextureDescriptor[] GetImageDescriptors(bool includeArrays = true) { - return GetDescriptors(_usedImages, _usedImages.Count); + return GetDescriptors(_usedImages, includeArrays); } - private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary usedResources, int count) + private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary usedResources, bool includeArrays) { - TextureDescriptor[] descriptors = new TextureDescriptor[count]; + List descriptors = new(); - int descriptorIndex = 0; + bool hasAnyArray = false; foreach ((TextureInfo info, TextureMeta meta) in usedResources) { - descriptors[descriptorIndex++] = new TextureDescriptor( + if (info.ArrayLength > 1) + { + hasAnyArray = true; + continue; + } + + descriptors.Add(new TextureDescriptor( meta.Binding, meta.Type, info.Format, info.CbufSlot, info.Handle, - meta.UsageFlags); + info.ArrayLength, + meta.UsageFlags)); } - return descriptors; + if (hasAnyArray && includeArrays) + { + foreach ((TextureInfo info, TextureMeta meta) in usedResources) + { + if (info.ArrayLength <= 1) + { + continue; + } + + descriptors.Add(new TextureDescriptor( + meta.Binding, + meta.Type, + info.Format, + info.CbufSlot, + info.Handle, + info.ArrayLength, + meta.UsageFlags)); + } + } + + return descriptors.ToArray(); } public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle) @@ -531,6 +554,19 @@ namespace Ryujinx.Graphics.Shader.Translation return FindDescriptorIndex(GetImageDescriptors(), binding); } + public bool IsArrayOfTexturesOrImages(int binding, bool isImage) + { + foreach ((TextureInfo info, TextureMeta meta) in isImage ? _usedImages : _usedTextures) + { + if (meta.Binding == binding) + { + return info.ArrayLength != 1; + } + } + + return false; + } + private void AddNewConstantBuffer(int binding, string name) { StructureType type = new(new[] diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs index 87ebb8e7c..1e87585f1 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation public readonly ShaderDefinitions Definitions; public readonly ResourceManager ResourceManager; public readonly IGpuAccessor GpuAccessor; + public readonly TargetApi TargetApi; public readonly TargetLanguage TargetLanguage; public readonly ShaderStage Stage; public readonly ref FeatureFlags UsedFeatures; @@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Shader.Translation ShaderDefinitions definitions, ResourceManager resourceManager, IGpuAccessor gpuAccessor, + TargetApi targetApi, TargetLanguage targetLanguage, ShaderStage stage, ref FeatureFlags usedFeatures) @@ -28,6 +30,7 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions = definitions; ResourceManager = resourceManager; GpuAccessor = gpuAccessor; + TargetApi = targetApi; TargetLanguage = targetLanguage; Stage = stage; UsedFeatures = ref usedFeatures; diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index 495ea8a94..072b45695 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage); node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor); - node = InsertConstOffsets(node, context.GpuAccessor, context.Stage); + node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor, context.Stage); if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) { @@ -45,13 +45,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - int coordsCount = texOp.Type.GetDimensions(); - - int coordsIndex = isBindless || isIndexed ? 1 : 0; bool isImage = IsImageInstructionWithScale(texOp.Inst); + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage); if ((texOp.Inst == Instruction.TextureSample || isImage) && (intCoords || isImage) && @@ -62,9 +58,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); int samplerIndex = isImage - ? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) + ? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) : resourceManager.FindTextureDescriptorIndex(texOp.Binding); + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = isBindless ? 1 : 0; + for (int index = 0; index < coordsCount; index++) { Operand scaledCoord = Local(); @@ -97,7 +96,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms TextureOperation texOp = (TextureOperation)node.Value; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); if (texOp.Inst == Instruction.TextureQuerySize && texOp.Index < 2 && @@ -152,8 +151,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms TextureOperation texOp = (TextureOperation)node.Value; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); - if (isBindless || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) + if (isBindless || isIndexed || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) { return node; } @@ -167,10 +167,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int coordsCount = texOp.Type.GetDimensions(); - int coordsIndex = isBindless || isIndexed ? 1 : 0; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; @@ -178,16 +175,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { Operand coordSize = Local(); - Operand[] texSizeSources; - - if (isBindless || isIndexed) - { - texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; - } - else - { - texSizeSources = new Operand[] { Const(0) }; - } + Operand[] texSizeSources = new Operand[] { Const(0) }; LinkedListNode textureSizeNode = node.List.AddBefore(node, new TextureOperation( Instruction.TextureQuerySize, @@ -201,13 +189,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type); - Operand source = texOp.GetSource(coordsIndex + index); + Operand source = texOp.GetSource(index); Operand coordNormalized = Local(); node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize))); - texOp.SetSource(coordsIndex + index, coordNormalized); + texOp.SetSource(index, coordNormalized); InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); } @@ -234,7 +222,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); int coordsCount = texOp.Type.GetDimensions(); int coordsIndex = isBindless || isIndexed ? 1 : 0; @@ -287,7 +275,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - private static LinkedListNode InsertConstOffsets(LinkedListNode node, IGpuAccessor gpuAccessor, ShaderStage stage) + private static LinkedListNode InsertConstOffsets(LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage) { // Non-constant texture offsets are not allowed (according to the spec), // however some GPUs does support that. @@ -321,7 +309,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; @@ -342,6 +329,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms offsetsCount = 0; } + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); + Operand[] offsets = new Operand[offsetsCount]; Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index a193ab3c4..581f4372c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -294,6 +294,7 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions, resourceManager, GpuAccessor, + Options.TargetApi, Options.TargetLanguage, Definitions.Stage, ref usedFeatures); @@ -412,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (Stage == ShaderStage.Vertex) { int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; - TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition indexBuffer = new(2, ibBinding, 1, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); resourceManager.Properties.AddOrUpdateTexture(indexBuffer); int inputMap = _program.AttributeUsage.UsedInputAttributes; @@ -421,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation { int location = BitOperations.TrailingZeroCount(inputMap); int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); - TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition vaBuffer = new(2, binding, 1, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); resourceManager.Properties.AddOrUpdateTexture(vaBuffer); inputMap &= ~(1 << location); @@ -430,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation else if (Stage == ShaderStage.Geometry) { int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; - TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + TextureDefinition remapBuffer = new(2, trbBinding, 1, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); resourceManager.Properties.AddOrUpdateTexture(remapBuffer); int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs index edf699dc3..3f65e1225 100644 --- a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -1,5 +1,7 @@ +using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using System; +using System.Buffers; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; @@ -291,16 +293,14 @@ namespace Ryujinx.Graphics.Texture.Astc int depth, int levels, int layers, - out byte[] decoded) + out IMemoryOwner decoded) { - byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels, layers)]; + decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers)); - AstcDecoder decoder = new(data, output, blockWidth, blockHeight, width, height, depth, levels, layers); + AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers); Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); - decoded = output; - return decoder.Success; } diff --git a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs index 2d68ca346..eb85334a2 100644 --- a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -12,7 +14,7 @@ namespace Ryujinx.Graphics.Texture private const int BlockWidth = 4; private const int BlockHeight = 4; - public static byte[] DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -21,12 +23,12 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output); + Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -100,7 +102,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -109,12 +111,12 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output); + Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -195,7 +197,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -204,13 +206,13 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span rPal = stackalloc byte[8]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output); + Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -292,7 +294,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static IMemoryOwner DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -304,8 +306,8 @@ namespace Ryujinx.Graphics.Texture // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. int alignedWidth = BitUtils.AlignUp(width, 4); - byte[] output = new byte[size]; - Span outputSpan = new(output); + IMemoryOwner output = ByteMemoryPool.Rent(size); + Span outputSpan = output.Memory.Span; ReadOnlySpan data64 = MemoryMarshal.Cast(data); @@ -400,7 +402,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static IMemoryOwner DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -412,7 +414,7 @@ namespace Ryujinx.Graphics.Texture // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. int alignedWidth = BitUtils.AlignUp(width, 2); - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); ReadOnlySpan data64 = MemoryMarshal.Cast(data); @@ -421,7 +423,7 @@ namespace Ryujinx.Graphics.Texture Span rPal = stackalloc byte[8]; Span gPal = stackalloc byte[8]; - Span outputAsUshort = MemoryMarshal.Cast(output); + Span outputAsUshort = MemoryMarshal.Cast(output.Memory.Span); Span rTileAsUint = MemoryMarshal.Cast(rTile); Span gTileAsUint = MemoryMarshal.Cast(gTile); @@ -525,7 +527,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static IMemoryOwner DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -534,7 +536,8 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); + Span outputSpan = output.Memory.Span; int inputOffset = 0; int outputOffset = 0; @@ -548,7 +551,7 @@ namespace Ryujinx.Graphics.Texture { for (int z = 0; z < depth; z++) { - BC6Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height, signed); + BC6Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height, signed); inputOffset += w * h * 16; outputOffset += width * height * 8; @@ -563,7 +566,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -572,7 +575,8 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); + Span outputSpan = output.Memory.Span; int inputOffset = 0; int outputOffset = 0; @@ -586,7 +590,7 @@ namespace Ryujinx.Graphics.Texture { for (int z = 0; z < depth; z++) { - BC7Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height); + BC7Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height); inputOffset += w * h * 16; outputOffset += width * height * 4; diff --git a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs index 8103990ff..253ba305c 100644 --- a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs +++ b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs @@ -1,6 +1,8 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.Texture.Encoders; using System; +using System.Buffers; namespace Ryujinx.Graphics.Texture { @@ -9,7 +11,7 @@ namespace Ryujinx.Graphics.Texture private const int BlockWidth = 4; private const int BlockHeight = 4; - public static byte[] EncodeBC7(byte[] data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner EncodeBC7(Memory data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -21,7 +23,8 @@ namespace Ryujinx.Graphics.Texture size += w * h * 16 * Math.Max(1, depth >> l) * layers; } - byte[] output = new byte[size]; + IMemoryOwner output = ByteMemoryPool.Rent(size); + Memory outputMemory = output.Memory; int imageBaseIOffs = 0; int imageBaseOOffs = 0; @@ -36,8 +39,8 @@ namespace Ryujinx.Graphics.Texture for (int z = 0; z < depth; z++) { BC7Encoder.Encode( - output.AsMemory()[imageBaseOOffs..], - data.AsMemory()[imageBaseIOffs..], + outputMemory[imageBaseOOffs..], + data[imageBaseIOffs..], width, height, EncodeMode.Fast | EncodeMode.Multithreaded); diff --git a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs index 57f2e98d0..52801ff47 100644 --- a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs +++ b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; @@ -49,15 +51,15 @@ namespace Ryujinx.Graphics.Texture new int[] { -3, -5, -7, -9, 2, 4, 6, 8 }, }; - public static byte[] DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; + IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output); + Span outputUint = MemoryMarshal.Cast(output.Memory.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; @@ -111,15 +113,15 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; + IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output); + Span outputUint = MemoryMarshal.Cast(output.Memory.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; @@ -168,15 +170,15 @@ namespace Ryujinx.Graphics.Texture return output; } - public static byte[] DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static IMemoryOwner DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; + IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output); + Span outputUint = MemoryMarshal.Cast(output.Memory.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs index d9a666c3f..d6732674b 100644 --- a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Runtime.Intrinsics; using static Ryujinx.Graphics.Texture.BlockLinearConstants; @@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.Texture }; } - public static byte[] ConvertBlockLinearToLinear( + public static IMemoryOwner ConvertBlockLinearToLinear( int width, int height, int depth, @@ -119,7 +121,8 @@ namespace Ryujinx.Graphics.Texture blockHeight, bytesPerPixel); - byte[] output = new byte[outSize]; + IMemoryOwner outputOwner = ByteMemoryPool.Rent(outSize); + Span output = outputOwner.Memory.Span; int outOffs = 0; @@ -243,10 +246,10 @@ namespace Ryujinx.Graphics.Texture _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), }; } - return output; + return outputOwner; } - public static byte[] ConvertLinearStridedToLinear( + public static IMemoryOwner ConvertLinearStridedToLinear( int width, int height, int blockWidth, @@ -262,8 +265,8 @@ namespace Ryujinx.Graphics.Texture int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); lineSize = Math.Min(lineSize, outStride); - byte[] output = new byte[h * outStride]; - Span outSpan = output; + IMemoryOwner output = ByteMemoryPool.Rent(h * outStride); + Span outSpan = output.Memory.Span; int outOffs = 0; int inOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/PixelConverter.cs b/src/Ryujinx.Graphics.Texture/PixelConverter.cs index 7955aed3f..4475cc98a 100644 --- a/src/Ryujinx.Graphics.Texture/PixelConverter.cs +++ b/src/Ryujinx.Graphics.Texture/PixelConverter.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -19,13 +21,13 @@ namespace Ryujinx.Graphics.Texture return (remainder, outRemainder, length / stride); } - public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) + public unsafe static IMemoryOwner ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); if (remainder == 0) { @@ -36,7 +38,7 @@ namespace Ryujinx.Graphics.Texture int sizeTrunc = data.Length & ~7; start = sizeTrunc; - fixed (byte* inputPtr = data, outputPtr = output) + fixed (byte* inputPtr = data, outputPtr = output.Memory.Span) { for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) { @@ -47,7 +49,7 @@ namespace Ryujinx.Graphics.Texture for (int i = start; i < data.Length; i++) { - outputSpan[i] = (ushort)data[i]; + outputSpan[i] = data[i]; } } else @@ -70,16 +72,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) + public static IMemoryOwner ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { @@ -107,16 +109,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) + public static IMemoryOwner ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { @@ -144,16 +146,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) + public static IMemoryOwner ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { @@ -181,16 +183,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public unsafe static byte[] ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) + public static IMemoryOwner ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) { - byte[] output = new byte[data.Length * 2]; + IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output); + Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); for (int y = 0; y < height; y++) { diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs index 7594384d6..707ae1292 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan { class DescriptorSetManager : IDisposable { - public const uint MaxSets = 16; + public const uint MaxSets = 8; public class DescriptorPoolHolder : IDisposable { diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 765686025..a0010e660 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; @@ -14,6 +15,9 @@ namespace Ryujinx.Graphics.Vulkan class DescriptorSetUpdater { private const ulong StorageBufferMaxMirrorable = 0x2000; + + private const int ArrayGrowthSize = 16; + private record struct BufferRef { public Auto Buffer; @@ -65,6 +69,18 @@ namespace Ryujinx.Graphics.Vulkan } } + private record struct ArrayRef + { + public ShaderStage Stage; + public T Array; + + public ArrayRef(ShaderStage stage, T array) + { + Stage = stage; + Array = array; + } + } + private readonly VulkanRenderer _gd; private readonly Device _device; private readonly PipelineBase _pipeline; @@ -78,6 +94,9 @@ namespace Ryujinx.Graphics.Vulkan private readonly TextureBuffer[] _bufferImageRefs; private readonly Format[] _bufferImageFormats; + private ArrayRef[] _textureArrayRefs; + private ArrayRef[] _imageArrayRefs; + private readonly DescriptorBufferInfo[] _uniformBuffers; private readonly DescriptorBufferInfo[] _storageBuffers; private readonly DescriptorImageInfo[] _textures; @@ -130,6 +149,9 @@ namespace Ryujinx.Graphics.Vulkan _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageFormats = new Format[Constants.MaxImageBindings * 2]; + _textureArrayRefs = Array.Empty>(); + _imageArrayRefs = Array.Empty>(); + _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings]; _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings]; _textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage]; @@ -195,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize() { - Span dummyTextureData = stackalloc byte[4]; + IMemoryOwner dummyTextureData = ByteMemoryPool.RentCleared(4); _dummyTexture.SetData(dummyTextureData); } @@ -263,10 +285,18 @@ namespace Ryujinx.Graphics.Vulkan { if (segment.Type == ResourceType.TextureAndSampler) { - for (int i = 0; i < segment.Count; i++) + if (!segment.IsArray) { - ref var texture = ref _textureRefs[segment.Binding + i]; - texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); + for (int i = 0; i < segment.Count; i++) + { + ref var texture = ref _textureRefs[segment.Binding + i]; + texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); + } + } + else + { + PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags(); + _textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } @@ -275,10 +305,18 @@ namespace Ryujinx.Graphics.Vulkan { if (segment.Type == ResourceType.Image) { - for (int i = 0; i < segment.Count; i++) + if (!segment.IsArray) { - ref var image = ref _imageRefs[segment.Binding + i]; - image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); + for (int i = 0; i < segment.Count; i++) + { + ref var image = ref _imageRefs[segment.Binding + i]; + image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); + } + } + else + { + PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags(); + _imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags); } } } @@ -455,6 +493,58 @@ namespace Ryujinx.Graphics.Vulkan } } + public void SetTextureArray(CommandBufferScoped cbs, ShaderStage stage, int binding, ITextureArray array) + { + if (_textureArrayRefs.Length <= binding) + { + Array.Resize(ref _textureArrayRefs, binding + ArrayGrowthSize); + } + + if (_textureArrayRefs[binding].Stage != stage || _textureArrayRefs[binding].Array != array) + { + if (_textureArrayRefs[binding].Array != null) + { + _textureArrayRefs[binding].Array.Bound = false; + } + + if (array is TextureArray textureArray) + { + textureArray.Bound = true; + textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + _textureArrayRefs[binding] = new ArrayRef(stage, array as TextureArray); + + SignalDirty(DirtyFlags.Texture); + } + } + + public void SetImageArray(CommandBufferScoped cbs, ShaderStage stage, int binding, IImageArray array) + { + if (_imageArrayRefs.Length <= binding) + { + Array.Resize(ref _imageArrayRefs, binding + ArrayGrowthSize); + } + + if (_imageArrayRefs[binding].Stage != stage || _imageArrayRefs[binding].Array != array) + { + if (_imageArrayRefs[binding].Array != null) + { + _imageArrayRefs[binding].Array.Bound = false; + } + + if (array is ImageArray imageArray) + { + imageArray.Bound = true; + imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + _imageArrayRefs[binding] = new ArrayRef(stage, array as ImageArray); + + SignalDirty(DirtyFlags.Image); + } + } + public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers) { for (int i = 0; i < buffers.Length; i++) @@ -653,66 +743,94 @@ namespace Ryujinx.Graphics.Vulkan } else if (setIndex == PipelineBase.TextureSetIndex) { - if (segment.Type != ResourceType.BufferTexture) + if (!segment.IsArray) { - Span textures = _textures; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferTexture) { - ref var texture = ref textures[i]; - ref var refs = ref _textureRefs[binding + i]; + Span textures = _textures; - texture.ImageView = refs.View?.Get(cbs).Value ?? default; - texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; - - if (texture.ImageView.Handle == 0) + for (int i = 0; i < count; i++) { - texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[binding + i]; + + texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; + } } - if (texture.Sampler.Handle == 0) - { - texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; - } + tu.Push(textures[..count]); } + else + { + Span bufferTextures = _bufferTextures; - tu.Push(textures[..count]); + for (int i = 0; i < count; i++) + { + bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + } + + tu.Push(bufferTextures[..count]); + } } else { - Span bufferTextures = _bufferTextures; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferTexture) { - bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + tu.Push(_textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler)); + } + else + { + tu.Push(_textureArrayRefs[binding].Array.GetBufferViews(cbs)); } - - tu.Push(bufferTextures[..count]); } } else if (setIndex == PipelineBase.ImageSetIndex) { - if (segment.Type != ResourceType.BufferImage) + if (!segment.IsArray) { - Span images = _images; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferImage) { - images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; - } + Span images = _images; - tu.Push(images[..count]); + for (int i = 0; i < count; i++) + { + images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; + } + + tu.Push(images[..count]); + } + else + { + Span bufferImages = _bufferImages; + + for (int i = 0; i < count; i++) + { + bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; + } + + tu.Push(bufferImages[..count]); + } } else { - Span bufferImages = _bufferImages; - - for (int i = 0; i < count; i++) + if (segment.Type != ResourceType.BufferTexture) { - bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; + tu.Push(_imageArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture)); + } + else + { + tu.Push(_imageArrayRefs[binding].Array.GetBufferViews(cbs)); } - - tu.Push(bufferImages[..count]); } } } @@ -825,6 +943,16 @@ namespace Ryujinx.Graphics.Vulkan AdvancePdSequence(); } + public void ForceTextureDirty() + { + SignalDirty(DirtyFlags.Texture); + } + + public void ForceImageDirty() + { + SignalDirty(DirtyFlags.Image); + } + private static void SwapBuffer(BufferRef[] list, Auto from, Auto to) { for (int i = 0; i < list.Length; i++) diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index 259be9d64..08e07f256 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -174,8 +174,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects SwizzleComponent.Blue, SwizzleComponent.Alpha); - var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin"); - var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin"); + var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin"); + var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin"); _areaTexture = _renderer.CreateTexture(areaInfo) as TextureView; _searchTexture = _renderer.CreateTexture(searchInfo) as TextureView; diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs new file mode 100644 index 000000000..38a5b6b48 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs @@ -0,0 +1,179 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class ImageArray : IImageArray + { + private readonly VulkanRenderer _gd; + + private record struct TextureRef + { + public TextureStorage Storage; + public TextureView View; + public GAL.Format ImageFormat; + } + + private readonly TextureRef[] _textureRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + + private readonly DescriptorImageInfo[] _textures; + private readonly BufferView[] _bufferTextures; + + private HashSet _storages; + + private int _cachedCommandBufferIndex; + private int _cachedSubmissionCount; + + private readonly bool _isBuffer; + + public bool Bound; + + public ImageArray(VulkanRenderer gd, int size, bool isBuffer) + { + _gd = gd; + + if (isBuffer) + { + _bufferTextureRefs = new TextureBuffer[size]; + _bufferTextures = new BufferView[size]; + } + else + { + _textureRefs = new TextureRef[size]; + _textures = new DescriptorImageInfo[size]; + } + + _storages = null; + + _cachedCommandBufferIndex = -1; + _cachedSubmissionCount = 0; + + _isBuffer = isBuffer; + } + + public void SetFormats(int index, GAL.Format[] imageFormats) + { + for (int i = 0; i < imageFormats.Length; i++) + { + _textureRefs[index + i].ImageFormat = imageFormats[i]; + } + + SetDirty(); + } + + public void SetImages(int index, ITexture[] images) + { + for (int i = 0; i < images.Length; i++) + { + ITexture image = images[i]; + + if (image is TextureBuffer textureBuffer) + { + _bufferTextureRefs[index + i] = textureBuffer; + } + else if (image is TextureView view) + { + _textureRefs[index + i].Storage = view.Storage; + _textureRefs[index + i].View = view; + } + else if (!_isBuffer) + { + _textureRefs[index + i].Storage = null; + _textureRefs[index + i].View = default; + } + else + { + _bufferTextureRefs[index + i] = null; + } + } + + SetDirty(); + } + + private void SetDirty() + { + _cachedCommandBufferIndex = -1; + _storages = null; + + _gd.PipelineInternal.ForceImageDirty(); + } + + public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) + { + HashSet storages = _storages; + + if (storages == null) + { + storages = new HashSet(); + + for (int index = 0; index < _textureRefs.Length; index++) + { + if (_textureRefs[index].Storage != null) + { + storages.Add(_textureRefs[index].Storage); + } + } + + _storages = storages; + } + + foreach (TextureStorage storage in storages) + { + storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags); + } + } + + public ReadOnlySpan GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture) + { + int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex); + + Span textures = _textures; + + if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount) + { + return textures; + } + + _cachedCommandBufferIndex = cbs.CommandBufferIndex; + _cachedSubmissionCount = submissionCount; + + for (int i = 0; i < textures.Length; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[i]; + + if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].ImageFormat == refs.ImageFormat) + { + texture = textures[i - 1]; + + continue; + } + + texture.ImageLayout = ImageLayout.General; + texture.ImageView = refs.View?.GetView(refs.ImageFormat).GetIdentityImageView().Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value; + } + } + + return textures; + } + + public ReadOnlySpan GetBufferViews(CommandBufferScoped cbs) + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < bufferTextures.Length; i++) + { + bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, _textureRefs[i].ImageFormat, true) ?? default; + } + + return bufferTextures; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 2bcab5143..41ab84d94 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -898,6 +898,11 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater.SetImage(binding, image); } + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + _descriptorSetUpdater.SetImageArray(Cbs, stage, binding, array); + } + public void SetIndexBuffer(BufferRange buffer, IndexType type) { if (buffer.Handle != BufferHandle.Null) @@ -981,6 +986,7 @@ namespace Ryujinx.Graphics.Vulkan _bindingBarriersDirty = true; _newState.PipelineLayout = internalProgram.PipelineLayout; + _newState.HasTessellationControlShader = internalProgram.HasTessellationControlShader; _newState.StagesCount = (uint)stages.Length; stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]); @@ -1145,6 +1151,11 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler); } + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + _descriptorSetUpdater.SetTextureArray(Cbs, stage, binding, array); + } + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { PauseTransformFeedbackInternal(); @@ -1374,6 +1385,16 @@ namespace Ryujinx.Graphics.Vulkan SignalCommandBufferChange(); } + public void ForceTextureDirty() + { + _descriptorSetUpdater.ForceTextureDirty(); + } + + public void ForceImageDirty() + { + _descriptorSetUpdater.ForceImageDirty(); + } + public unsafe void TextureBarrier() { MemoryBarrier memoryBarrier = new() diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index f388d9e88..fb1f0a5ff 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -8,14 +8,7 @@ namespace Ryujinx.Graphics.Vulkan { class PipelineLayoutCacheEntry { - // Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts. - // It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically. - private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets; - private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets; - private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets; - private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets; - - private const int MaxPoolSizesPerSet = 2; + private const int MaxPoolSizesPerSet = 8; private readonly VulkanRenderer _gd; private readonly Device _device; @@ -24,6 +17,9 @@ namespace Ryujinx.Graphics.Vulkan public PipelineLayout PipelineLayout { get; } private readonly int[] _consumedDescriptorsPerSet; + private readonly DescriptorPoolSize[][] _poolSizes; + + private readonly DescriptorSetManager _descriptorSetManager; private readonly List>[][] _dsCache; private List>[] _currentDsCache; @@ -65,6 +61,9 @@ namespace Ryujinx.Graphics.Vulkan (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); _consumedDescriptorsPerSet = new int[setDescriptors.Count]; + _poolSizes = new DescriptorPoolSize[setDescriptors.Count][]; + + Span poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++) { @@ -76,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan } _consumedDescriptorsPerSet[setIndex] = count; + _poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray(); } if (usePushDescriptors) @@ -83,6 +83,8 @@ namespace Ryujinx.Graphics.Vulkan _pdDescriptors = setDescriptors[0]; _pdTemplates = new(); } + + _descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count); } public void UpdateCommandBufferIndex(int commandBufferIndex) @@ -105,17 +107,12 @@ namespace Ryujinx.Graphics.Vulkan int index = _dsCacheCursor[setIndex]++; if (index == list.Count) { - Span poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; - poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex); - - int consumedDescriptors = _consumedDescriptorsPerSet[setIndex]; - - var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet( + var dsc = _descriptorSetManager.AllocateDescriptorSet( _gd.Api, DescriptorSetLayouts[setIndex], - poolSizes, + _poolSizes[setIndex], setIndex, - consumedDescriptors, + _consumedDescriptorsPerSet[setIndex], false); list.Add(dsc); @@ -127,28 +124,35 @@ namespace Ryujinx.Graphics.Vulkan return list[index]; } - private static Span GetDescriptorPoolSizes(Span output, int setIndex) + private static Span GetDescriptorPoolSizes(Span output, ResourceDescriptorCollection setDescriptor, uint multiplier) { - int count = 1; + int count = 0; - switch (setIndex) + for (int index = 0; index < setDescriptor.Descriptors.Count; index++) { - case PipelineBase.UniformSetIndex: - output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity); - break; - case PipelineBase.StorageSetIndex: - output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity); - break; - case PipelineBase.TextureSetIndex: - output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity); - output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity); - count = 2; - break; - case PipelineBase.ImageSetIndex: - output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity); - output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity); - count = 2; - break; + ResourceDescriptor descriptor = setDescriptor.Descriptors[index]; + DescriptorType descriptorType = descriptor.Type.Convert(); + + bool found = false; + + for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++) + { + if (output[poolSizeIndex].Type == descriptorType) + { + output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier; + found = true; + break; + } + } + + if (!found) + { + output[count++] = new DescriptorPoolSize() + { + Type = descriptorType, + DescriptorCount = (uint)descriptor.Count, + }; + } } return output[..count]; @@ -206,6 +210,8 @@ namespace Ryujinx.Graphics.Vulkan { _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); } + + _descriptorSetManager.Dispose(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index 25fd7168f..49c12b376 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -311,6 +311,7 @@ namespace Ryujinx.Graphics.Vulkan set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); } + public bool HasTessellationControlShader; public NativeArray Stages; public PipelineLayout PipelineLayout; public SpecData SpecializationData; @@ -319,6 +320,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize() { + HasTessellationControlShader = false; Stages = new NativeArray(Constants.MaxShaderStages); AdvancedBlendSrcPreMultiplied = true; @@ -419,6 +421,15 @@ namespace Ryujinx.Graphics.Vulkan PVertexBindingDescriptions = pVertexBindingDescriptions, }; + // Using patches topology without a tessellation shader is invalid. + // If we find such a case, return null pipeline to skip the draw. + if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader) + { + program.AddGraphicsPipeline(ref Internal, null); + + return null; + } + bool primitiveRestartEnable = PrimitiveRestartEnable; bool topologySupportsRestart; diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs index 3d883b2d5..9edea5788 100644 --- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -122,7 +122,6 @@ namespace Ryujinx.Graphics.Vulkan gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); - _renderPass?.Dispose(); _renderPass = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); } @@ -162,7 +161,7 @@ namespace Ryujinx.Graphics.Vulkan public void Dispose() { - // Dispose all framebuffers + // Dispose all framebuffers. foreach (var fb in _framebuffers.Values) { @@ -175,6 +174,10 @@ namespace Ryujinx.Graphics.Vulkan { texture.RemoveRenderPass(_key); } + + // Dispose render pass. + + _renderPass.Dispose(); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs index 8902f13e6..6e27da4a6 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs @@ -8,13 +8,15 @@ namespace Ryujinx.Graphics.Vulkan public readonly int Count; public readonly ResourceType Type; public readonly ResourceStages Stages; + public readonly bool IsArray; - public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages) + public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray) { Binding = binding; Count = count; Type = type; Stages = stages; + IsArray = isArray; } } } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs index f5ac39684..76a5ef4f9 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan }; _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages)); - _resourceUsages[setIndex].Add(new ResourceUsage(binding, type, stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages)); return this; } diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index e4ea0e4e6..178546983 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan public bool HasMinimalLayout { get; } public bool UsePushDescriptors { get; } public bool IsCompute { get; } + public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0; public uint Stages { get; } @@ -240,7 +241,9 @@ namespace Ryujinx.Graphics.Vulkan if (currentDescriptor.Binding + currentCount != descriptor.Binding || currentDescriptor.Type != descriptor.Type || - currentDescriptor.Stages != descriptor.Stages) + currentDescriptor.Stages != descriptor.Stages || + currentDescriptor.Count > 1 || + descriptor.Count > 1) { if (currentCount != 0) { @@ -248,7 +251,8 @@ namespace Ryujinx.Graphics.Vulkan currentDescriptor.Binding, currentCount, currentDescriptor.Type, - currentDescriptor.Stages)); + currentDescriptor.Stages, + currentDescriptor.Count > 1)); } currentDescriptor = descriptor; @@ -266,7 +270,8 @@ namespace Ryujinx.Graphics.Vulkan currentDescriptor.Binding, currentCount, currentDescriptor.Type, - currentDescriptor.Stages)); + currentDescriptor.Stages, + currentDescriptor.Count > 1)); } segments[setIndex] = currentSegments.ToArray(); @@ -292,7 +297,9 @@ namespace Ryujinx.Graphics.Vulkan if (currentUsage.Binding + currentCount != usage.Binding || currentUsage.Type != usage.Type || - currentUsage.Stages != usage.Stages) + currentUsage.Stages != usage.Stages || + currentUsage.ArrayLength > 1 || + usage.ArrayLength > 1) { if (currentCount != 0) { @@ -300,11 +307,12 @@ namespace Ryujinx.Graphics.Vulkan currentUsage.Binding, currentCount, currentUsage.Type, - currentUsage.Stages)); + currentUsage.Stages, + currentUsage.ArrayLength > 1)); } currentUsage = usage; - currentCount = 1; + currentCount = usage.ArrayLength; } else { @@ -318,7 +326,8 @@ namespace Ryujinx.Graphics.Vulkan currentUsage.Binding, currentCount, currentUsage.Type, - currentUsage.Stages)); + currentUsage.Stages, + currentUsage.ArrayLength > 1)); } segments[setIndex] = currentSegments.ToArray(); @@ -343,7 +352,13 @@ namespace Ryujinx.Graphics.Vulkan if (segments != null && segments.Length > 0) { - templates[setIndex] = new DescriptorSetTemplate(_gd, _device, segments, _plce, IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, setIndex); + templates[setIndex] = new DescriptorSetTemplate( + _gd, + _device, + segments, + _plce, + IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, + setIndex); } } @@ -461,6 +476,7 @@ namespace Ryujinx.Graphics.Vulkan stages[i] = _shaders[i].GetInfo(); } + pipeline.HasTessellationControlShader = HasTessellationControlShader; pipeline.StagesCount = (uint)_shaders.Length; pipeline.PipelineLayout = PipelineLayout; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs new file mode 100644 index 000000000..6ef9087bc --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs @@ -0,0 +1,194 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureArray : ITextureArray + { + private readonly VulkanRenderer _gd; + + private struct TextureRef + { + public TextureStorage Storage; + public Auto View; + public Auto Sampler; + } + + private readonly TextureRef[] _textureRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + + private readonly DescriptorImageInfo[] _textures; + private readonly BufferView[] _bufferTextures; + + private HashSet _storages; + + private int _cachedCommandBufferIndex; + private int _cachedSubmissionCount; + + private readonly bool _isBuffer; + + public bool Bound; + + public TextureArray(VulkanRenderer gd, int size, bool isBuffer) + { + _gd = gd; + + if (isBuffer) + { + _bufferTextureRefs = new TextureBuffer[size]; + _bufferTextures = new BufferView[size]; + } + else + { + _textureRefs = new TextureRef[size]; + _textures = new DescriptorImageInfo[size]; + } + + _storages = null; + + _cachedCommandBufferIndex = -1; + _cachedSubmissionCount = 0; + + _isBuffer = isBuffer; + } + + public void SetSamplers(int index, ISampler[] samplers) + { + for (int i = 0; i < samplers.Length; i++) + { + ISampler sampler = samplers[i]; + + if (sampler is SamplerHolder samplerHolder) + { + _textureRefs[index + i].Sampler = samplerHolder.GetSampler(); + } + else + { + _textureRefs[index + i].Sampler = default; + } + } + + SetDirty(); + } + + public void SetTextures(int index, ITexture[] textures) + { + for (int i = 0; i < textures.Length; i++) + { + ITexture texture = textures[i]; + + if (texture is TextureBuffer textureBuffer) + { + _bufferTextureRefs[index + i] = textureBuffer; + } + else if (texture is TextureView view) + { + _textureRefs[index + i].Storage = view.Storage; + _textureRefs[index + i].View = view.GetImageView(); + } + else if (!_isBuffer) + { + _textureRefs[index + i].Storage = null; + _textureRefs[index + i].View = default; + } + else + { + _bufferTextureRefs[index + i] = null; + } + } + + SetDirty(); + } + + private void SetDirty() + { + _cachedCommandBufferIndex = -1; + _storages = null; + + _gd.PipelineInternal.ForceTextureDirty(); + } + + public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) + { + HashSet storages = _storages; + + if (storages == null) + { + storages = new HashSet(); + + for (int index = 0; index < _textureRefs.Length; index++) + { + if (_textureRefs[index].Storage != null) + { + storages.Add(_textureRefs[index].Storage); + } + } + + _storages = storages; + } + + foreach (TextureStorage storage in storages) + { + storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags); + } + } + + public ReadOnlySpan GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture, SamplerHolder dummySampler) + { + int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex); + + Span textures = _textures; + + if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount) + { + return textures; + } + + _cachedCommandBufferIndex = cbs.CommandBufferIndex; + _cachedSubmissionCount = submissionCount; + + for (int i = 0; i < textures.Length; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[i]; + + if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].Sampler == refs.Sampler) + { + texture = textures[i - 1]; + + continue; + } + + texture.ImageLayout = ImageLayout.General; + texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = dummySampler.GetSampler().Get(cbs).Value; + } + } + + return textures; + } + + public ReadOnlySpan GetBufferViews(CommandBufferScoped cbs) + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < bufferTextures.Length; i++) + { + bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, false) ?? default; + } + + return bufferTextures; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs index 81e478814..e0694b197 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -1,7 +1,7 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; +using System.Buffers; using System.Collections.Generic; using Format = Ryujinx.Graphics.GAL.Format; using VkFormat = Silk.NET.Vulkan.Format; @@ -94,17 +94,21 @@ namespace Ryujinx.Graphics.Vulkan _bufferView = null; } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - _gd.SetBufferData(_bufferHandle, _offset, data); + _gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span); + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { throw new NotSupportedException(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { throw new NotSupportedException(); } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index 31d139652..f2aaf4693 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -1,9 +1,10 @@ -using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Threading; using Format = Ryujinx.Graphics.GAL.Format; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkFormat = Silk.NET.Vulkan.Format; @@ -36,7 +37,8 @@ namespace Ryujinx.Graphics.Vulkan public int FirstLayer { get; } public int FirstLevel { get; } public VkFormat VkFormat { get; } - public bool Valid { get; private set; } + private int _isValid; + public bool Valid => Volatile.Read(ref _isValid) != 0; public TextureView( VulkanRenderer gd, @@ -158,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan } } - Valid = true; + _isValid = 1; } /// @@ -178,7 +180,7 @@ namespace Ryujinx.Graphics.Vulkan VkFormat = format; - Valid = true; + _isValid = 1; } public Auto GetImage() @@ -700,19 +702,25 @@ namespace Ryujinx.Graphics.Vulkan return GetDataFromBuffer(result, size, result); } - public void SetData(SpanOrArray data) + /// + public void SetData(IMemoryOwner data) { - SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level) + /// + public void SetData(IMemoryOwner data, int layer, int level) { - SetData(data, layer, level, 1, 1, singleSlice: true); + SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true); + data.Dispose(); } - public void SetData(SpanOrArray data, int layer, int level, Rectangle region) + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) { - SetData(data, layer, level, 1, 1, singleSlice: true, region); + SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region); + data.Dispose(); } private void SetData(ReadOnlySpan data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle? region = null) @@ -1017,10 +1025,11 @@ namespace Ryujinx.Graphics.Vulkan { if (disposing) { - Valid = false; - - if (_gd.Textures.Remove(this)) + bool wasValid = Interlocked.Exchange(ref _isValid, 0) != 0; + if (wasValid) { + _gd.Textures.Remove(this); + _imageView.Dispose(); _imageView2dArray?.Dispose(); @@ -1034,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan _imageViewDraw.Dispose(); } - Storage.DecrementViewsCount(); + Storage?.DecrementViewsCount(); if (_renderPasses != null) { @@ -1045,22 +1054,22 @@ namespace Ryujinx.Graphics.Vulkan pass.Dispose(); } } + + if (_selfManagedViews != null) + { + foreach (var view in _selfManagedViews.Values) + { + view.Dispose(); + } + + _selfManagedViews = null; + } } } } public void Dispose() { - if (_selfManagedViews != null) - { - foreach (var view in _selfManagedViews.Values) - { - view.Dispose(); - } - - _selfManagedViews = null; - } - Dispose(true); } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index d1afeaeae..e75e7f4b4 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -48,7 +48,6 @@ namespace Ryujinx.Graphics.Vulkan internal MemoryAllocator MemoryAllocator { get; private set; } internal HostMemoryAllocator HostMemoryAllocator { get; private set; } internal CommandBufferPool CommandBufferPool { get; private set; } - internal DescriptorSetManager DescriptorSetManager { get; private set; } internal PipelineLayoutCache PipelineLayoutCache { get; private set; } internal BackgroundResources BackgroundResources { get; private set; } internal Action InterruptAction { get; private set; } @@ -414,8 +413,6 @@ namespace Ryujinx.Graphics.Vulkan CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); - DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts); - PipelineLayoutCache = new PipelineLayoutCache(); BackgroundResources = new BackgroundResources(this, _device); @@ -507,6 +504,11 @@ namespace Ryujinx.Graphics.Vulkan return BufferManager.CreateSparse(this, storageBuffers); } + public IImageArray CreateImageArray(int size, bool isBuffer) + { + return new ImageArray(this, size, isBuffer); + } + public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info) { bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute; @@ -539,6 +541,11 @@ namespace Ryujinx.Graphics.Vulkan return CreateTextureView(info); } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + return new TextureArray(this, size, isBuffer); + } + internal TextureView CreateTextureView(TextureCreateInfo info) { // This should be disposed when all views are destroyed. @@ -925,7 +932,6 @@ namespace Ryujinx.Graphics.Vulkan HelperShader.Dispose(); _pipeline.Dispose(); BufferManager.Dispose(); - DescriptorSetManager.Dispose(); PipelineLayoutCache.Dispose(); Barriers.Dispose(); diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 5ddb6eeda..a4ac9e9f1 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -330,6 +330,7 @@ namespace Ryujinx.Graphics.Vulkan _swapchainIsDirty) { RecreateSwapchain(); + semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length; } else { diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index b27eb5ead..3c34a886b 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -104,20 +104,15 @@ namespace Ryujinx.HLE.FileSystem foreach (StorageId storageId in Enum.GetValues()) { - string contentDirectory = null; - string contentPathString = null; - string registeredDirectory = null; - - try - { - contentPathString = ContentPath.GetContentPath(storageId); - contentDirectory = ContentPath.GetRealPath(contentPathString); - registeredDirectory = Path.Combine(contentDirectory, "registered"); - } - catch (NotSupportedException) + if (!ContentPath.TryGetContentPath(storageId, out var contentPathString)) { continue; } + if (!ContentPath.TryGetRealPath(contentPathString, out var contentDirectory)) + { + continue; + } + var registeredDirectory = Path.Combine(contentDirectory, "registered"); Directory.CreateDirectory(registeredDirectory); @@ -471,8 +466,8 @@ namespace Ryujinx.HLE.FileSystem public void InstallFirmware(string firmwareSource) { - string contentPathString = ContentPath.GetContentPath(StorageId.BuiltInSystem); - string contentDirectory = ContentPath.GetRealPath(contentPathString); + ContentPath.TryGetContentPath(StorageId.BuiltInSystem, out var contentPathString); + ContentPath.TryGetRealPath(contentPathString, out var contentDirectory); string registeredDirectory = Path.Combine(contentDirectory, "registered"); string temporaryDirectory = Path.Combine(contentDirectory, "temp"); diff --git a/src/Ryujinx.HLE/FileSystem/ContentPath.cs b/src/Ryujinx.HLE/FileSystem/ContentPath.cs index 02539c5c6..ffc212f77 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentPath.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentPath.cs @@ -26,17 +26,19 @@ namespace Ryujinx.HLE.FileSystem public const string Nintendo = "Nintendo"; public const string Contents = "Contents"; - public static string GetRealPath(string switchContentPath) + public static bool TryGetRealPath(string switchContentPath, out string realPath) { - return switchContentPath switch + realPath = switchContentPath switch { SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents), UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents), SdCardContent => Path.Combine(GetSdCardPath(), Nintendo, Contents), System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath), User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath), - _ => throw new NotSupportedException($"Content Path \"`{switchContentPath}`\" is not supported."), + _ => null, }; + + return realPath != null; } public static string GetContentPath(ContentStorageId contentStorageId) @@ -50,15 +52,17 @@ namespace Ryujinx.HLE.FileSystem }; } - public static string GetContentPath(StorageId storageId) + public static bool TryGetContentPath(StorageId storageId, out string contentPath) { - return storageId switch + contentPath = storageId switch { StorageId.BuiltInSystem => SystemContent, StorageId.BuiltInUser => UserContent, StorageId.SdCard => SdCardContent, - _ => throw new NotSupportedException($"Storage Id \"`{storageId}`\" is not supported."), + _ => null, }; + + return contentPath != null; } public static StorageId GetStorageId(string contentPathString) diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index e8c433269..6646826cb 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -75,7 +75,7 @@ namespace Ryujinx.HLE.HOS // We want to use host tracked mode if the host page size is > 4KB. if ((mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && MemoryBlock.GetPageSize() <= 0x1000) { - if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace)) + if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, out addressSpace)) { Logger.Warning?.Print(LogClass.Cpu, "Address space creation failed, falling back to software page table"); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs index d7b601d1c..d262c159d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { private readonly IVirtualMemoryManager _cpuMemory; - protected override bool Supports4KBPages => _cpuMemory.Supports4KBPages; + protected override bool UsesPrivateAllocations => _cpuMemory.UsesPrivateAllocations; public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory, ulong reservedAddressSpaceSize) : base(context, reservedAddressSpaceSize) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index 6470742d9..ae99a434a 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory private const int MaxBlocksNeededForInsertion = 2; protected readonly KernelContext Context; - protected virtual bool Supports4KBPages => true; + protected virtual bool UsesPrivateAllocations => false; public ulong AddrSpaceStart { get; private set; } public ulong AddrSpaceEnd { get; private set; } @@ -1947,17 +1947,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory Result result; - if (srcPageTable.Supports4KBPages) + if (srcPageTable.UsesPrivateAllocations) + { + result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize); + } + else { KPageList pageList = new(); srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList); result = MapPages(currentVa, pageList, permission, MemoryMapFlags.None); } - else - { - result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize); - } if (result != Result.Success) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs index e302ee443..e593a7e13 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -2,7 +2,6 @@ using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.Horizon.Common; -using Ryujinx.Memory; namespace Ryujinx.HLE.HOS.Kernel.Memory { @@ -49,17 +48,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.InvalidPermission; } - // On platforms with page size > 4 KB, this can fail due to the address not being page aligned, - // we can return an error to force the application to retry with a different address. - - try - { - return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); - } - catch (InvalidMemoryRegionException) - { - return KernelResult.InvalidMemState; - } + return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); } public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process) diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index 8ae529655..ee179c929 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -173,36 +173,16 @@ namespace Ryujinx.HLE.HOS if (StrEquals(RomfsDir, modDir.Name)) { - bool enabled; - - try - { - var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); - enabled = modData.Enabled; - } - catch - { - // Mod is not in the list yet. New mods should be enabled by default. - enabled = true; - } + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + var enabled = modData?.Enabled ?? true; mods.RomfsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); types.Append('R'); } else if (StrEquals(ExefsDir, modDir.Name)) { - bool enabled; - - try - { - var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); - enabled = modData.Enabled; - } - catch - { - // Mod is not in the list yet. New mods should be enabled by default. - enabled = true; - } + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + var enabled = modData?.Enabled ?? true; mods.ExefsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); types.Append('E'); @@ -218,7 +198,7 @@ namespace Ryujinx.HLE.HOS if (types.Length > 0) { - Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]"); + Logger.Info?.Print(LogClass.ModLoader, $"Found {(mod.Enabled ? "enabled" : "disabled")} mod '{mod.Name}' [{types}]"); } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs deleted file mode 100644 index 66ffd0a49..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Services.Ptm.Ts.Types; - -namespace Ryujinx.HLE.HOS.Services.Ptm.Ts -{ - [Service("ts")] - class IMeasurementServer : IpcService - { - private const uint DefaultTemperature = 42u; - - public IMeasurementServer(ServiceCtx context) { } - - [CommandCmif(1)] - // GetTemperature(Location location) -> u32 - public ResultCode GetTemperature(ServiceCtx context) - { - Location location = (Location)context.RequestData.ReadByte(); - - Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); - - context.ResponseData.Write(DefaultTemperature); - - return ResultCode.Success; - } - - [CommandCmif(3)] - // GetTemperatureMilliC(Location location) -> u32 - public ResultCode GetTemperatureMilliC(ServiceCtx context) - { - Location location = (Location)context.RequestData.ReadByte(); - - Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); - - context.ResponseData.Write(DefaultTemperature * 1000); - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index 1e8a90051..21d48288e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -440,8 +440,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd // If we are here, that mean nothing was available, sleep for 50ms context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); + context.Thread.HandlePostSyscall(); } - while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); + while (context.Thread.Context.Running && PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); } else if (timeout == -1) { diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs index 4c8d441b6..d1be8298d 100644 --- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Horizon.Generators.Hipc private const string ResponseVariableName = "response"; private const string OutRawDataVariableName = "outRawData"; + private const string TypeSystemBuffersReadOnlySequence = "System.Buffers.ReadOnlySequence"; + private const string TypeSystemMemory = "System.Memory"; private const string TypeSystemReadOnlySpan = "System.ReadOnlySpan"; private const string TypeSystemSpan = "System.Span"; private const string TypeStructLayoutAttribute = "System.Runtime.InteropServices.StructLayoutAttribute"; @@ -329,7 +331,15 @@ namespace Ryujinx.Horizon.Generators.Hipc value = $"{InObjectsVariableName}[{inObjectIndex++}]"; break; case CommandArgType.Buffer: - if (IsReadOnlySpan(compilation, parameter)) + if (IsMemory(compilation, parameter)) + { + value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySequence(compilation, parameter)) + { + value = $"CommandSerialization.GetReadOnlySequence(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySpan(compilation, parameter)) { string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0); value = GenerateSpanCast(spanGenericTypeName, $"CommandSerialization.GetReadOnlySpan(processor.GetBufferRange({index}))"); @@ -346,7 +356,13 @@ namespace Ryujinx.Horizon.Generators.Hipc break; } - if (IsSpan(compilation, parameter)) + if (IsMemory(compilation, parameter)) + { + generator.AppendLine($"using var {argName} = {value};"); + + argName = $"{argName}.Memory"; + } + else if (IsSpan(compilation, parameter)) { generator.AppendLine($"using var {argName} = {value};"); @@ -637,7 +653,9 @@ namespace Ryujinx.Horizon.Generators.Hipc private static bool IsValidTypeForBuffer(Compilation compilation, ParameterSyntax parameter) { - return IsReadOnlySpan(compilation, parameter) || + return IsMemory(compilation, parameter) || + IsReadOnlySequence(compilation, parameter) || + IsReadOnlySpan(compilation, parameter) || IsSpan(compilation, parameter) || IsUnmanagedType(compilation, parameter.Type); } @@ -649,6 +667,16 @@ namespace Ryujinx.Horizon.Generators.Hipc return typeInfo.Type.IsUnmanagedType; } + private static bool IsMemory(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemMemory; + } + + private static bool IsReadOnlySequence(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemBuffersReadOnlySequence; + } + private static bool IsReadOnlySpan(Compilation compilation, ParameterSyntax parameter) { return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemReadOnlySpan; diff --git a/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs b/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs new file mode 100644 index 000000000..ce7c0474a --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Ts; +using Ryujinx.Horizon.Ts.Ipc; + +namespace Ryujinx.Horizon.Ptm.Ipc +{ + partial class MeasurementServer : IMeasurementServer + { + // NOTE: Values are randomly choosen. + public const int DefaultTemperature = 42; + public const int MinimumTemperature = 0; + public const int MaximumTemperature = 100; + + [CmifCommand(0)] // 1.0.0-16.1.0 + public Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + minimumTemperature = MinimumTemperature; + maximumTemperature = MaximumTemperature; + + return Result.Success; + } + + [CmifCommand(1)] // 1.0.0-16.1.0 + public Result GetTemperature(out int temperature, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + temperature = DefaultTemperature; + + return Result.Success; + } + + [CmifCommand(2)] // 1.0.0-13.2.1 + public Result SetMeasurementMode(Location location, byte measurementMode) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location, measurementMode }); + + return Result.Success; + } + + [CmifCommand(3)] // 1.0.0-13.2.1 + public Result GetTemperatureMilliC(out int temperatureMilliC, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + temperatureMilliC = DefaultTemperature * 1000; + + return Result.Success; + } + + [CmifCommand(4)] // 8.0.0+ + public Result OpenSession(out ISession session, DeviceCode deviceCode) + { + session = new Session(deviceCode); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs b/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs new file mode 100644 index 000000000..191a4b3af --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs @@ -0,0 +1,47 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Ptm.Ipc; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Ts; + +namespace Ryujinx.Horizon.Ts.Ipc +{ + partial class Session : ISession + { + private readonly DeviceCode _deviceCode; + + public Session(DeviceCode deviceCode) + { + _deviceCode = deviceCode; + } + + [CmifCommand(0)] + public Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode }); + + minimumTemperature = MeasurementServer.MinimumTemperature; + maximumTemperature = MeasurementServer.MaximumTemperature; + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetMeasurementMode(byte measurementMode) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode, measurementMode }); + + return Result.Success; + } + + [CmifCommand(4)] + public Result GetTemperature(out int temperature) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode }); + + temperature = MeasurementServer.DefaultTemperature; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs b/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs new file mode 100644 index 000000000..db25d8e2e --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.Ptm.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ptm +{ + class TsIpcServer + { + private const int MaxSessionsCount = 4; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new MeasurementServer(), ServiceName.Encode("ts"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/TsMain.cs b/src/Ryujinx.Horizon/Ptm/TsMain.cs new file mode 100644 index 000000000..237d52cd4 --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/TsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ptm +{ + class TsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + TsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs index 776df641a..4d446bba7 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -57,23 +57,14 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [CmifCommand(4)] public Result RequestUpdate( - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span performanceOutput, - [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySequence input) { - using IMemoryOwner outputOwner = ByteMemoryPool.Rent(output.Length); - using IMemoryOwner performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length); + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); - Memory outputMemory = outputOwner.Memory; - Memory performanceOutputMemory = performanceOutputOwner.Memory; - - using MemoryHandle outputHandle = outputMemory.Pin(); - using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin(); - - Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray())); - - outputMemory.Span.CopyTo(output); - performanceOutputMemory.Span.CopyTo(performanceOutput); + Result result = new Result((int)_renderSystem.Update(output, performanceOutput, input)); return result; } @@ -127,9 +118,9 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [CmifCommand(10)] // 3.0.0+ public Result RequestUpdateAuto( - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span output, - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span performanceOutput, - [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan input) + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySequence input) { return RequestUpdate(output, performanceOutput, input); } diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs index e4ca2e8ec..b766bd73c 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs @@ -1,6 +1,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Sdk.Sf; using System; +using System.Buffers; namespace Ryujinx.Horizon.Sdk.Audio.Detail { @@ -10,13 +11,13 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail Result GetSampleCount(out int sampleCount); Result GetMixBufferCount(out int mixBufferCount); Result GetState(out int state); - Result RequestUpdate(Span output, Span performanceOutput, ReadOnlySpan input); + Result RequestUpdate(Memory output, Memory performanceOutput, ReadOnlySequence input); Result Start(); Result Stop(); Result QuerySystemEvent(out int eventHandle); Result SetRenderingTimeLimit(int percent); Result GetRenderingTimeLimit(out int percent); - Result RequestUpdateAuto(Span output, Span performanceOutput, ReadOnlySpan input); + Result RequestUpdateAuto(Memory output, Memory performanceOutput, ReadOnlySequence input); Result ExecuteAudioRendererRendering(); Result SetVoiceDropParameter(float voiceDropParameter); Result GetVoiceDropParameter(out float voiceDropParameter); diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs index 038135ac8..7f5284648 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs @@ -2,6 +2,7 @@ using Ryujinx.Horizon.Sdk.Sf.Cmif; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Memory; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,6 +10,11 @@ namespace Ryujinx.Horizon.Sdk.Sf { static class CommandSerialization { + public static ReadOnlySequence GetReadOnlySequence(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetReadOnlySequence(bufferRange.Address, checked((int)bufferRange.Size)); + } + public static ReadOnlySpan GetReadOnlySpan(PointerAndSize bufferRange) { return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size)); diff --git a/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs b/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs new file mode 100644 index 000000000..4fce4238e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Ts +{ + enum DeviceCode : uint + { + Internal = 0x41000001, + External = 0x41000002, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs b/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs new file mode 100644 index 000000000..ba9c2a748 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ts +{ + interface IMeasurementServer : IServiceObject + { + Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature, Location location); + Result GetTemperature(out int temperature, Location location); + Result SetMeasurementMode(Location location, byte measurementMode); + Result GetTemperatureMilliC(out int temperatureMilliC, Location location); + Result OpenSession(out ISession session, DeviceCode deviceCode); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs b/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs new file mode 100644 index 000000000..23c0d94f6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ts +{ + interface ISession : IServiceObject + { + Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature); + Result GetTemperature(out int temperature); + Result SetMeasurementMode(byte measurementMode); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs b/src/Ryujinx.Horizon/Sdk/Ts/Location.cs similarity index 61% rename from src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs rename to src/Ryujinx.Horizon/Sdk/Ts/Location.cs index 409188a97..177b0ee88 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs +++ b/src/Ryujinx.Horizon/Sdk/Ts/Location.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.HOS.Services.Ptm.Ts.Types +namespace Ryujinx.Horizon.Sdk.Ts { enum Location : byte { diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index b81e62a47..28c43a716 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -11,6 +11,7 @@ using Ryujinx.Horizon.Ngc; using Ryujinx.Horizon.Ovln; using Ryujinx.Horizon.Prepo; using Ryujinx.Horizon.Psc; +using Ryujinx.Horizon.Ptm; using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Srepo; using Ryujinx.Horizon.Usb; @@ -54,6 +55,7 @@ namespace Ryujinx.Horizon RegisterService(); RegisterService(); RegisterService(); + RegisterService(); RegisterService(); RegisterService(); diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index f00db94e2..8411c10a7 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -245,9 +245,9 @@ namespace Ryujinx.Input.HLE { if (config is StandardControllerInputConfig controllerConfig) { - bool needsMotionInputUpdate = _config == null || (_config is StandardControllerInputConfig oldControllerConfig && - (oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && - (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); + bool needsMotionInputUpdate = _config is not StandardControllerInputConfig oldControllerConfig || + ((oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && + (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); if (needsMotionInputUpdate) { diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index f19b45b65..807c5c0f4 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Memory { @@ -11,10 +10,10 @@ namespace Ryujinx.Memory /// Represents a address space manager. /// Supports virtual memory region mapping, address translation and read/write access to mapped regions. /// - public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager, IWritableBlock + public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager { /// - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; /// /// Address space width in bits. @@ -63,8 +62,7 @@ namespace Ryujinx.Memory } } - /// - public void MapForeign(ulong va, nuint hostPointer, ulong size) + public override void MapForeign(ulong va, nuint hostPointer, ulong size) { AssertValidAddressAndSize(va, size); @@ -92,106 +90,6 @@ namespace Ryujinx.Memory } } - /// - public T Read(ulong va) where T : unmanaged - { - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; - } - - /// - public void Write(ulong va, T value) where T : unmanaged - { - Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); - } - - /// - public void Write(ulong va, ReadOnlySpan data) - { - if (data.Length == 0) - { - return; - } - - AssertValidAddressAndSize(va, (ulong)data.Length); - - if (IsContiguousAndMapped(va, data.Length)) - { - data.CopyTo(GetHostSpanContiguous(va, data.Length)); - } - else - { - int offset = 0, size; - - if ((va & PageMask) != 0) - { - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - data[..size].CopyTo(GetHostSpanContiguous(va, size)); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - size = Math.Min(data.Length - offset, PageSize); - - data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size)); - } - } - } - - /// - public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) - { - Write(va, data); - - return true; - } - - /// - public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) - { - if (size == 0) - { - return ReadOnlySpan.Empty; - } - - if (IsContiguousAndMapped(va, size)) - { - return GetHostSpanContiguous(va, size); - } - else - { - Span data = new byte[size]; - - Read(va, data); - - return data; - } - } - - /// - public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) - { - if (size == 0) - { - return new WritableRegion(null, va, Memory.Empty); - } - - if (IsContiguousAndMapped(va, size)) - { - return new WritableRegion(null, va, new NativeMemoryManager((byte*)GetHostAddress(va), size).Memory); - } - else - { - Memory memory = new byte[size]; - - GetSpan(va, size).CopyTo(memory.Span); - - return new WritableRegion(this, va, memory); - } - } - /// public unsafe ref T GetRef(ulong va) where T : unmanaged { @@ -203,50 +101,6 @@ namespace Ryujinx.Memory return ref *(T*)GetHostAddress(va); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetPagesCount(ulong va, uint size, out ulong startVa) - { - // WARNING: Always check if ulong does not overflow during the operations. - startVa = va & ~(ulong)PageMask; - ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; - - return (int)(vaSpan / PageSize); - } - - private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsContiguous(ulong va, int size) - { - if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) - { - return false; - } - - int pages = GetPagesCount(va, (uint)size, out va); - - for (int page = 0; page < pages - 1; page++) - { - if (!ValidateAddress(va + PageSize)) - { - return false; - } - - if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize)) - { - return false; - } - - va += PageSize; - } - - return true; - } - /// public IEnumerable GetHostRegions(ulong va, ulong size) { @@ -304,7 +158,7 @@ namespace Ryujinx.Memory return null; } - int pages = GetPagesCount(va, (uint)size, out va); + int pages = GetPagesCount(va, size, out va); var regions = new List(); @@ -336,9 +190,8 @@ namespace Ryujinx.Memory return regions; } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsMapped(ulong va) + public override bool IsMapped(ulong va) { if (!ValidateAddress(va)) { @@ -351,7 +204,7 @@ namespace Ryujinx.Memory /// public bool IsRangeMapped(ulong va, ulong size) { - if (size == 0UL) + if (size == 0) { return true; } @@ -376,11 +229,6 @@ namespace Ryujinx.Memory return true; } - private unsafe Span GetHostSpanContiguous(ulong va, int size) - { - return new Span((void*)GetHostAddress(va), size); - } - private nuint GetHostAddress(ulong va) { return _pageTable.Read(va) + (nuint)(va & PageMask); @@ -397,16 +245,16 @@ namespace Ryujinx.Memory throw new NotImplementedException(); } - /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) - { - // Only the ARM Memory Manager has tracking for now. - } + protected unsafe override Memory GetPhysicalAddressMemory(nuint pa, int size) + => new NativeMemoryManager((byte*)pa, size).Memory; protected override unsafe Span GetPhysicalAddressSpan(nuint pa, int size) - => new((void*)pa, size); + => new Span((void*)pa, size); - protected override nuint TranslateVirtualAddressForRead(ulong va) + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetHostAddress(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) => GetHostAddress(va); } } diff --git a/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs b/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs new file mode 100644 index 000000000..5fe8d936c --- /dev/null +++ b/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs @@ -0,0 +1,60 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory +{ + /// + /// A concrete implementation of , + /// with methods to help build a full sequence. + /// + public sealed class BytesReadOnlySequenceSegment : ReadOnlySequenceSegment + { + public BytesReadOnlySequenceSegment(Memory memory) => Memory = memory; + + public BytesReadOnlySequenceSegment Append(Memory memory) + { + var nextSegment = new BytesReadOnlySequenceSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = nextSegment; + + return nextSegment; + } + + /// + /// Attempts to determine if the current and are contiguous. + /// Only works if both were created by a . + /// + /// The segment to check if continuous with the current one + /// The starting address of the contiguous segment + /// The size of the contiguous segment + /// True if the segments are contiguous, otherwise false + public unsafe bool IsContiguousWith(Memory other, out nuint contiguousStart, out int contiguousSize) + { + if (MemoryMarshal.TryGetMemoryManager>(Memory, out var thisMemoryManager) && + MemoryMarshal.TryGetMemoryManager>(other, out var otherMemoryManager) && + thisMemoryManager.Pointer + thisMemoryManager.Length == otherMemoryManager.Pointer) + { + contiguousStart = (nuint)thisMemoryManager.Pointer; + contiguousSize = thisMemoryManager.Length + otherMemoryManager.Length; + return true; + } + else + { + contiguousStart = 0; + contiguousSize = 0; + return false; + } + } + + /// + /// Replaces the current value with the one provided. + /// + /// The new segment to hold in this + public void Replace(Memory memory) + => Memory = memory; + } +} diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 557da2f26..102cedc94 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -8,10 +8,10 @@ namespace Ryujinx.Memory public interface IVirtualMemoryManager { /// - /// Indicates whenever the memory manager supports aliasing pages at 4KB granularity. + /// Indicates whether the memory manager creates private allocations when the flag is set on map. /// - /// True if 4KB pages are supported by the memory manager, false otherwise - bool Supports4KBPages { get; } + /// True if private mappings might be used, false otherwise + bool UsesPrivateAllocations { get; } /// /// Maps a virtual memory range into a physical memory range. @@ -124,6 +124,16 @@ namespace Ryujinx.Memory } } + /// + /// Gets a read-only sequence of read-only memory blocks from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the memory + /// A read-only sequence of read-only memory of the data + /// Throw for unhandled invalid or unmapped memory accesses + ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false); + /// /// Gets a read-only span of data from CPU mapped memory. /// diff --git a/src/Ryujinx.Memory/NativeMemoryManager.cs b/src/Ryujinx.Memory/NativeMemoryManager.cs index fe718bda8..9ca632938 100644 --- a/src/Ryujinx.Memory/NativeMemoryManager.cs +++ b/src/Ryujinx.Memory/NativeMemoryManager.cs @@ -14,6 +14,10 @@ namespace Ryujinx.Memory _length = length; } + public unsafe T* Pointer => _pointer; + + public int Length => _length; + public override Span GetSpan() { return new Span((void*)_pointer, _length); diff --git a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs index cbec88cc5..506e25f66 100644 --- a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs +++ b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs @@ -1,34 +1,171 @@ +using Ryujinx.Common.Memory; using System; -using System.Numerics; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Ryujinx.Memory { - public abstract class VirtualMemoryManagerBase - where TVirtual : IBinaryInteger - where TPhysical : IBinaryInteger + public abstract class VirtualMemoryManagerBase : IWritableBlock { public const int PageBits = 12; public const int PageSize = 1 << PageBits; public const int PageMask = PageSize - 1; - protected abstract TVirtual AddressSpaceSize { get; } + protected abstract ulong AddressSpaceSize { get; } - public virtual void Read(TVirtual va, Span data) + public virtual ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return new ReadOnlySequence(GetPhysicalAddressMemory(pa, size)); + } + else + { + AssertValidAddressAndSize(va, size); + + int offset = 0, segmentSize; + + BytesReadOnlySequenceSegment first = null, last = null; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + segmentSize = Math.Min(size, PageSize - (int)(va & PageMask)); + + Memory memory = GetPhysicalAddressMemory(pa, segmentSize); + + first = last = new BytesReadOnlySequenceSegment(memory); + + offset += segmentSize; + } + + for (; offset < size; offset += segmentSize) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + segmentSize = Math.Min(size - offset, PageSize); + + Memory memory = GetPhysicalAddressMemory(pa, segmentSize); + + if (first is null) + { + first = last = new BytesReadOnlySequenceSegment(memory); + } + else + { + if (last.IsContiguousWith(memory, out nuint contiguousStart, out int contiguousSize)) + { + last.Replace(GetPhysicalAddressMemory(contiguousStart, contiguousSize)); + } + else + { + last = last.Append(memory); + } + } + } + + return new ReadOnlySequence(first, 0, last, (int)(size - last.RunningIndex)); + } + } + + public virtual ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return GetPhysicalAddressSpan(pa, size); + } + else + { + Span data = new byte[size]; + + Read(va, data); + + return data; + } + } + + public virtual WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return new WritableRegion(null, va, GetPhysicalAddressMemory(pa, size)); + } + else + { + IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); + + Read(va, memoryOwner.Memory.Span); + + return new WritableRegion(this, va, memoryOwner); + } + } + + public abstract bool IsMapped(ulong va); + + public virtual void MapForeign(ulong va, nuint hostPointer, ulong size) + { + throw new NotSupportedException(); + } + + public virtual T Read(ulong va) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + } + + public virtual void Read(ulong va, Span data) { if (data.Length == 0) { return; } - AssertValidAddressAndSize(va, TVirtual.CreateChecked(data.Length)); + AssertValidAddressAndSize(va, data.Length); int offset = 0, size; - if ((int.CreateTruncating(va) & PageMask) != 0) + if ((va & PageMask) != 0) { - TPhysical pa = TranslateVirtualAddressForRead(va); + nuint pa = TranslateVirtualAddressChecked(va); - size = Math.Min(data.Length, PageSize - ((int.CreateTruncating(va) & PageMask))); + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]); @@ -37,7 +174,7 @@ namespace Ryujinx.Memory for (; offset < data.Length; offset += size) { - TPhysical pa = TranslateVirtualAddressForRead(va + TVirtual.CreateChecked(offset)); + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); size = Math.Min(data.Length - offset, PageSize); @@ -45,13 +182,84 @@ namespace Ryujinx.Memory } } + public virtual T ReadTracked(ulong va) where T : unmanaged + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + + public virtual void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + // No default implementation + } + + public virtual void Write(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + SignalMemoryTracking(va, (ulong)data.Length, true); + + WriteImpl(va, data); + } + + public virtual void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + public virtual void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + WriteImpl(va, data); + } + + public virtual bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return false; + } + + if (IsContiguousAndMapped(va, data.Length)) + { + SignalMemoryTracking(va, (ulong)data.Length, false); + + nuint pa = TranslateVirtualAddressChecked(va); + + var target = GetPhysicalAddressSpan(pa, data.Length); + + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + else + { + Write(va, data); + + return true; + } + } + /// /// Ensures the combination of virtual address and size is part of the addressable space. /// /// Virtual address of the range /// Size of the range in bytes /// Throw when the memory region specified outside the addressable space - protected void AssertValidAddressAndSize(TVirtual va, TVirtual size) + protected void AssertValidAddressAndSize(ulong va, ulong size) { if (!ValidateAddressAndSize(va, size)) { @@ -59,16 +267,82 @@ namespace Ryujinx.Memory } } - protected abstract Span GetPhysicalAddressSpan(TPhysical pa, int size); + /// + /// Ensures the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// Throw when the memory region specified outside the addressable space + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void AssertValidAddressAndSize(ulong va, int size) + => AssertValidAddressAndSize(va, (ulong)size); - protected abstract TPhysical TranslateVirtualAddressForRead(TVirtual va); + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + protected abstract Memory GetPhysicalAddressMemory(nuint pa, int size); + + protected abstract Span GetPhysicalAddressSpan(nuint pa, int size); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsContiguous(ulong va, int size) => IsContiguous(va, (ulong)size); + + protected virtual bool IsContiguous(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return false; + } + + int pages = GetPagesCount(va, size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (TranslateVirtualAddressUnchecked(va) + PageSize != TranslateVirtualAddressUnchecked(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsContiguousAndMapped(ulong va, int size) + => IsContiguous(va, size) && IsMapped(va); + + protected abstract nuint TranslateVirtualAddressChecked(ulong va); + + protected abstract nuint TranslateVirtualAddressUnchecked(ulong va); /// /// Checks if the virtual address is part of the addressable space. /// /// Virtual address /// True if the virtual address is part of the addressable space - protected bool ValidateAddress(TVirtual va) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool ValidateAddress(ulong va) { return va < AddressSpaceSize; } @@ -79,13 +353,53 @@ namespace Ryujinx.Memory /// Virtual address of the range /// Size of the range in bytes /// True if the combination of virtual address and size is part of the addressable space - protected bool ValidateAddressAndSize(TVirtual va, TVirtual size) + protected bool ValidateAddressAndSize(ulong va, ulong size) { - TVirtual endVa = va + size; + ulong endVa = va + size; return endVa >= va && endVa >= size && endVa <= AddressSpaceSize; } protected static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + + protected static void ThrowMemoryNotContiguous() + => throw new MemoryNotContiguousException(); + + protected virtual void WriteImpl(ulong va, ReadOnlySpan data) + { + AssertValidAddressAndSize(va, data.Length); + + if (IsContiguousAndMapped(va, data.Length)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + data.CopyTo(GetPhysicalAddressSpan(pa, data.Length)); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); + + data[..size].CopyTo(GetPhysicalAddressSpan(pa, size)); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + size = Math.Min(data.Length - offset, PageSize); + + data.Slice(offset, size).CopyTo(GetPhysicalAddressSpan(pa, size)); + } + } + } + } } diff --git a/src/Ryujinx.Memory/WritableRegion.cs b/src/Ryujinx.Memory/WritableRegion.cs index 666c8a99b..2c21ef4e8 100644 --- a/src/Ryujinx.Memory/WritableRegion.cs +++ b/src/Ryujinx.Memory/WritableRegion.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; namespace Ryujinx.Memory { @@ -6,6 +7,7 @@ namespace Ryujinx.Memory { private readonly IWritableBlock _block; private readonly ulong _va; + private readonly IMemoryOwner _memoryOwner; private readonly bool _tracked; private bool NeedsWriteback => _block != null; @@ -20,6 +22,12 @@ namespace Ryujinx.Memory Memory = memory; } + public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner memoryOwner, bool tracked = false) + : this(block, va, memoryOwner.Memory, tracked) + { + _memoryOwner = memoryOwner; + } + public void Dispose() { if (NeedsWriteback) @@ -33,6 +41,8 @@ namespace Ryujinx.Memory _block.WriteUntracked(_va, Memory.Span); } } + + _memoryOwner?.Dispose(); } } } diff --git a/src/Ryujinx.ShaderTools/Program.cs b/src/Ryujinx.ShaderTools/Program.cs index 04453912b..4252f1b25 100644 --- a/src/Ryujinx.ShaderTools/Program.cs +++ b/src/Ryujinx.ShaderTools/Program.cs @@ -11,17 +11,57 @@ namespace Ryujinx.ShaderTools { private class GpuAccessor : IGpuAccessor { + private const int DefaultArrayLength = 32; + private readonly byte[] _data; + private int _texturesCount; + private int _imagesCount; + public GpuAccessor(byte[] data) { _data = data; + _texturesCount = 0; + _imagesCount = 0; + } + + public int CreateConstantBufferBinding(int index) + { + return index + 1; + } + + public int CreateImageBinding(int count, bool isBuffer) + { + int binding = _imagesCount; + + _imagesCount += count; + + return binding; + } + + public int CreateStorageBufferBinding(int index) + { + return index; + } + + public int CreateTextureBinding(int count, bool isBuffer) + { + int binding = _texturesCount; + + _texturesCount += count; + + return binding; } public ReadOnlySpan GetCode(ulong address, int minimumSize) { return MemoryMarshal.Cast(new ReadOnlySpan(_data)[(int)address..]); } + + public int QueryTextureArrayLengthFromBuffer(int slot) + { + return DefaultArrayLength; + } } private class Options diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs index 85a1ac02b..3fe44db21 100644 --- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -1,13 +1,14 @@ using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; namespace Ryujinx.Tests.Memory { public class MockVirtualMemoryManager : IVirtualMemoryManager { - public bool Supports4KBPages => true; + public bool UsesPrivateAllocations => false; public bool NoMappings = false; @@ -57,6 +58,11 @@ namespace Ryujinx.Tests.Memory throw new NotImplementedException(); } + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { throw new NotImplementedException(); diff --git a/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs new file mode 100644 index 000000000..c0127530a --- /dev/null +++ b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs @@ -0,0 +1,359 @@ +using NUnit.Framework; +using Ryujinx.Common.Extensions; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Common.Extensions +{ + public class SequenceReaderExtensionsTests + { + [TestCase(null)] + [TestCase(sizeof(int) + 1)] + public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence sequence = + CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf()); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out _); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + [Test] + public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, 3); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.Advance(1); + + ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy(out _); + }); + } + + [Test] + public void ReadLittleEndian_Int32_RoundTripsSuccessfully() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ResultIsNotBigEndian() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreNotEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + sequenceReader.Advance(1); + + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + }); + } + + [Test] + public void ReadUnmanaged_ContiguousSequence_Succeeds() + => ReadUnmanaged_Succeeds(int.MaxValue); + + [Test] + public void ReadUnmanaged_FragmentedSequence_Succeeds() + => ReadUnmanaged_Succeeds(sizeof(int) + 1); + + [Test] + public void ReadUnmanaged_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.Advance(1); + + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + }); + } + + [Test] + public void SetConsumed_ContiguousSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(int.MaxValue); + + [Test] + public void SetConsumed_FragmentedSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(sizeof(int) + 1); + + [Test] + public void SetConsumed_ThrowsWhenBeyondActualLength() + { + const int StructCount = 2; + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf); + + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1); + }); + } + + private static void ReadUnmanaged_Succeeds(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader(sequence); + + static void SetConsumedAndAssert(scoped ref SequenceReader sequenceReader, long consumed) + { + sequenceReader.SetConsumed(consumed); + Assert.AreEqual(consumed, sequenceReader.Consumed); + } + + // Act/Assert + ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy(out _); + + Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf); + + SetConsumedAndAssert(ref sequenceReader, 0); + + ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B); + + SetConsumedAndAssert(ref sequenceReader, 1); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy(out _); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct MyUnmanagedStruct + { + public int BehaviourSize; + public int MemoryPoolsSize; + public short VoicesSize; + public int VoiceResourcesSize; + public short EffectsSize; + public int RenderInfoSize; + + public unsafe fixed byte Reserved[16]; + + public static readonly int SizeOf = Unsafe.SizeOf(); + + public static unsafe MyUnmanagedStruct Generate(Random rng) + { + const int BaseInt32Value = 0x1234abcd; + const short BaseInt16Value = 0x5678; + + var result = new MyUnmanagedStruct + { + BehaviourSize = BaseInt32Value ^ rng.Next(), + MemoryPoolsSize = BaseInt32Value ^ rng.Next(), + VoicesSize = (short)(BaseInt16Value ^ rng.Next()), + VoiceResourcesSize = BaseInt32Value ^ rng.Next(), + EffectsSize = (short)(BaseInt16Value ^ rng.Next()), + RenderInfoSize = BaseInt32Value ^ rng.Next(), + }; + + Unsafe.Write(result.Reserved, rng.NextInt64()); + + return result; + } + + public static unsafe void Assert(Action assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual) + { + assert(expected.BehaviourSize, actual.BehaviourSize); + assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize); + assert(expected.VoicesSize, actual.VoicesSize); + assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize); + assert(expected.EffectsSize, actual.EffectsSize); + assert(expected.RenderInfoSize, actual.RenderInfoSize); + + fixed (void* expectedReservedPtr = expected.Reserved) + fixed (void* actualReservedPtr = actual.Reserved) + { + long expectedReservedLong = Unsafe.Read(expectedReservedPtr); + long actualReservedLong = Unsafe.Read(actualReservedPtr); + + assert(expectedReservedLong, actualReservedLong); + } + } + } + + private static IEnumerable EnumerateNewUnmanagedStructs() + { + var rng = new Random(0); + + while (true) + { + yield return MyUnmanagedStruct.Generate(rng); + } + } + + private static ReadOnlySequence CreateSegmentedByteSequence(T[] array, int maxSegmentLength) where T : unmanaged + { + byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray(); + var memory = new Memory(arrayBytes); + int index = 0; + + BytesReadOnlySequenceSegment first = null, last = null; + + while (index < memory.Length) + { + int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index); + var nextSegment = memory.Slice(index, nextSegmentLength); + + if (first == null) + { + first = last = new BytesReadOnlySequenceSegment(nextSegment); + } + else + { + last = last.Append(nextSegment); + } + + index += nextSegmentLength; + } + + return new ReadOnlySequence(first, 0, last, (int)(memory.Length - last.RunningIndex)); + } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 7530a2c3b..3390e5171 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -162,6 +162,11 @@ namespace Ryujinx.UI.Common.Configuration /// public bool ShowConfirmExit { get; set; } + /// + /// Enables hardware-accelerated rendering for Avalonia + /// + public bool EnableHardwareAcceleration { get; set; } + /// /// Whether to hide cursor on idle, always or never /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 2dca1c7f2..c4f5fa5de 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -633,6 +633,11 @@ namespace Ryujinx.UI.Common.Configuration /// public ReactiveObject ShowConfirmExit { get; private set; } + /// + /// Enables hardware-accelerated rendering for Avalonia + /// + public ReactiveObject EnableHardwareAcceleration { get; private set; } + /// /// Hide Cursor on Idle /// @@ -649,6 +654,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); + EnableHardwareAcceleration = new ReactiveObject(); HideCursor = new ReactiveObject(); } @@ -686,6 +692,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration = EnableDiscordIntegration, CheckUpdatesOnStart = CheckUpdatesOnStart, ShowConfirmExit = ShowConfirmExit, + EnableHardwareAcceleration = EnableHardwareAcceleration, HideCursor = HideCursor, EnableVsync = Graphics.EnableVsync, EnableShaderCache = Graphics.EnableShaderCache, @@ -794,6 +801,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration.Value = true; CheckUpdatesOnStart.Value = true; ShowConfirmExit.Value = true; + EnableHardwareAcceleration.Value = true; HideCursor.Value = HideCursorMode.OnIdle; Graphics.EnableVsync.Value = true; Graphics.EnableShaderCache.Value = true; @@ -1457,6 +1465,15 @@ namespace Ryujinx.UI.Common.Configuration { Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 50."); + configurationFileFormat.EnableHardwareAcceleration = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 51) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 51."); + configurationFileFormat.TurboMultiplier = 200; configurationFileFormat.Hotkeys = new KeyboardHotkeys @@ -1508,6 +1525,7 @@ namespace Ryujinx.UI.Common.Configuration EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; + EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; HideCursor.Value = configurationFileFormat.HideCursor; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache; diff --git a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs index c3c5bd37e..bbacd5fec 100644 --- a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs +++ b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs @@ -8,6 +8,7 @@ namespace Ryujinx.UI.Common.Helper public static string[] Arguments { get; private set; } public static bool? OverrideDockedMode { get; private set; } + public static bool? OverrideHardwareAcceleration { get; private set; } public static string OverrideGraphicsBackend { get; private set; } public static string OverrideHideCursor { get; private set; } public static string BaseDirPathArg { get; private set; } @@ -87,6 +88,9 @@ namespace Ryujinx.UI.Common.Helper OverrideHideCursor = args[++i]; break; + case "--software-gui": + OverrideHardwareAcceleration = false; + break; default: LaunchPathArg = arg; break; diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 72a355266..cf1b5f27e 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -113,6 +113,7 @@ namespace Ryujinx.Ava private readonly object _lockObject = new(); public event EventHandler AppExit; + public event EventHandler StatusInitEvent; public event EventHandler StatusUpdatedEvent; public VirtualFileSystem VirtualFileSystem { get; } @@ -778,32 +779,32 @@ namespace Ryujinx.Ava var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB; HLEConfiguration configuration = new(VirtualFileSystem, - _viewModel.LibHacHorizonManager, - ContentManager, - _accountManager, - _userChannelPersistence, - renderer, - InitializeAudio(), - memoryConfiguration, - _viewModel.UiHandler, - (SystemLanguage)ConfigurationState.Instance.System.Language.Value, - (RegionCode)ConfigurationState.Instance.System.Region.Value, - ConfigurationState.Instance.Graphics.EnableVsync, - ConfigurationState.Instance.System.EnableDockedMode, - ConfigurationState.Instance.System.EnablePtc, - ConfigurationState.Instance.System.EnableInternetAccess, - ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, - ConfigurationState.Instance.System.FsGlobalAccessLogMode, - ConfigurationState.Instance.System.SystemTimeOffset, - ConfigurationState.Instance.System.TimeZone, - ConfigurationState.Instance.System.MemoryManagerMode, - ConfigurationState.Instance.System.IgnoreMissingServices, - ConfigurationState.Instance.Graphics.AspectRatio, - ConfigurationState.Instance.System.AudioVolume, - ConfigurationState.Instance.System.UseHypervisor, - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, - ConfigurationState.Instance.Multiplayer.Mode, - ConfigurationState.Instance.System.TurboMultiplier); + _viewModel.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + InitializeAudio(), + memoryConfiguration, + _viewModel.UiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode, + ConfigurationState.Instance.System.TurboMultiplier); Device = new Switch(configuration); } @@ -949,6 +950,7 @@ namespace Ryujinx.Ava { _renderingStarted = true; _viewModel.SwitchToRenderer(false); + InitStatus(); } Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); @@ -972,6 +974,18 @@ namespace Ryujinx.Ava (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); } + public void InitStatus() + { + StatusInitEvent?.Invoke(this, new StatusInitEventArgs( + ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch + { + GraphicsBackend.Vulkan => "Vulkan", + GraphicsBackend.OpenGl => "OpenGL", + _ => throw new NotImplementedException() + }, + $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); + } + public void UpdateStatus() { // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. @@ -985,13 +999,11 @@ namespace Ryujinx.Ava StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( Device.EnableDeviceVsync, LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", - ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)" + (Device.TurboMode ? $" Turbo ({Device.Configuration.TurboMultiplier}%)" : ""), - $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", - $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %")); } public async Task ShowExitPrompt() diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 57ca5b0ae..51df3a052 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -1,5 +1,5 @@ { - "Language": "اَلْعَرَبِيَّةُ", + "Language": "العربية", "MenuBarFileOpenApplet": "فتح التطبيق المُصغَّر", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق محرر الـMii المُصغَّر في الوضع المستقل", "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", @@ -11,8 +11,8 @@ "MenuBarFile": "_ملف", "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", "MenuBarFileOpenUnpacked": "تحميل لعبه غير محزومه", - "MenuBarFileOpenEmuFolder": "فتح مجلّد Ryujinx", - "MenuBarFileOpenLogsFolder": "افتح مجلد السجلات", + "MenuBarFileOpenEmuFolder": "فتح مجلد Ryujinx", + "MenuBarFileOpenLogsFolder": "فتح مجلد السجلات", "MenuBarFileExit": "_خروج", "MenuBarOptions": "_خيارات", "MenuBarOptionsToggleFullscreen": "وضع ملء الشاشة", @@ -73,9 +73,9 @@ "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", "GameListContextMenuCreateShortcutToolTip": "قم بإنشاء اختصار لسطح المكتب لتشغيل التطبيق المحدد", "GameListContextMenuCreateShortcutToolTipMacOS": "قم بإنشاء اختصار في مجلد تطبيقات نظام التشغيل MacOS الذي يقوم بتشغيل التطبيق المحدد", - "GameListContextMenuOpenModsDirectory": "افتح مجلد التعديلات", + "GameListContextMenuOpenModsDirectory": "فتح مجلد التعديلات", "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات التطبيق", - "GameListContextMenuOpenSdModsDirectory": "افتح مجلد تعديلات Atmosphere", + "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح دليل Atmosphere لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", "StatusBarGamesLoaded": "{0}/{1} الألعاب التي تم تحميلها", "StatusBarSystemVersion": "إصدار النظام: {0}", @@ -141,7 +141,7 @@ "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", - "SettingsTabGraphics": "الرسوميات", + "SettingsTabGraphics": "الرسومات", "SettingsTabGraphicsAPI": "الرسومات API", "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", "SettingsTabGraphicsAnisotropicFiltering": "تصفية متباين الخواص:", @@ -174,8 +174,8 @@ "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", - "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول لـ Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل وصول عالمي Fs :", "SettingsTabLoggingDeveloperOptions": "خيارات المطور", "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", @@ -307,7 +307,7 @@ "DialogMessageSaveNotAvailableMessage": "لا يوجد حفظ لـ {0} [{1:x16}]", "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء حفظ لهذه اللعبة؟", "DialogConfirmationTitle": "Ryujinx - تأكيد", - "DialogUpdaterTitle": "Ryujinx - تحديث", + "DialogUpdaterTitle": "تحديث Ryujinx", "DialogErrorTitle": "Ryujinx - خطأ", "DialogWarningTitle": "Ryujinx - تحذير", "DialogExitTitle": "Ryujinx - الخروج", @@ -323,7 +323,7 @@ "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف السجل لمزيد من المعلومات.", "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار Ryujinx الحالي.", - "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث!", + "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث", "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من Ryujinx!", "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار GitHub. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة GitHub Actions. جرب مجددا بعد دقائق.", "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار Ryujinx المستلم من إصدار Github.", @@ -355,7 +355,7 @@ "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", "DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", - "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogUserErrorDialogTitle": "خطأ ريوجينكس ({0})", "DialogAmiiboApiTitle": "أميبو API", "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من واجهة برمجة التطبيقات.", "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API Amiibo. قد تكون الخدمة متوقفة أو قد تحتاج إلى التحقق من اتصال الإنترنت الخاص بك على الإنترنت.", @@ -384,7 +384,7 @@ "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogLoadFileErrorMessage": "{0}. ملف خاطئ: {1}", "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للملف المحدد!", "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "أنت على وشك حذف التعديل: {0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogModManagerDeletionAllWarningMessage": "أنت على وشك حذف كافة التعديلات لهذا العنوان.\n\nهل انت متأكد انك تريد المتابعة؟", "SettingsTabGraphicsFeaturesOptions": "المميزات", "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", "CommonAuto": "تلقائي", @@ -411,19 +411,19 @@ "MenuBarOptionsPauseEmulation": "إيقاف مؤقت", "MenuBarOptionsResumeEmulation": "استئناف", "AboutUrlTooltipMessage": "انقر لفتح موقع Ryujinx في متصفحك الافتراضي.", - "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", - "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", - "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", - "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", - "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", - "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.", + "AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.", + "AboutGithubUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في غيت هاب في متصفحك الافتراضي.", + "AboutDiscordUrlTooltipMessage": "انقر لفتح دعوة إلى خادم ريوجينكس في ديكسورد في متصفحك الافتراضي.", + "AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.", "AboutRyujinxAboutTitle": "حول:", - "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.", "AboutRyujinxMaintainersTitle": "تم إصلاحها بواسطة:", "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", "AmiiboSeriesLabel": "مجموعة أميبو", - "AmiiboCharacterLabel": "Character", + "AmiiboCharacterLabel": "شخصية", "AmiiboScanButtonLabel": "فحصه", "AmiiboOptionsShowAllLabel": "إظهار كل أميبو", "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", @@ -444,38 +444,38 @@ "OrderDescending": "ترتيب تنازلي", "SettingsTabGraphicsFeatures": "الميزات والتحسينات", "ErrorWindowTitle": "نافذة الخطأ", - "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليًا\" أم لا", "AddGameDirBoxTooltip": "أدخل دليل اللعبة لإضافته إلى القائمة", "AddGameDirTooltip": "إضافة دليل اللعبة إلى القائمة", "RemoveGameDirTooltip": "إزالة دليل اللعبة المحدد", - "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemeCheckTooltip": "استخدم سمة أفالونيا المخصصة لواجهة المستخدم الرسومية لتغيير مظهر قوائم المحاكي", "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", - "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DockModeToggleTooltip": "يجعل وضع الإرساء النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم إرساءه. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع الإرساء؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدًا.", + "DirectKeyboardTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", + "DirectMouseTooltip": "دعم الوصول المباشر إلى لوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", "RegionTooltip": "تغيير منطقة النظام", "LanguageTooltip": "تغيير لغة النظام", "TimezoneTooltip": "تغيير المنطقة الزمنية للنظام", "TimeTooltip": "تغيير وقت النظام", "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", - "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.", "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", - "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.", "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", - "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.", "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", - "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل الصقل أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.", + "ResolutionScaleEntryTooltip": "مقياس دقة النقطة العائمة، مثل 1.5. من المرجح أن تتسبب المقاييس غير المتكاملة في حدوث مشكلات أو تعطل.", + "AnisotropyTooltip": "مستوى تصفية متباين الخواص. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.", + "AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه على 16:9 إذا لم تكن متأكدًا.", "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", @@ -488,14 +488,14 @@ "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", "DeveloperOptionTooltip": "استخدمه بعناية", "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", - "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "DebugLogTooltip": "طباعة رسائل سجل التصحيح في وحدة التحكم.\n\nاستخدم هذا فقط إذا طلب منك أحد الموظفين تحديدًا ذلك، لأنه سيجعل من الصعب قراءة السجلات وسيؤدي إلى تدهور أداء المحاكي.", "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع Switch لتحميله", "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع Switch للتحميل", "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات Ryujinx", - "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "OpenRyujinxLogsTooltip": "يفتح المجلد الذي تتم كتابة السجلات إليه", "ExitTooltip": "الخروج من Ryujinx", "OpenSettingsTooltip": "فتح نافذة الإعدادات", - "OpenProfileManagerTooltip": "افتح نافذة إدارة ملفات تعريف المستخدمين", + "OpenProfileManagerTooltip": "فتح نافذة إدارة ملفات تعريف المستخدمين", "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", "CheckUpdatesTooltip": "التحقق من وجود تحديثات لـ Ryujinx", "OpenAboutTooltip": "فتح حول النافذة", @@ -505,7 +505,7 @@ "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", "SettingsTabSystemAudioVolume": "الحجم:", "AudioVolumeTooltip": "تغيير مستوى الصوت", - "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "SettingsTabSystemEnableInternetAccess": "الوصول إلى إنترنت كضيف/وضع LAN", "EnableInternetAccessTooltip": "للسماح للتطبيق المحاكي بالاتصال بالإنترنت.\n\nالألعاب ذات وضع الشبكة المحلية يمكن الاتصال ببعضها البعض عندما يتم تمكين هذا النظام وتكون الأنظمة متصلة بنفس نقطة الوصول. هذا يشمل وحدات التحكم الحقيقية أيضًا.\n\nلا يسمح بالاتصال بخوادم Nintendo. قد يسبب الانهيار في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه متوقفا إن لم يكن مؤكدا.", "GameListContextMenuManageCheatToolTip": "إدارة الغش", "GameListContextMenuManageCheat": "إدارة الغش", @@ -533,10 +533,10 @@ "UserErrorApplicationNotFound": "التطبيق غير موجود", "UserErrorUnknown": "خطأ غير معروف", "UserErrorUndefined": "خطأ غير محدد", - "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", - "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", - "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorNoKeysDescription": "لم يتمكن Ryujinx من العثور على ملف 'prod.keys' الخاص بك", + "UserErrorNoFirmwareDescription": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة", + "UserErrorFirmwareParsingFailedDescription": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.", + "UserErrorApplicationNotFoundDescription": "تعذر على ريوجينكس العثور على تطبيق صالح في المسار المحدد.", "UserErrorUnknownDescription": "حدث خطأ غير معروف!", "UserErrorUndefinedDescription": "حدث خطأ غير محدد! لا ينبغي أن يحدث هذا، يرجى الاتصال بمطور!", "OpenSetupGuideMessage": "فتح دليل الإعداد", @@ -546,12 +546,12 @@ "RyujinxConfirm": "Ryujinx - تأكيد", "FileDialogAllTypes": "كل الأنواع", "Never": "مطلقاً", - "SwkbdMinCharacters": "Must be at least {0} characters long", - "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفًا على الأقل", + "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفًا", "SoftwareKeyboard": "لوحة المفاتيح البرمجية", "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط", + "SoftwareKeyboardModeASCII": "يجب أن يكون نص ASCII فقط", "ControllerAppletControllers": "ذراع التحكم المدعومة:", "ControllerAppletPlayers": "اللاعبين:", "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", @@ -593,7 +593,7 @@ "Writable": "قابل للكتابة", "SelectDlcDialogTitle": "حدد ملفات DLC", "SelectUpdateDialogTitle": "حدد ملفات التحديث", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "حدد مجلد التعديل", "UserProfileWindowTitle": "مدير ملفات تعريف المستخدمين", "CheatWindowTitle": "مدير الغش", "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", @@ -612,7 +612,7 @@ "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", "SettingsTabHotkeysResScaleUpHotkey": "زيادة الدقة:", - "SettingsTabHotkeysResScaleDownHotkey": "تقليل الدقة:", + "SettingsTabHotkeysResScaleDownHotkey": "خفض الدقة:", "UserProfilesName": "الاسم:", "UserProfilesUserId": "معرف المستخدم:", "SettingsTabGraphicsBackend": "خلفية الرسومات", @@ -626,7 +626,7 @@ "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", "RyujinxUpdaterMessage": "هل تريد تحديث Ryujinx إلى أحدث إصدار؟", "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", - "SettingsTabHotkeysVolumeDownHotkey": "تقليل الحجم:", + "SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:", "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.", "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", @@ -639,7 +639,7 @@ "SaveManagerTitle": "مدير الحفظ", "Name": "الاسم", "Size": "الحجم", - "Search": "البحث", + "Search": "بحث", "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", "Recover": "استعادة", "UserProfilesRecoverHeading": "تم العثور على الحفظ للحسابات التالية", @@ -648,8 +648,11 @@ "GraphicsAALabel": "تنعيم الحواف:", "GraphicsScalingFilterLabel": "فلتر التكبير:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "المستوى", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "اضبط مستوى وضوح FSR 1.0. الأعلى هو أكثر وضوحا.", "SmaaLow": "SMAA منخفض", "SmaaMedium": "SMAA متوسط", "SmaaHigh": "SMAA عالي", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", "SettingsTabNetworkMultiplayer": "لعب جماعي", "MultiplayerMode": "النمط:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "معطل", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index c70660532..71af4f3fe 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -73,10 +73,10 @@ "GameListContextMenuCreateShortcut": "Erstelle Anwendungsverknüpfung", "GameListContextMenuCreateShortcutToolTip": "Erstelle eine Desktop-Verknüpfung die die gewählte Anwendung startet", "GameListContextMenuCreateShortcutToolTipMacOS": "Erstellen Sie eine Verknüpfung im MacOS-Programme-Ordner, die die ausgewählte Anwendung startet", - "GameListContextMenuOpenModsDirectory": "Open Mods Directory", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", + "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für echte Hardware erstellt worden sind.", "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", "StatusBarSystemVersion": "Systemversion: {0}", "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "Direkter Zugriff auf die Tastatur (HID). Bietet Spielen Zugriff auf Ihre Tastatur als Texteingabegerät.\n\nFunktioniert nur mit Spielen, die die Tastaturnutzung auf Switch-Hardware nativ unterstützen.\n\nAus lassen, wenn unsicher.", + "DirectMouseTooltip": "Unterstützt den direkten Mauszugriff (HID). Bietet Spielen Zugriff auf Ihre Maus als Zeigegerät.\n\nFunktioniert nur mit Spielen, die nativ die Steuerung mit der Maus auf Switch-Hardware unterstützen (nur sehr wenige).\n\nTouchscreen-Funktionalität ist möglicherweise eingeschränkt, wenn dies aktiviert ist.\n\n Aus lassen, wenn unsicher.", "RegionTooltip": "Ändert die Systemregion", "LanguageTooltip": "Ändert die Systemsprache", "TimezoneTooltip": "Ändert die Systemzeitzone", "TimeTooltip": "Ändert die Systemzeit", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist quasi ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen oder Ladebildschirme länger benötigen/hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden (standardmäßig F1). \n\nIm Zweifelsfall AN lassen.", "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "Multipliziert die Rendering-Auflösung des Spiels.\n\nEinige wenige Spiele funktionieren damit nicht und sehen auch bei höherer Auflösung pixelig aus; für diese Spiele müssen Sie möglicherweise Mods finden, die Anti-Aliasing entfernen oder die interne Rendering-Auflösung erhöhen. Für die Verwendung von Letzterem sollten Sie Native wählen.\n\nSie können diese Option ändern, während ein Spiel läuft, indem Sie unten auf \"Übernehmen\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nDenken Sie daran, dass 4x für praktisch jedes Setup Overkill ist.", "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "Stufe der Anisotropen Filterung. Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden.", + "AspectRatioTooltip": "Seitenverhältnis, das auf das Renderer-Fenster angewendet wird.\n\nÄndern Sie dies nur, wenn Sie einen Seitenverhältnis-Mod für Ihr Spiel verwenden, da sonst die Grafik gestreckt wird.\n\nLassen Sie es auf 16:9, wenn Sie unsicher sind.", "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", @@ -616,9 +616,9 @@ "UserProfilesName": "Name:", "UserProfilesUserId": "Benutzer-ID:", "SettingsTabGraphicsBackend": "Grafik-Backend:", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "Wählen Sie das Grafik-Backend, das im Emulator verwendet werden soll.\n\nVulkan ist insgesamt besser für alle modernen Grafikkarten geeignet, sofern deren Treiber auf dem neuesten Stand sind. Vulkan bietet auch eine schnellere Shader-Kompilierung (weniger Stottern) auf allen GPU-Anbietern.\n\nOpenGL kann auf alten Nvidia-GPUs, alten AMD-GPUs unter Linux oder auf GPUs mit geringerem VRAM bessere Ergebnisse erzielen, obwohl die Shader-Kompilierung stärker stottert.\n\nSetzen Sie auf Vulkan, wenn Sie unsicher sind. Stellen Sie OpenGL ein, wenn Ihr Grafikprozessor selbst mit den neuesten Grafiktreibern Vulkan nicht unterstützt.", "SettingsEnableTextureRecompression": "Textur-Rekompression", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "Komprimiert ASTC-Texturen, um die VRAM-Nutzung zu reduzieren.\n\nZu den Spielen, die dieses Texturformat verwenden, gehören Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder und The Legend of Zelda: Tears of the Kingdom.\n\nGrafikkarten mit 4GiB VRAM oder weniger werden beim Ausführen dieser Spiele wahrscheinlich irgendwann abstürzen.\n\nAktivieren Sie diese Option nur, wenn Ihnen bei den oben genannten Spielen der VRAM ausgeht. Lassen Sie es aus, wenn Sie unsicher sind.", "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", @@ -644,12 +644,15 @@ "Recover": "Wiederherstellen", "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Rendering des Spiels an.\n\nFXAA verwischt den größten Teil des Bildes, während SMAA versucht, gezackte Kanten zu finden und sie zu glätten.\n\nEs wird nicht empfohlen, diese Option in Verbindung mit dem FSR-Skalierungsfilter zu verwenden.\n\nDiese Option kann geändert werden, während ein Spiel läuft, indem Sie unten auf \"Anwenden\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nLassen Sie die Option auf NONE, wenn Sie unsicher sind.", "GraphicsAALabel": "Antialiasing:", "GraphicsScalingFilterLabel": "Skalierungsfilter:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "Wählen Sie den Skalierungsfilter, der bei der Auflösungsskalierung angewendet werden soll.\n\nBilinear eignet sich gut für 3D-Spiele und ist eine sichere Standardoption.\n\nNearest wird für Pixel-Art-Spiele empfohlen.\n\nFSR 1.0 ist lediglich ein Schärfungsfilter und wird nicht für die Verwendung mit FXAA oder SMAA empfohlen.\n\nDiese Option kann geändert werden, während ein Spiel läuft, indem Sie unten auf \"Anwenden\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nBleiben Sie auf BILINEAR, wenn Sie unsicher sind.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nächstes", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Stufe", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0 Schärfelevel festlegen. Höher ist schärfer.", "SmaaLow": "SMAA Niedrig", "SmaaMedium": "SMAA Mittel", "SmaaHigh": "SMAA Hoch", @@ -657,12 +660,14 @@ "UserEditorTitle": "Nutzer bearbeiten", "UserEditorTitleCreate": "Nutzer erstellen", "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "Die für LAN/LDN-Funktionen verwendete Netzwerkschnittstelle.\n\nIn Verbindung mit einem VPN oder XLink Kai und einem Spiel mit LAN-Unterstützung kann eine Verbindung mit demselben Netzwerk über das Internet vorgetäuscht werden.\n\nIm Zweifelsfall auf DEFAULT belassen.", "NetworkInterfaceDefault": "Standard", "PackagingShaders": "Verpackt Shader", "AboutChangelogButton": "Changelog in GitHub öffnen", "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen.", "SettingsTabNetworkMultiplayer": "Mehrspieler", "MultiplayerMode": "Modus:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.", + "MultiplayerModeDisabled": "Deaktiviert", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 5b7e3b97d..ba5c7078c 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", "DialogInstallFileTypesSuccessMessage": "Επιτυχής εγκατάσταση τύπων αρχείων!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anti-Aliasing", "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Επίπεδο", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Χαμηλό SMAA", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Κάντε κλικ για να ανοίξετε το αρχείο αλλαγών για αυτήν την έκδοση στο προεπιλεγμένο πρόγραμμα περιήγησης σας.", "SettingsTabNetworkMultiplayer": "Πολλαπλοί παίκτες", "MultiplayerMode": "Λειτουργία:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 6214b0a94..16f12f519 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -601,6 +601,7 @@ "UserProfileWindowTitle": "User Profiles Manager", "CheatWindowTitle": "Cheats Manager", "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", + "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Title Update Manager", "CheatWindowHeading": "Cheats Available for {0} [{1}]", "BuildId": "BuildId:", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index ff41c855f..38499c046 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "No se encontró firmware instalado pero Ryujinx pudo instalar el firmware {0} a partir de este juego.\nA continuación, se iniciará el emulador.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", "DialogInstallFileTypesSuccessMessage": "¡Tipos de archivos instalados con éxito!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Suavizado de bordes:", "GraphicsScalingFilterLabel": "Filtro de escalado:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Nivel", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Bajo", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado.", "SettingsTabNetworkMultiplayer": "Multijugador", "MultiplayerMode": "Modo:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index b1501d848..bb2864cf2 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anticrénelage :", "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", "GraphicsScalingFilterTooltip": "Choisissez le filtre de mise à l'échelle qui sera appliqué lors de l'utilisation de la mise à l'échelle de la résolution.\n\nLe filtre bilinéaire fonctionne bien pour les jeux en 3D et constitue une option par défaut sûre.\n\nLe filtre le plus proche est recommandé pour les jeux de pixel art.\n\nFSR 1.0 est simplement un filtre de netteté, non recommandé pour une utilisation avec FXAA ou SMAA.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'aspect souhaité pour un jeu.\n\nLaissez sur BILINEAR si vous n'êtes pas sûr.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Niveau ", "GraphicsScalingFilterLevelTooltip": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.", "SmaaLow": "SMAA Faible", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut.", "SettingsTabNetworkMultiplayer": "Multijoueur", "MultiplayerMode": "Mode :", - "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr." + "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index afa92f8d2..16d68b775 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (לא מומלץ)", "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -648,6 +648,9 @@ "GraphicsAALabel": "החלקת-עקומות:", "GraphicsScalingFilterLabel": "מסנן מידת איכות:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "רמה", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA נמוך", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך.", "SettingsTabNetworkMultiplayer": "רב משתתפים", "MultiplayerMode": "מצב:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 7db45b001..0e832ffb6 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -1,6 +1,6 @@ { "Language": "Italiano", - "MenuBarFileOpenApplet": "Apri Applet", + "MenuBarFileOpenApplet": "Apri applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", "SettingsTabInputDirectMouseAccess": "Accesso diretto mouse", "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", @@ -12,7 +12,7 @@ "MenuBarFileOpenFromFile": "_Carica applicazione da un file", "MenuBarFileOpenUnpacked": "Carica _gioco estratto", "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", - "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", + "MenuBarFileOpenLogsFolder": "Apri cartella dei log", "MenuBarFileExit": "_Esci", "MenuBarOptions": "_Opzioni", "MenuBarOptionsToggleFullscreen": "Schermo intero", @@ -40,29 +40,29 @@ "GameListHeaderDeveloper": "Sviluppatore", "GameListHeaderVersion": "Versione", "GameListHeaderTimePlayed": "Tempo di gioco", - "GameListHeaderLastPlayed": "Giocato l'ultima volta", + "GameListHeaderLastPlayed": "Ultima partita", "GameListHeaderFileExtension": "Estensione", "GameListHeaderFileSize": "Dimensione file", "GameListHeaderPath": "Percorso", - "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente", - "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", - "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella dei salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i dati di salvataggio dell'utente", + "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dei salvataggi del dispositivo", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i dati di salvataggio del dispositivo", + "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella del salvataggio BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene il salvataggio BCAT dell'applicazione", "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", - "GameListContextMenuManageDlc": "Gestici DLC", - "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", + "GameListContextMenuManageDlc": "Gestisci DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione dei DLC", "GameListContextMenuCacheManagement": "Gestione della cache", - "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", - "GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione", + "GameListContextMenuCacheManagementPurgePptc": "Accoda rigenerazione della cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Esegue la rigenerazione della cache PPTC al prossimo avvio del gioco", + "GameListContextMenuCacheManagementPurgeShaderCache": "Elimina la cache degli shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la cache degli shader dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri la cartella della cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la cache PPTC dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri la cartella della cache degli shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la cache degli shader dell'applicazione", "GameListContextMenuExtractData": "Estrai dati", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", @@ -70,16 +70,16 @@ "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", - "GameListContextMenuCreateShortcut": "Crea Collegamento", - "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento del gioco sul Desktop", - "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento del gioco sulla Scrivania", - "GameListContextMenuOpenModsDirectory": "Apri la cartella delle Mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella delle Mods del gioco", - "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle Mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella delle Mods Atmosphere (Utilizzare solo per le Mods eseguite su Hardware reale)", - "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", + "GameListContextMenuCreateShortcut": "Crea collegamento", + "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento sul desktop che avvia l'applicazione selezionata", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento nella cartella Applicazioni di macOS che avvia l'applicazione selezionata", + "GameListContextMenuOpenModsDirectory": "Apri la cartella delle mod", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", + "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle mod Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella alternativa di Atmosphere sulla scheda SD che contiene le mod dell'applicazione. Utile per le mod create per funzionare sull'hardware reale.", + "StatusBarGamesLoaded": "{0}/{1} giochi caricati", "StatusBarSystemVersion": "Versione di sistema: {0}", - "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", + "LinuxVmMaxMapCountDialogTitle": "Rilevato limite basso per le mappature di memoria", "LinuxVmMaxMapCountDialogTextPrimary": "Vuoi aumentare il valore di vm.max_map_count a {0}?", "LinuxVmMaxMapCountDialogTextSecondary": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sì, fino al prossimo riavvio", @@ -94,7 +94,7 @@ "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", "SettingsTabGeneralHideCursor": "Nascondi il cursore:", "SettingsTabGeneralHideCursorNever": "Mai", - "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", + "SettingsTabGeneralHideCursorOnIdle": "Quando è inattivo", "SettingsTabGeneralHideCursorAlways": "Sempre", "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", "SettingsTabGeneralAdd": "Aggiungi", @@ -133,17 +133,17 @@ "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", "SettingsTabSystemAudioBackend": "Backend audio:", - "SettingsTabSystemAudioBackendDummy": "Manichino", + "SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "AudioIO", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "Trucchi", - "SettingsTabSystemHacksNote": " (Possono causare instabilità)", - "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB", + "SettingsTabSystemHacks": "Espedienti", + "SettingsTabSystemHacksNote": "Possono causare instabilità", + "SettingsTabSystemExpandDramSize": "Usa layout di memoria alternativo (per sviluppatori)", "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", "SettingsTabGraphics": "Grafica", - "SettingsTabGraphicsAPI": "API Grafiche", - "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", + "SettingsTabGraphicsAPI": "API grafica", + "SettingsTabGraphicsEnableShaderCache": "Attiva la cache degli shader", "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", @@ -163,34 +163,34 @@ "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", - "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", - "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", + "SettingsTabGraphicsDeveloperOptions": "Opzioni per sviluppatori", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shader:", "SettingsTabLogging": "Log", "SettingsTabLoggingLogging": "Log", "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", - "SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs", - "SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs", - "SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs", - "SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs", - "SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs", - "SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs", - "SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs", - "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:", - "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", + "SettingsTabLoggingEnableStubLogs": "Attiva log di stub", + "SettingsTabLoggingEnableInfoLogs": "Attiva log di informazioni", + "SettingsTabLoggingEnableWarningLogs": "Attiva log di avviso", + "SettingsTabLoggingEnableErrorLogs": "Attiva log di errore", + "SettingsTabLoggingEnableTraceLogs": "Attiva log di trace", + "SettingsTabLoggingEnableGuestLogs": "Attiva log del guest", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva log di accesso FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log di accesso globale FS:", + "SettingsTabLoggingDeveloperOptions": "Opzioni per sviluppatori", "SettingsTabLoggingDeveloperOptionsNote": "ATTENZIONE: ridurrà le prestazioni", - "SettingsTabLoggingGraphicsBackendLogLevel": "Livello registro backend grafico:", + "SettingsTabLoggingGraphicsBackendLogLevel": "Livello di log del backend grafico:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", - "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", + "SettingsTabLoggingEnableDebugLogs": "Attiva log di debug", "SettingsTabInput": "Input", "SettingsTabInputEnableDockedMode": "Attiva modalità TV", "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", "SettingsButtonSave": "Salva", "SettingsButtonClose": "Chiudi", "SettingsButtonOk": "OK", - "SettingsButtonCancel": "Cancella", + "SettingsButtonCancel": "Annulla", "SettingsButtonApply": "Applica", "ControllerSettingsPlayer": "Giocatore", "ControllerSettingsPlayer1": "Giocatore 1", @@ -237,8 +237,8 @@ "ControllerSettingsStickInvertXAxis": "Inverti levetta X", "ControllerSettingsStickInvertYAxis": "Inverti levetta Y", "ControllerSettingsStickDeadzone": "Zona morta:", - "ControllerSettingsLStick": "Stick sinistro", - "ControllerSettingsRStick": "Stick destro", + "ControllerSettingsLStick": "Levetta sinistra", + "ControllerSettingsRStick": "Levetta destra", "ControllerSettingsTriggersLeft": "Grilletto sinistro", "ControllerSettingsTriggersRight": "Grilletto destro", "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", @@ -252,7 +252,7 @@ "ControllerSettingsLeftSR": "SR", "ControllerSettingsRightSL": "SL", "ControllerSettingsRightSR": "SR", - "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", + "ControllerSettingsExtraButtonsLeft": "Tasto sinistro", "ControllerSettingsExtraButtonsRight": "Tasto destro", "ControllerSettingsMisc": "Varie", "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", @@ -279,10 +279,10 @@ "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", "ProfileImageSelectionImportImage": "Importa file immagine", "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", - "InputDialogTitle": "Finestra di dialogo ", + "InputDialogTitle": "Finestra di input", "InputDialogOk": "OK", "InputDialogCancel": "Annulla", - "InputDialogAddNewProfileTitle": "Scegli il nome profilo", + "InputDialogAddNewProfileTitle": "Scegli il nome del profilo", "InputDialogAddNewProfileHeader": "Digita un nome profilo", "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", "AvatarChoose": "Scegli", @@ -292,8 +292,8 @@ "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", "ControllerSettingsSaveProfileToolTip": "Salva profilo", - "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", - "MenuBarFileToolsHideUi": "Nascondi UI", + "MenuBarFileToolsTakeScreenshot": "Cattura uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi l'interfaccia", "GameListContextMenuRunApplication": "Esegui applicazione", "GameListContextMenuToggleFavorite": "Preferito", "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", @@ -318,14 +318,14 @@ "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA", + "DialogNcaExtractionTitle": "Ryujinx - Estrazione sezione NCA", "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", - "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Consulta il file di log per maggiori informazioni.", "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", - "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", + "DialogUpdaterCancelUpdateMessage": "Annullamento dell'aggiornamento in corso!", "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di ottenere informazioni sulla versione da GitHub Release. Ciò può essere causato se una nuova versione viene compilata da GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di recuperare le informazioni sulla versione da GitHub Release. Ciò può verificarsi se una nuova versione è in fase di compilazione da GitHub Actions. Riprova tra qualche minuto.", "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", @@ -338,7 +338,7 @@ "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", "DialogRestartRequiredMessage": "Riavvio richiesto", - "DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.", + "DialogThemeRestartMessage": "Il tema è stato salvato. È richiesto un riavvio per applicare il tema.", "DialogThemeRestartSubMessage": "Vuoi riavviare?", "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", @@ -354,7 +354,7 @@ "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", + "DialogUserErrorDialogInfoMessage": "\nPer maggiori informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", @@ -364,10 +364,10 @@ "DialogProfileDeleteProfileTitle": "Eliminazione profilo", "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", "DialogWarning": "Avviso", - "DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", - "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}", - "DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", - "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}", + "DialogPPTCDeletionMessage": "Stai per accodare la rigenerazione della cache PPTC al prossimo avvio per:\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della cache PPTC a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la cache degli shader per:\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della cache degli shader a {0}: {1}", "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", @@ -385,30 +385,30 @@ "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", "DialogLoadFileErrorMessage": "{0}. Errore File: {1}", - "DialogModAlreadyExistsMessage": "La Mod risulta già installata", - "DialogModInvalidMessage": "Questa cartella non contiene nessuna Mods!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogModAlreadyExistsMessage": "La mod risulta già installata", + "DialogModInvalidMessage": "La cartella specificata non contiene nessuna mod!", + "DialogModDeleteNoParentMessage": "Eliminazione non riuscita: impossibile trovare la directory superiore per la mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitarlo adesso?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato il dump degli shader, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il dump degli shader. Vuoi disabilitarlo adesso?", "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "Stai per eliminare la mod: {0}\n\nConfermi di voler procedere?", + "DialogModManagerDeletionAllWarningMessage": "Stai per eliminare tutte le mod per questo titolo.\n\nVuoi davvero procedere?", "SettingsTabGraphicsFeaturesOptions": "Funzionalità", - "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", + "SettingsTabGraphicsBackendMultithreading": "Multithreading del backend grafico:", "CommonAuto": "Auto", - "CommonOff": "Spento", - "CommonOn": "Acceso", - "InputDialogYes": "Si", + "CommonOff": "Disattivato", + "CommonOn": "Attivo", + "InputDialogYes": "Sì", "InputDialogNo": "No", - "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", - "MenuBarOptionsPauseEmulation": "Pausa", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene dei caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Metti in pausa", "MenuBarOptionsResumeEmulation": "Riprendi", "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", @@ -418,15 +418,15 @@ "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", "AboutRyujinxAboutTitle": "Informazioni:", - "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la console Nintendo Switch™.\nSostienici su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", "AboutRyujinxMaintainersTitle": "Mantenuto da:", - "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei contributori nel tuo browser predefinito.", "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", "AmiiboSeriesLabel": "Serie Amiibo", "AmiiboCharacterLabel": "Personaggio", - "AmiiboScanButtonLabel": "Scannerizza", + "AmiiboScanButtonLabel": "Scansiona", "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", - "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", + "AmiiboOptionsUsRandomTagLabel": "Espediente: Usa un UUID del tag casuale", "DlcManagerTableHeadingEnabledLabel": "Abilitato", "DlcManagerTableHeadingTitleIdLabel": "ID Titolo", "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", @@ -434,7 +434,7 @@ "DlcManagerRemoveAllButton": "Rimuovi tutti", "DlcManagerEnableAllButton": "Abilita tutto", "DlcManagerDisableAllButton": "Disabilita tutto", - "ModManagerDeleteAllButton": "Delete All", + "ModManagerDeleteAllButton": "Elimina tutto", "MenuBarOptionsChangeLanguage": "Cambia lingua", "MenuBarShowFileTypes": "Mostra tipi di file", "CommonSort": "Ordina", @@ -442,75 +442,75 @@ "CommonFavorite": "Preferito", "OrderAscending": "Crescente", "OrderDescending": "Decrescente", - "SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti", - "ErrorWindowTitle": "Finestra errore", - "ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence", - "AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista", - "AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista", - "RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata", + "SettingsTabGraphicsFeatures": "Funzionalità e miglioramenti", + "ErrorWindowTitle": "Finestra di errore", + "ToggleDiscordTooltip": "Scegli se mostrare o meno Ryujinx nella tua attività su Discord", + "AddGameDirBoxTooltip": "Inserisci una cartella dei giochi per aggiungerla alla lista", + "AddGameDirTooltip": "Aggiungi una cartella dei giochi alla lista", + "RemoveGameDirTooltip": "Rimuovi la cartella dei giochi selezionata", "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", - "DockModeToggleTooltip": "Attiva o disabilta modalità TV", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DockModeToggleTooltip": "La modalità TV fa sì che il sistema emulato si comporti come una Nintendo Switch posizionata nella sua base. Ciò migliora la qualità grafica nella maggior parte dei giochi. Al contrario, disabilitandola il sistema emulato si comporterà come una Nintendo Switch in modalità portatile, riducendo la qualità grafica.\n\nConfigura i controlli del giocatore 1 se intendi usare la modalità TV; configura i controlli della modalità portatile se intendi usare quest'ultima.\n\nNel dubbio, lascia l'opzione attiva.", + "DirectKeyboardTooltip": "Supporto per l'accesso diretto alla tastiera (HID). Fornisce ai giochi l'accesso alla tastiera come dispositivo di inserimento del testo.\n\nFunziona solo con i giochi che supportano nativamente l'utilizzo della tastiera su hardware Switch.\n\nNel dubbio, lascia l'opzione disattivata.", + "DirectMouseTooltip": "Supporto per l'accesso diretto al mouse (HID). Fornisce ai giochi l'accesso al mouse come dispositivo di puntamento.\n\nFunziona solo con i rari giochi che supportano nativamente l'utilizzo del mouse su hardware Switch.\n\nQuando questa opzione è attivata, il touchscreen potrebbe non funzionare.\n\nNel dubbio, lascia l'opzione disattivata.", "RegionTooltip": "Cambia regione di sistema", "LanguageTooltip": "Cambia lingua di sistema", "TimezoneTooltip": "Cambia fuso orario di sistema", "TimeTooltip": "Cambia data e ora di sistema", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", - "PptcToggleTooltip": "Attiva o disattiva PPTC", - "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", - "AudioBackendTooltip": "Cambia backend audio", - "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", + "VSyncToggleTooltip": "Sincronizzazione verticale della console Emulata. Essenzialmente un limitatore di frame per la maggior parte dei giochi; disabilitarlo può far girare giochi a velocità più alta, allungare le schermate di caricamento o farle bloccare.\n\nPuò essere attivata in gioco con un tasto di scelta rapida (F1 per impostazione predefinita). Ti consigliamo di farlo se hai intenzione di disabilitarlo.\n\nLascia ON se non sei sicuro.", + "PptcToggleTooltip": "Salva le funzioni JIT tradotte in modo che non debbano essere tradotte tutte le volte che si avvia un determinato gioco.\n\nRiduce i fenomeni di stuttering e velocizza sensibilmente gli avvii successivi del gioco.\n\nNel dubbio, lascia l'opzione attiva.", + "FsIntegrityToggleTooltip": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.", + "AudioBackendTooltip": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.", "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", "UseHypervisorTooltip": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.", - "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB", - "IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti", - "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", - "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", - "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "DRamTooltip": "Utilizza un layout di memoria alternativo per imitare un'unità di sviluppo di Switch.\n\nQuesta opzione è utile soltanto per i pacchetti di texture ad alta risoluzione o per le mod che aumentano la risoluzione a 4K. NON migliora le prestazioni.\n\nNel dubbio, lascia l'opzione disattivata.", + "IgnoreMissingServicesTooltip": "Ignora i servizi non implementati del sistema operativo Horizon. Può aiutare ad aggirare gli arresti anomali che si verificano avviando alcuni giochi.\n\nNel dubbio, lascia l'opzione disattivata.", + "GraphicsBackendThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Auto.", + "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Auto.", + "ShaderCacheToggleTooltip": "Salva una cache degli shader su disco che riduce i fenomeni di stuttering nelle esecuzioni successive.\n\nNel dubbio, lascia l'opzione attiva.", + "ResolutionScaleTooltip": "Moltiplica la risoluzione di rendering del gioco.\n\nAlcuni giochi potrebbero non funzionare con questa opzione e sembrare pixelati anche quando la risoluzione è aumentata; per quei giochi, potrebbe essere necessario trovare mod che rimuovono l'anti-aliasing o che aumentano la risoluzione di rendering interna. Per quest'ultimo caso, probabilmente dovrai selezionare Nativo (1x).\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nTenete a mente che 4x è troppo per praticamente qualsiasi configurazione.", "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", - "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", - "FileLogTooltip": "Attiva o disattiva il logging su file", - "StubLogTooltip": "Attiva messaggi stub log", - "InfoLogTooltip": "Attiva messaggi info log", - "WarnLogTooltip": "Attiva messaggi warning log", - "ErrorLogTooltip": "Attiva messaggi error log", - "TraceLogTooltip": "Attiva messaggi trace log", - "GuestLogTooltip": "Attiva messaggi guest log", - "FileAccessLogTooltip": "Attiva messaggi file access log", - "FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3", + "AnisotropyTooltip": "Livello del filtro anisotropico. Imposta su Auto per usare il valore richiesto dal gioco.", + "AspectRatioTooltip": "Proporzioni dello schermo applicate alla finestra di renderizzazione.\n\nCambialo solo se stai usando una mod di proporzioni per il tuo gioco, altrimenti la grafica verrà allungata.\n\nLasciare il 16:9 se incerto.", + "ShaderDumpPathTooltip": "Percorso di dump degli shader", + "FileLogTooltip": "Salva il log della console in un file su disco. Non influisce sulle prestazioni.", + "StubLogTooltip": "Stampa i messaggi di log relativi alle stub nella console. Non influisce sulle prestazioni.", + "InfoLogTooltip": "Stampa i messaggi di log informativi nella console. Non influisce sulle prestazioni.", + "WarnLogTooltip": "Stampa i messaggi di log relativi agli avvisi nella console. Non influisce sulle prestazioni.", + "ErrorLogTooltip": "Stampa i messaggi di log relativi agli errori nella console. Non influisce sulle prestazioni.", + "TraceLogTooltip": "Stampa i messaggi di log relativi al trace nella console. Non influisce sulle prestazioni.", + "GuestLogTooltip": "Stampa i messaggi di log del guest nella console. Non influisce sulle prestazioni.", + "FileAccessLogTooltip": "Stampa i messaggi di log relativi all'accesso ai file nella console.", + "FSAccessLogModeTooltip": "Attiva l'output dei log di accesso FS nella console. Le modalità possibili vanno da 0 a 3", "DeveloperOptionTooltip": "Usa con attenzione", - "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", - "DebugLogTooltip": "Attiva messaggi debug log", + "OpenGlLogLevel": "Richiede che i livelli di log appropriati siano abilitati", + "DebugLogTooltip": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.", "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", - "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono salvati i log", "ExitTooltip": "Esci da Ryujinx", - "OpenSettingsTooltip": "Apri finestra delle impostazioni", + "OpenSettingsTooltip": "Apri la finestra delle impostazioni", "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", - "OpenAboutTooltip": "Apri finestra delle informazioni", + "OpenAboutTooltip": "Apri la finestra delle informazioni", "GridSize": "Dimensione griglia", - "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", - "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", + "GridSizeTooltip": "Cambia la dimensione dei riquadri della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese brasiliano", + "AboutRyujinxContributorsButtonHeader": "Mostra tutti i contributori", "SettingsTabSystemAudioVolume": "Volume: ", "AudioVolumeTooltip": "Cambia volume audio", - "SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access", - "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", - "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", - "GameListContextMenuManageCheat": "Gestisci Cheats", - "GameListContextMenuManageModToolTip": "Manage Mods", - "GameListContextMenuManageMod": "Manage Mods", + "SettingsTabSystemEnableInternetAccess": "Attiva l'accesso a Internet da parte del guest/Modalità LAN", + "EnableInternetAccessTooltip": "Consente all'applicazione emulata di connettersi a Internet.\n\nI giochi che dispongono di una modalità LAN possono connettersi tra di loro quando questa opzione è abilitata e sono connessi alla stessa rete, comprese le console reali.\n\nQuesta opzione NON consente la connessione ai server di Nintendo. Potrebbe causare arresti anomali in alcuni giochi che provano a connettersi a Internet.\n\nNel dubbio, lascia l'opzione disattivata.", + "GameListContextMenuManageCheatToolTip": "Gestisci trucchi", + "GameListContextMenuManageCheat": "Gestisci trucchi", + "GameListContextMenuManageModToolTip": "Gestisci mod", + "GameListContextMenuManageMod": "Gestisci mod", "ControllerSettingsStickRange": "Raggio:", "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", @@ -519,14 +519,14 @@ "SettingsTabNetwork": "Rete", "SettingsTabNetworkConnection": "Connessione di rete", "SettingsTabCpuCache": "Cache CPU", - "SettingsTabCpuMemory": "Memoria CPU", - "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", + "SettingsTabCpuMemory": "Modalità CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Aggiorna Ryujinx tramite FlatHub.", "UpdaterDisabledWarningTitle": "Updater disabilitato!", "ControllerSettingsRotate90": "Ruota in senso orario di 90°", - "IconSize": "Dimensioni icona", - "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", + "IconSize": "Dimensioni icone", + "IconSizeTooltip": "Cambia le dimensioni delle icone dei giochi", "MenuBarOptionsShowConsole": "Mostra console", - "ShaderCachePurgeError": "Errore nella pulizia della shader cache a {0}: {1}", + "ShaderCachePurgeError": "Errore nell'eliminazione della cache degli shader a {0}: {1}", "UserErrorNoKeys": "Chiavi non trovate", "UserErrorNoFirmware": "Firmware non trovato", "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", @@ -538,10 +538,10 @@ "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida nel percorso specificato.", "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", - "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Ciò non dovrebbe accadere, contatta uno sviluppatore!", "OpenSetupGuideMessage": "Apri la guida all'installazione", "NoUpdate": "Nessun aggiornamento", - "TitleUpdateVersionLabel": "Versione {0} - {1}", + "TitleUpdateVersionLabel": "Versione {0}", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Conferma", "FileDialogAllTypes": "Tutti i tipi", @@ -549,18 +549,18 @@ "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", "SoftwareKeyboard": "Tastiera software", - "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", + "SoftwareKeyboardModeNumeric": "Deve essere solo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", - "ControllerAppletControllers": "Supported Controllers:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletControllers": "Controller supportati:", + "ControllerAppletPlayers": "Giocatori:", + "ControllerAppletDescription": "La configurazione corrente non è valida. Aprire le impostazioni e riconfigurare gli input.", + "ControllerAppletDocked": "Modalità TV attivata. Gli input della modalità portatile dovrebbero essere disabilitati.", "UpdaterRenaming": "Rinominazione dei vecchi files...", - "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", - "UpdaterAddingFiles": "Aggiunta nuovi files...", - "UpdaterExtracting": "Estrazione aggiornamento...", - "UpdaterDownloading": "Download aggiornamento...", + "UpdaterRenameFailed": "Non è stato possibile rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta dei nuovi file...", + "UpdaterExtracting": "Estrazione dell'aggiornamento...", + "UpdaterDownloading": "Download dell'aggiornamento...", "Game": "Gioco", "Docked": "TV", "Handheld": "Portatile", @@ -568,20 +568,20 @@ "AboutPageDeveloperListMore": "{0} e altri ancora...", "ApiError": "Errore dell'API.", "LoadingHeading": "Caricamento di {0}", - "CompilingPPTC": "Compilazione PTC", - "CompilingShaders": "Compilazione Shaders", + "CompilingPPTC": "Compilazione PPTC", + "CompilingShaders": "Compilazione degli shader", "AllKeyboards": "Tutte le tastiere", "OpenFileDialogTitle": "Seleziona un file supportato da aprire", "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", "AllSupportedFormats": "Tutti i formati supportati", - "RyujinxUpdater": "Aggiornamento Ryujinx", + "RyujinxUpdater": "Aggiornamento di Ryujinx", "SettingsTabHotkeys": "Tasti di scelta rapida", "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", - "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", - "SettingsTabHotkeysScreenshotHotkey": "Cattura Schermo:", - "SettingsTabHotkeysShowUiHotkey": "Mostra UI:", + "SettingsTabHotkeysToggleVsyncHotkey": "Attiva/disattiva VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Cattura uno screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Mostra l'interfaccia:", "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", - "SettingsTabHotkeysToggleMuteHotkey": "Muta:", + "SettingsTabHotkeysToggleMuteHotkey": "Disattiva l'audio:", "ControllerMotionTitle": "Impostazioni dei sensori di movimento", "ControllerRumbleTitle": "Impostazioni di vibrazione", "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", @@ -593,63 +593,66 @@ "Writable": "Scrivibile", "SelectDlcDialogTitle": "Seleziona file dei DLC", "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", - "SelectModDialogTitle": "Select mod directory", - "UserProfileWindowTitle": "Gestisci profili degli utenti", - "CheatWindowTitle": "Gestisci cheat dei giochi", - "DlcWindowTitle": "Gestisci DLC dei giochi", - "UpdateWindowTitle": "Gestisci aggiornamenti dei giochi", - "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", + "SelectModDialogTitle": "Seleziona cartella delle mod", + "UserProfileWindowTitle": "Gestione profili utente", + "CheatWindowTitle": "Gestione trucchi", + "DlcWindowTitle": "Gestisci DLC per {0} ({1})", + "UpdateWindowTitle": "Gestione aggiornamenti", + "CheatWindowHeading": "Trucchi disponibili per {0} [{1}]", "BuildId": "ID Build", "DlcWindowHeading": "DLC disponibili per {0} [{1}]", - "ModWindowHeading": "{0} Mod(s)", + "ModWindowHeading": "{0} mod", "UserProfilesEditProfile": "Modifica selezionati", "Cancel": "Annulla", "Save": "Salva", "Discard": "Scarta", - "Paused": "Paused", + "Paused": "In pausa", "UserProfilesSetProfileImage": "Imposta immagine profilo", - "UserProfileEmptyNameError": "È richiesto un nome", + "UserProfileEmptyNameError": "Il nome è obbligatorio", "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", - "GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:", - "SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:", + "GameUpdateWindowHeading": "Gestisci aggiornamenti per {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumenta la risoluzione:", + "SettingsTabHotkeysResScaleDownHotkey": "Riduci la risoluzione:", "UserProfilesName": "Nome:", "UserProfilesUserId": "ID utente:", - "SettingsTabGraphicsBackend": "Backend grafica", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", - "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsBackend": "Backend grafico", + "SettingsTabGraphicsBackendTooltip": "Seleziona il backend grafico che verrà utilizzato nell'emulatore.\n\nVulkan è nel complesso migliore per tutte le schede grafiche moderne, a condizione che i relativi driver siano aggiornati. Vulkan dispone anche di una compilazione degli shader più veloce (con minore stuttering) su tutte le marche di GPU.\n\nOpenGL può ottenere risultati migliori su vecchie GPU Nvidia, su vecchie GPU AMD su Linux, o su GPU con poca VRAM, anche se lo stuttering dovuto alla compilazione degli shader sarà maggiore.\n\nNel dubbio, scegli Vulkan. Seleziona OpenGL se la GPU non supporta Vulkan nemmeno con i driver grafici più recenti.", + "SettingsEnableTextureRecompression": "Attiva la ricompressione delle texture", + "SettingsEnableTextureRecompressionTooltip": "Comprime le texture ASTC per ridurre l'utilizzo di VRAM.\n\nI giochi che utilizzano questo formato di texture includono Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder e The Legend of Zelda: Tears of the Kingdom.\n\nLe schede grafiche con 4GiB o meno di VRAM probabilmente si bloccheranno ad un certo punto durante l'esecuzione di questi giochi.\n\nAttiva questa opzione solo se sei a corto di VRAM nei giochi sopra menzionati. Nel dubbio, lascia l'opzione disattivata.", "SettingsTabGraphicsPreferredGpu": "GPU preferita", "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", - "SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:", - "SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:", - "SettingsEnableMacroHLE": "Abilita Macro HLE", - "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice Macro GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nLasciare ON se non sei sicuro.", - "SettingsEnableColorSpacePassthrough": "Spazio colore passante", - "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio colore. Per gli utenti con schermi ad ampia gamma, questo può risultare in colori più vivaci, al costo della correttezza del colore.", + "SettingsTabHotkeysVolumeUpHotkey": "Alza il volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Abbassa il volume:", + "SettingsEnableMacroHLE": "Attiva HLE macro", + "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice macro della GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nNel dubbio, lascia l'opzione attiva.", + "SettingsEnableColorSpacePassthrough": "Passthrough dello spazio dei colori", + "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio dei colori. Per gli utenti con schermi ad ampia gamma, ciò può rendere i colori più vivaci, sacrificando la correttezza del colore.", "VolumeShort": "Vol", "UserProfilesManageSaves": "Gestisci i salvataggi", "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", "IrreversibleActionNote": "Questa azione non è reversibile.", - "SaveManagerHeading": "Gestisci i salvataggi per {0}", - "SaveManagerTitle": "Gestione Salvataggi", + "SaveManagerHeading": "Gestisci salvataggi per {0} ({1})", + "SaveManagerTitle": "Gestione salvataggi", "Name": "Nome", "Size": "Dimensione", "Search": "Cerca", - "UserProfilesRecoverLostAccounts": "Recupera il tuo account", + "UserProfilesRecoverLostAccounts": "Recupera account persi", "Recover": "Recupera", "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco.\n\nFXAA sfocerà la maggior parte dell'immagine, mentre SMAA tenterà di trovare bordi frastagliati e lisciarli.\n\nNon si consiglia di usarlo in combinazione con il filtro di scala FSR.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nLasciare su Nessuno se incerto.", "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Filtro di scala:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "Scegli il filtro di scaling che verrà applicato quando si utilizza o scaling di risoluzione.\n\nBilineare funziona bene per i giochi 3D ed è un'opzione predefinita affidabile.\n\nNearest è consigliato per i giochi in pixel art.\n\nFSR 1.0 è solo un filtro di nitidezza, non raccomandato per l'uso con FXAA o SMAA.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nLasciare su Bilineare se incerto.", + "GraphicsScalingFilterBilinear": "Bilineare", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Livello", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "Imposta il livello di nitidezza di FSR 1.0. Valori più alti comportano una maggiore nitidezza.", "SmaaLow": "SMAA Basso", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -657,12 +660,14 @@ "UserEditorTitle": "Modificare L'Utente", "UserEditorTitleCreate": "Crea Un Utente", "SettingsTabNetworkInterface": "Interfaccia di rete:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN/LDN.\n\nIn combinazione con una VPN o XLink Kai e un gioco che supporta la modalità LAN, questa opzione può essere usata per simulare la connessione alla stessa rete attraverso Internet.\n\nNel dubbio, lascia l'opzione su Predefinito.", "NetworkInterfaceDefault": "Predefinito", - "PackagingShaders": "Comprimendo shader", + "PackagingShaders": "Salvataggio degli shader", "AboutChangelogButton": "Visualizza changelog su GitHub", "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito.", - "SettingsTabNetworkMultiplayer": "Multiplayer", - "MultiplayerMode": "Mode:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "SettingsTabNetworkMultiplayer": "Multigiocatore", + "MultiplayerMode": "Modalità:", + "MultiplayerModeTooltip": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.", + "MultiplayerModeDisabled": "Disabilitato", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index b57d15e2e..8790135e0 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -17,7 +17,7 @@ "MenuBarOptions": "オプション(_O)", "MenuBarOptionsToggleFullscreen": "全画面切り替え", "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", - "MenuBarOptionsStopEmulation": "エミュレーションを停止", + "MenuBarOptionsStopEmulation": "エミュレーションを中止", "MenuBarOptionsSettings": "設定(_S)", "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", "MenuBarActions": "アクション(_A)", @@ -72,11 +72,11 @@ "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", "GameListContextMenuCreateShortcut": "アプリケーションのショートカットを作成", "GameListContextMenuCreateShortcutToolTip": "選択したアプリケーションを起動するデスクトップショートカットを作成します", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "選択したアプリケーションを起動する ショートカットを macOS の Applications フォルダに作成します", "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用に作成された Mod データに有用です.", "StatusBarGamesLoaded": "{0}/{1} ゲーム", "StatusBarSystemVersion": "システムバージョン: {0}", "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", @@ -385,21 +385,21 @@ "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", "DialogLoadFileErrorMessage": "{0}. エラー発生ファイル: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogModAlreadyExistsMessage": "Modはすでに存在します", + "DialogModInvalidMessage": "指定したディレクトリにはmodが含まれていません!", + "DialogModDeleteNoParentMessage": "削除に失敗しました: Mod \"{0}\" の親ディレクトリが見つかりませんでした!", "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダーダンプを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダーダンプを無効にすることを推奨します. シェーダーダンプを無効にしてよろしいですか?", "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", - "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを停止またはエミュレータを閉じてください.", + "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを中止またはエミュレータを閉じてください.", "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "以下のModを削除しようとしています: {0}\n\n続行してもよろしいですか?", + "DialogModManagerDeletionAllWarningMessage": "このタイトルの Mod をすべて削除しようとしています.\n\n続行してもよろしいですか?", "SettingsTabGraphicsFeaturesOptions": "機能", "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", "CommonAuto": "自動", @@ -408,7 +408,7 @@ "InputDialogYes": "はい", "InputDialogNo": "いいえ", "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", - "MenuBarOptionsPauseEmulation": "中断", + "MenuBarOptionsPauseEmulation": "一時停止", "MenuBarOptionsResumeEmulation": "再開", "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "カスタム GUI テーマのパスです", "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "直接キーボード アクセス (HID) のサポートです. テキスト入力デバイスとしてキーボードへのゲームアクセスを提供します.\n\nSwitchハードウェアでキーボードの使用をネイティブにサポートしているゲームでのみ動作します.\n\nわからない場合はオフのままにしてください.", + "DirectMouseTooltip": "直接マウスアクセス (HID) のサポートです. ポインティングデバイスとしてマウスへのゲームアクセスを提供します.\n\nSwitchハードウェアでマウスの使用をネイティブにサポートしているゲームでのみ動作します.\n\n有効にしている場合, タッチスクリーン機能は動作しない場合があります.\n\nわからない場合はオフのままにしてください.", "RegionTooltip": "システムの地域を変更します", "LanguageTooltip": "システムの言語を変更します", "TimezoneTooltip": "システムのタイムゾーンを変更します", "TimeTooltip": "システムの時刻を変更します", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキー(デフォルトではF1)で, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "ゲームのレンダリング解像度倍率を設定します.\n\n解像度を上げてもピクセルのように見えるゲームもあります. そのようなゲームでは, アンチエイリアスを削除するか, 内部レンダリング解像度を上げる mod を見つける必要があるかもしれません. その場合, ようなゲームでは、ネイティブを選択してください.\n\nこのオプションはゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動して, ゲームが好みの表示になるよう試してみてください.\n\nどのような設定でも, \"4x\" はやり過ぎであることを覚えておいてください.", "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "異方性フィルタリングのレベルです. ゲームが要求する値を使用する場合は「自動」を設定してください.", + "AspectRatioTooltip": "レンダリングウインドウに適用するアスペクト比です.\n\nゲームにアスペクト比を変更する mod を使用している場合のみ変更してください.\n\nわからない場合は16:9のままにしておいてください.\n", "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", @@ -496,7 +496,7 @@ "ExitTooltip": "Ryujinx を終了します", "OpenSettingsTooltip": "設定ウインドウを開きます", "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", - "StopEmulationTooltip": "ゲームのエミュレーションを停止してゲーム選択画面に戻ります", + "StopEmulationTooltip": "ゲームのエミュレーションを中止してゲーム選択画面に戻ります", "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", "GridSize": "グリッドサイズ", @@ -509,11 +509,11 @@ "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", "GameListContextMenuManageCheatToolTip": "チートを管理します", "GameListContextMenuManageCheat": "チートを管理", - "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageModToolTip": "Modを管理します", "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "範囲:", - "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", - "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", + "DialogStopEmulationTitle": "Ryujinx - エミュレーションを中止", + "DialogStopEmulationMessage": "エミュレーションを中止してよろしいですか?", "SettingsTabCpu": "CPU", "SettingsTabAudio": "音声", "SettingsTabNetwork": "ネットワーク", @@ -554,8 +554,8 @@ "SoftwareKeyboardModeASCII": "ASCII文字列のみ", "ControllerAppletControllers": "サポートされているコントローラ:", "ControllerAppletPlayers": "プレイヤー:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletDescription": "現在の設定は無効です. 設定を開いて入力を再設定してください.", + "ControllerAppletDocked": "ドッキングモードが設定されています. 携帯コントロールは無効にする必要があります.", "UpdaterRenaming": "古いファイルをリネーム中...", "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", "UpdaterAddingFiles": "新規ファイルを追加中...", @@ -580,7 +580,7 @@ "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", "SettingsTabHotkeysShowUiHotkey": "UI表示:", - "SettingsTabHotkeysPauseHotkey": "中断:", + "SettingsTabHotkeysPauseHotkey": "一時停止:", "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", "ControllerMotionTitle": "モーションコントロール設定", "ControllerRumbleTitle": "振動設定", @@ -593,7 +593,7 @@ "Writable": "書き込み可能", "SelectDlcDialogTitle": "DLC ファイルを選択", "SelectUpdateDialogTitle": "アップデートファイルを選択", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "modディレクトリを選択", "UserProfileWindowTitle": "ユーザプロファイルを管理", "CheatWindowTitle": "チート管理", "DlcWindowTitle": "DLC 管理", @@ -606,7 +606,7 @@ "Cancel": "キャンセル", "Save": "セーブ", "Discard": "破棄", - "Paused": "Paused", + "Paused": "一時停止中", "UserProfilesSetProfileImage": "プロファイル画像を設定", "UserProfileEmptyNameError": "名称が必要です", "UserProfileNoImageError": "プロファイル画像が必要です", @@ -616,9 +616,9 @@ "UserProfilesName": "名称:", "UserProfilesUserId": "ユーザID:", "SettingsTabGraphicsBackend": "グラフィックスバックエンド", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "エミュレーションに使用するグラフィックスバックエンドを選択します.\n\nVulkanは, 最近のグラフィックカードでドライバが最新であれば, 全体的に優れています. すべてのGPUベンダーで, シェーダーコンパイルがより高速で, スタッタリングが少ないのが特徴です.\n\n古いNvidia GPU, Linuxでの古いAMD GPU, VRAMの少ないGPUなどでは, OpenGLの方が良い結果が得られるかもしれません. ですが, シェーダーコンパイルのスタッターは大きくなります.\n\n不明な場合はVulkanに設定してください。最新のグラフィックドライバでもVulkanをサポートしていないGPUの場合は, OpenGLに設定してください.", "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "VRAM使用量を減らすためにASTCテクスチャを圧縮します.\n\nこのテクスチャフォーマットを使用するゲームには, Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder, The Legend of Zelda: Tears of the Kingdomが含まれます.\n\nVRAMが4GB以下のグラフィックカードでは, これらのゲームを実行中にクラッシュする可能性があります.\n\n前述のゲームでVRAMが不足している場合のみ有効にしてください. 不明な場合はオフにしてください.", "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", @@ -644,12 +644,15 @@ "Recover": "復旧", "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "ゲームレンダリングにアンチエイリアスを適用します.\n\nFXAAは画像の大部分をぼかし, SMAAはギザギザのエッジを見つけて滑らかにします.\n\nFSRスケーリングフィルタとの併用は推奨しません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックして変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合は「なし」のままにしておいてください.", "GraphicsAALabel": "アンチエイリアス:", "GraphicsScalingFilterLabel": "スケーリングフィルタ:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "解像度変更時に適用されるスケーリングフィルタを選択します.\n\nBilinearは3Dゲームに適しており, 安全なデフォルトオプションです.\n\nピクセルアートゲームにはNearestを推奨します.\n\nFSR 1.0は単なるシャープニングフィルタであり, FXAAやSMAAとの併用は推奨されません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合はBilinearのままにしておいてください.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "レベル", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0のシャープ化レベルを設定します. 高い値ほどシャープになります.", "SmaaLow": "SMAA Low", "SmaaMedium": "SMAA Medium", "SmaaHigh": "SMAA High", @@ -657,12 +660,14 @@ "UserEditorTitle": "ユーザを編集", "UserEditorTitleCreate": "ユーザを作成", "SettingsTabNetworkInterface": "ネットワークインタフェース:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "LAN/LDN機能に使用されるネットワークインタフェースです.\n\nVPNやXLink Kai、LAN対応のゲームと併用することで, インターネット上の同一ネットワーク接続になりすますことができます.\n\n不明な場合はデフォルトのままにしてください.", "NetworkInterfaceDefault": "デフォルト", "PackagingShaders": "シェーダーを構築中", "AboutChangelogButton": "GitHub で更新履歴を表示", "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます.", "SettingsTabNetworkMultiplayer": "マルチプレイヤー", "MultiplayerMode": "モード:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.", + "MultiplayerModeDisabled": "無効", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 133a51b63..0cd054cab 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -1,8 +1,8 @@ { "Language": "한국어", "MenuBarFileOpenApplet": "애플릿 열기", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", - "SettingsTabInputDirectMouseAccess": "직접 마우스 접속", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드로 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "다이렉트 마우스 접근", "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드:", "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", @@ -72,11 +72,11 @@ "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", "GameListContextMenuCreateShortcut": "애플리케이션 바로 가기 만들기", "GameListContextMenuCreateShortcutToolTip": "선택한 애플리케이션을 실행하는 바탕 화면 바로 가기를 만듭니다.", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", - "GameListContextMenuOpenModsDirectory": "Open Mods Directory", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", - "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuCreateShortcutToolTipMacOS": "해당 게임을 실행할 수 있는 바로가기를 macOS의 응용 프로그램 폴더에 추가합니다.", + "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "해당 게임의 Mod가 저장된 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectoryToolTip": "해당 게임의 Mod가 포함된 대체 SD 카드 Atmosphere 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 Mod에 유용합니다.", "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", "StatusBarSystemVersion": "시스템 버전 : {0}", "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (권장하지 않음)", "SettingsTabGraphicsAspectRatio": "종횡비 :", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -297,9 +297,9 @@ "GameListContextMenuRunApplication": "응용프로그램 실행", "GameListContextMenuToggleFavorite": "즐겨찾기 전환", "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", - "SettingsTabGeneralTheme": "Theme:", - "SettingsTabGeneralThemeDark": "Dark", - "SettingsTabGeneralThemeLight": "Light", + "SettingsTabGeneralTheme": "테마:", + "SettingsTabGeneralThemeDark": "어두운 테마", + "SettingsTabGeneralThemeLight": "밝은 테마", "ControllerSettingsConfigureGeneral": "구성", "ControllerSettingsRumble": "진동", "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", @@ -384,10 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogLoadFileErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogModAlreadyExistsMessage": "Mod가 이미 존재합니다.", + "DialogModInvalidMessage": "지정된 디렉터리에 Mod가 없습니다!", + "DialogModDeleteNoParentMessage": "삭제 실패: \"{0}\" Mod의 상위 디렉터리를 찾을 수 없습니다!", "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "해당 Mod를 삭제하려고 합니다: {0}\n\n정말로 삭제하시겠습니까?", + "DialogModManagerDeletionAllWarningMessage": "해당 타이틀에 대한 모든 Mod들을 삭제하려고 합니다.\n\n정말로 삭제하시겠습니까?", "SettingsTabGraphicsFeaturesOptions": "기능", "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", "CommonAuto": "자동", @@ -434,7 +434,7 @@ "DlcManagerRemoveAllButton": "모두 제거", "DlcManagerEnableAllButton": "모두 활성화", "DlcManagerDisableAllButton": "모두 비활성화", - "ModManagerDeleteAllButton": "Delete All", + "ModManagerDeleteAllButton": "모두 삭제", "MenuBarOptionsChangeLanguage": "언어 변경", "MenuBarShowFileTypes": "파일 유형 표시", "CommonSort": "정렬", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "다이렉트 키보드 접근(HID)은 게임에서 사용자의 키보드를 텍스트 입력 장치로 사용할 수 있게끔 제공합니다.\n\n스위치 하드웨어에서 키보드 사용을 네이티브로 지원하는 게임에서만 작동합니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장합니다.", + "DirectMouseTooltip": "다이렉트 마우스 접근(HID)은 게임에서 사용자의 마우스를 포인터 장치로 사용할 수 있게끔 제공합니다.\n\n스위치 하드웨어에서 마우스 사용을 네이티브로 지원하는 극히 일부 게임에서만 작동합니다.\n\n이 옵션이 활성화된 경우, 터치 스크린 기능이 작동하지 않을 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장합니다.", "RegionTooltip": "시스템 지역 변경", "LanguageTooltip": "시스템 언어 변경", "TimezoneTooltip": "시스템 시간대 변경", "TimeTooltip": "시스템 시간 변경", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화. 기본적으로 대부분의 게임에 대한 프레임 제한 장치로, 비활성화시 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다(기본값 F1). 핫키를 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n이 옵션에 대해 잘 모른다면 켜기를 권장드립니다.", "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "게임의 렌더링 해상도를 늘립니다.\n\n일부 게임에서는 해당 기능을 지원하지 않거나 해상도가 늘어났음에도 픽셀이 자글자글해 보일 수 있습니다; 이러한 게임들의 경우 사용자가 직접 안티 앨리어싱 기능을 끄는 Mod나 내부 렌더링 해상도를 증가시키는 Mod 등을 찾아보아야 합니다. 후자의 Mod를 사용 시에는 해당 옵션을 네이티브로 두시는 것이 좋습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 해상도를 실험하여 고를 수 있습니다.\n\n4x 설정은 어떤 셋업에서도 무리인 점을 유의하세요.", "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "비등방성 필터링 레벨. 게임에서 요청한 값을 사용하려면 자동으로 설정하세요.", + "AspectRatioTooltip": "렌더러 창에 적용될 화면비.\n\n화면비를 변경하는 Mod를 사용할 때에만 이 옵션을 바꾸세요, 그렇지 않을 경우 그래픽이 늘어나 보일 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 16:9로 설정하세요.", "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", @@ -509,8 +509,8 @@ "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", "GameListContextMenuManageCheatToolTip": "치트 관리", "GameListContextMenuManageCheat": "치트 관리", - "GameListContextMenuManageModToolTip": "Manage Mods", - "GameListContextMenuManageMod": "Manage Mods", + "GameListContextMenuManageModToolTip": "Mod 관리", + "GameListContextMenuManageMod": "Mod 관리", "ControllerSettingsStickRange": "범위 :", "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", @@ -552,10 +552,10 @@ "SoftwareKeyboardModeNumeric": "'0~9' 또는 '.'만 가능", "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", - "ControllerAppletControllers": "Supported Controllers:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletControllers": "지원하는 컨트롤러:", + "ControllerAppletPlayers": "플레이어:", + "ControllerAppletDescription": "현재 설정은 유효하지 않습니다. 설정을 열어 입력 장치를 다시 설정하세요.", + "ControllerAppletDocked": "독 모드가 설정되었습니다. 핸드헬드 컨트롤은 비활성화됩니다.", "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", @@ -593,7 +593,7 @@ "Writable": "쓰기 가능", "SelectDlcDialogTitle": "DLC 파일 선택", "SelectUpdateDialogTitle": "업데이트 파일 선택", - "SelectModDialogTitle": "Select mod directory", + "SelectModDialogTitle": "Mod 디렉터리 선택", "UserProfileWindowTitle": "사용자 프로파일 관리자", "CheatWindowTitle": "치트 관리자", "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", @@ -616,9 +616,9 @@ "UserProfilesName": "이름 :", "UserProfilesUserId": "사용자 ID :", "SettingsTabGraphicsBackend": "그래픽 후단부", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "에뮬레이터에 사용될 그래픽 백엔드를 선택합니다.\n\nVulkan이 드라이버가 최신이기 때문에 모든 현대 그래픽 카드들에서 더 좋은 성능을 발휘합니다. 또한 Vulkan은 모든 벤더사의 GPU에서 더 빠른 쉐이더 컴파일을 지원하여 스터터링이 적습니다.\n\nOpenGL의 경우 오래된 Nvidia GPU나 오래된 AMD GPU(리눅스 한정), 혹은 VRAM이 적은 GPU에서 더 나은 성능을 발휘할 수는 있으나 쉐이더 컴파일로 인한 스터터링이 Vulkan보다 심할 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 Vulkan으로 설정하세요. 사용하는 GPU가 최신 그래픽 드라이버에서도 Vulkan을 지원하지 않는다면 그 땐 OpenGL로 설정하세요.", "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "ASTC 텍스처를 압축하여 VRAM 사용량을 줄입니다.\n\n애스트럴 체인, 바요네타 3, 파이어 엠블렘 인게이지, 메트로이드 프라임 리마스터, 슈퍼 마리오브라더스 원더, 젤다의 전설: 티어스 오브 더 킹덤 등이 이러한 텍스처 포맷을 사용합니다.\n\nVRAM이 4GiB 이하인 그래픽 카드로 위와 같은 게임들을 구동할시 특정 지점에서 크래시가 발생할 수 있습니다.\n\n위에 서술된 게임들에서 VRAM이 부족한 경우에만 해당 옵션을 켜고, 그 외의 경우에는 끄기를 권장드립니다.", "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", @@ -644,12 +644,15 @@ "Recover": "복구", "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "게임 렌더에 안티 앨리어싱을 적용합니다.\n\nFXAA는 대부분의 이미지를 뿌옇게 만들지만, SMAA는 들쭉날쭉한 모서리 부분들을 찾아 부드럽게 만듭니다.\n\nFSR 스케일링 필터와 같이 사용하는 것은 권장하지 않습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 옵션을 실험하여 고를 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장드립니다.", "GraphicsAALabel": "안티 앨리어싱:", "GraphicsScalingFilterLabel": "스케일링 필터:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "해상도 스케일에 사용될 스케일링 필터를 선택하세요.\n\nBilinear는 3D 게임에서 잘 작동하며 안전한 기본값입니다.\n\nNearest는 픽셀 아트 게임에 추천합니다.\n\nFSR 1.0은 그저 샤프닝 필터임으로, FXAA나 SMAA와 같이 사용하는 것은 권장하지 않습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 옵션을 실험하여 고를 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 BILINEAR로 두세요.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "수준", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0의 샤프닝 레벨을 설정하세요. 높을수록 더 또렷해집니다.", "SmaaLow": "SMAA 낮음", "SmaaMedium": "SMAA 중간", "SmaaHigh": "SMAA 높음", @@ -657,12 +660,14 @@ "UserEditorTitle": "사용자 수정", "UserEditorTitleCreate": "사용자 생성", "SettingsTabNetworkInterface": "네트워크 인터페이스:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "LAN/LDN 기능에 사용될 네트워크 인터페이스입니다.\n\nLAN 기능을 지원하는 게임에서 VPN이나 XLink Kai 등을 동시에 사용하면, 인터넷을 통해 동일 네트워크 연결인 것을 속일 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 기본값으로 설정하세요.", "NetworkInterfaceDefault": "기본", "PackagingShaders": "셰이더 패키징 중", "AboutChangelogButton": "GitHub에서 변경 로그 보기", "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다.", "SettingsTabNetworkMultiplayer": "멀티 플레이어", "MultiplayerMode": "모드 :", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 로컬 무선/로컬 플레이 기능을 수정하여 LAN 모드에 있는 것처럼 만들어 로컬이나 동일한 네트워크 상에 있는 다른 Ryujinx 인스턴스나 커펌된 닌텐도 스위치 콘솔(ldn_mitm 모듈 설치 필요)과 연결할 수 있습니다.\n\n멀티플레이어 모드는 모든 플레이어들이 동일한 게임 버전을 요구합니다. 예를 들어 슈퍼 스매시브라더스 얼티밋 v13.0.1 사용자는 v13.0.0 사용자와 연결할 수 없습니다.\n\n해당 옵션에 대해 잘 모른다면 비활성화해두세요.", + "MultiplayerModeDisabled": "비활성화됨", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 4647f4308..94096dd7b 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego firmware'u, ale Ryujinx był w stanie zainstalować firmware {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", @@ -435,7 +435,7 @@ "DlcManagerEnableAllButton": "Włącz Wszystkie", "DlcManagerDisableAllButton": "Wyłącz Wszystkie", "ModManagerDeleteAllButton": "Usuń wszystko", - "MenuBarOptionsChangeLanguage": "Zmień Język", + "MenuBarOptionsChangeLanguage": "Zmień język", "MenuBarShowFileTypes": "Pokaż typy plików", "CommonSort": "Sortuj", "CommonShowNames": "Pokaż Nazwy", @@ -455,7 +455,7 @@ "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu do klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.\n\nDziała tylko z grami, które natywnie wspierają użycie klawiatury w urządzeniu Switch hardware.\n\nPozostaw wyłączone w razie braku pewności.", "DirectMouseTooltip": "Obsługa bezpośredniego dostępu do myszy (HID). Zapewnia dostęp gier do myszy jako urządzenia wskazującego.\n\nDziała tylko z grami, które natywnie obsługują przyciski myszy w urządzeniu Switch, które są nieliczne i daleko między nimi.\n\nPo włączeniu funkcja ekranu dotykowego może nie działać.\n\nPozostaw wyłączone w razie wątpliwości.", "RegionTooltip": "Zmień Region Systemu", - "LanguageTooltip": "Zmień Język Systemu", + "LanguageTooltip": "Zmień język systemu", "TimezoneTooltip": "Zmień Strefę Czasową Systemu", "TimeTooltip": "Zmień czas systemowy", "VSyncToggleTooltip": "Synchronizacja pionowa emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ.", @@ -523,7 +523,7 @@ "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", "ControllerSettingsRotate90": "Obróć o 90° w Prawo", - "IconSize": "Rozmiar Ikon", + "IconSize": "Rozmiar ikon", "IconSizeTooltip": "Zmień rozmiar ikon gry", "MenuBarOptionsShowConsole": "Pokaż Konsolę", "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Antyaliasing:", "GraphicsScalingFilterLabel": "Filtr skalowania:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Poziom", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Niskie", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce.", "SettingsTabNetworkMultiplayer": "Gra Wieloosobowa", "MultiplayerMode": "Tryb:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index cd6a2fd1c..fb48bd593 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", "DialogThemeRestartSubMessage": "Deseja reiniciar?", "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Nenhum firmware instalado foi encontrado, mas Ryujinx conseguiu instalar o firmware {0} do jogo fornecido.\nO emulador será reiniciado.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", "DialogInstallFileTypesSuccessMessage": "Tipos de arquivo instalados com sucesso!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anti-serrilhado:", "GraphicsScalingFilterLabel": "Filtro de escala:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Nível", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Baixo", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Clique para abrir o relatório de alterações para esta versão no seu navegador padrão.", "SettingsTabNetworkMultiplayer": "Multiplayer", "MultiplayerMode": "Modo:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index e7c36f6ca..284b8e2b4 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -31,10 +31,10 @@ "MenuBarToolsInstallFileTypes": "Установить типы файлов", "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", "MenuBarHelp": "_Помощь", - "MenuBarHelpCheckForUpdates": "Проверка обновлений", + "MenuBarHelpCheckForUpdates": "Проверить наличие обновлений", "MenuBarHelpAbout": "О программе", "MenuSearch": "Поиск...", - "GameListHeaderFavorite": "Избранные", + "GameListHeaderFavorite": "Избранное", "GameListHeaderIcon": "Значок", "GameListHeaderApplication": "Название", "GameListHeaderDeveloper": "Разработчик", @@ -45,23 +45,23 @@ "GameListHeaderFileSize": "Размер файла", "GameListHeaderPath": "Путь", "GameListContextMenuOpenUserSaveDirectory": "Открыть папку с сохранениями", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку с пользовательскими сохранениями", "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT", "GameListContextMenuManageTitleUpdates": "Управление обновлениями", - "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлениями приложения", "GameListContextMenuManageDlc": "Управление DLC", "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", "GameListContextMenuCacheManagement": "Управление кэшем", - "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", + "GameListContextMenuCacheManagementPurgePptc": "Перестроить очередь PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время следующего запуска игры.", "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть папку PPTC", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает папку, содержащую PPTC кэш приложений и игр", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку кэша шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку с кэшем шейдеров", "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает папку, содержащую кэш шейдеров приложений и игр", "GameListContextMenuExtractData": "Извлечь данные", "GameListContextMenuExtractDataExeFS": "ExeFS", @@ -71,13 +71,13 @@ "GameListContextMenuExtractDataLogo": "Логотип", "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", "GameListContextMenuCreateShortcut": "Создать ярлык приложения", - "GameListContextMenuCreateShortcutToolTip": "Создать ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", - "GameListContextMenuCreateShortcutToolTipMacOS": "Создать ярлык игры или приложения в папке Программы macOS", + "GameListContextMenuCreateShortcutToolTip": "Создает ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", + "GameListContextMenuCreateShortcutToolTipMacOS": "Создает ярлык игры или приложения в папке Программы macOS", "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды для приложений и игр", "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку Atmosphere SD-карты, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", - "StatusBarGamesLoaded": "{0}/{1} Игр загружено", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает папку Atmosphere на альтернативной SD-карте, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", + "StatusBarGamesLoaded": "{0}/{1} игр загружено", "StatusBarSystemVersion": "Версия прошивки: {0}", "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", @@ -85,7 +85,7 @@ "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", - "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", + "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вам потребуется вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", "Settings": "Параметры", "SettingsTabGeneral": "Интерфейс", "SettingsTabGeneralGeneral": "Общее", @@ -124,7 +124,7 @@ "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латинская Америка)", "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", "SettingsTabSystemSystemTimeZone": "Часовой пояс прошивки:", @@ -151,7 +151,7 @@ "SettingsTabGraphicsAnisotropicFiltering8x": "8x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x", "SettingsTabGraphicsResolutionScale": "Масштабирование:", - "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", + "SettingsTabGraphicsResolutionScaleCustom": "Пользовательское (не рекомендуется)", "SettingsTabGraphicsResolutionScaleNative": "Нативное (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", @@ -207,7 +207,7 @@ "ControllerSettingsDeviceDisabled": "Отключить", "ControllerSettingsControllerType": "Тип контроллера", "ControllerSettingsControllerTypeHandheld": "Портативный", - "ControllerSettingsControllerTypeProController": "Pro Контроллер", + "ControllerSettingsControllerTypeProController": "Pro Controller", "ControllerSettingsControllerTypeJoyConPair": "JoyCon (пара)", "ControllerSettingsControllerTypeJoyConLeft": "JoyCon (левый)", "ControllerSettingsControllerTypeJoyConRight": "JoyCon (правый)", @@ -268,35 +268,35 @@ "ControllerSettingsClose": "Закрыть", "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", - "UserProfilesChangeProfileImage": "Изменить изображение профиля", + "UserProfilesChangeProfileImage": "Изменить аватар", "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", "UserProfilesAddNewProfile": "Добавить новый профиль", "UserProfilesDelete": "Удалить", "UserProfilesClose": "Закрыть", "ProfileNameSelectionWatermark": "Выберите никнейм", "ProfileImageSelectionTitle": "Выбор изображения профиля", - "ProfileImageSelectionHeader": "Выберите изображение профиля", - "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", + "ProfileImageSelectionHeader": "Выберите аватар", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение или выбрать аватар из системной прошивки.", "ProfileImageSelectionImportImage": "Импорт изображения", - "ProfileImageSelectionSelectAvatar": "Выстроенные аватары", + "ProfileImageSelectionSelectAvatar": "Встроенные аватары", "InputDialogTitle": "Диалоговое окно ввода", "InputDialogOk": "ОК", "InputDialogCancel": "Отмена", "InputDialogAddNewProfileTitle": "Выберите имя профиля", "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", - "AvatarChoose": "Выбор аватара", + "AvatarChoose": "Выбрать аватар", "AvatarSetBackgroundColor": "Установить цвет фона", "AvatarClose": "Закрыть", "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", - "ControllerSettingsAddProfileToolTip": "Добавить профил", + "ControllerSettingsAddProfileToolTip": "Добавить профиль", "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", - "MenuBarFileToolsHideUi": "Скрыть UI", + "MenuBarFileToolsHideUi": "Скрыть интерфейс", "GameListContextMenuRunApplication": "Запуск приложения", "GameListContextMenuToggleFavorite": "Добавить в избранное", - "GameListContextMenuToggleFavoriteToolTip": "Помечает игру звездочкой как избранную", + "GameListContextMenuToggleFavoriteToolTip": "Добавляет игру в избранное и помечает звездочкой", "SettingsTabGeneralTheme": "Тема:", "SettingsTabGeneralThemeDark": "Темная", "SettingsTabGeneralThemeLight": "Светлая", @@ -304,7 +304,7 @@ "ControllerSettingsRumble": "Вибрация", "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", - "DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableMessage": "Нет сохранений для {0} [{1:x16}]", "DialogMessageSaveNotAvailableCreateSaveMessage": "Создать сохранение для этой игры?", "DialogConfirmationTitle": "Ryujinx - Подтверждение", "DialogUpdaterTitle": "Ryujinx - Обновление", @@ -313,52 +313,52 @@ "DialogExitTitle": "Ryujinx - Выход", "DialogErrorMessage": "Ryujinx обнаружил ошибку", "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", - "DialogExitSubMessage": "Все несохраненные данные будут потеряны!", + "DialogExitSubMessage": "Все несохраненные данные будут потеряны", "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", "FolderDialogExtractTitle": "Выберите папку для извлечения", - "DialogNcaExtractionMessage": "Извлечение {0} раздел от {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Экстрактор разделов NCA", + "DialogNcaExtractionMessage": "Извлечение {0} раздела из {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Извлечение разделов NCA", "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Отмена обновления!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx!", + "DialogUpdaterCancelUpdateMessage": "Отмена обновления...", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx", "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", "DialogUpdaterDownloadingMessage": "Загрузка обновления...", "DialogUpdaterExtractionMessage": "Извлечение обновления...", "DialogUpdaterRenamingMessage": "Переименование обновления...", "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", - "DialogUpdaterCompleteMessage": "Обновление завершено!", + "DialogUpdaterCompleteMessage": "Обновление завершено", "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", - "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", - "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", - "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", + "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас работает подключение к интернету", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновлять Dirty Build", "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", "DialogRestartRequiredMessage": "Требуется перезагрузка", "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не была найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", - "DialogInstallFileTypesSuccessMessage": "Успешно установлены типы файлов!", + "DialogInstallFileTypesSuccessMessage": "Типы файлов успешно установлены", "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", - "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", + "DialogUninstallFileTypesSuccessMessage": "Типы файлов успешно удалены", "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", "DialogOpenSettingsWindowLabel": "Открыть окно параметров", "DialogControllerAppletTitle": "Апплет контроллера", - "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения сообщения: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", "DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", - "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", - "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна, или вам может потребоваться проверить, подключено ли ваше интернет-соединение к сети.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна или вам может потребоваться проверить ваше интернет-соединение.", "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", "DialogProfileDeleteProfileTitle": "Удаление профиля", @@ -369,8 +369,8 @@ "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", - "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного ID.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Валидная системная прошивка не найдена в {0}.", "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия прошивки {0}.", "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию прошивки {0}.", @@ -382,33 +382,33 @@ "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", "DialogUserProfileUnsavedChangesMessage": "Вы внесли изменения в этот профиль пользователя которые не были сохранены.", "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", - "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки управления обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Сохранить?", "DialogLoadFileErrorMessage": "{0}. Файл с ошибкой: {1}", "DialogModAlreadyExistsMessage": "Мод уже существует", "DialogModInvalidMessage": "Выбранная папка не содержит модов", - "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"!", - "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", + "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры", "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Хотите отключить ведение журнала отладки?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен дамп шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить дамп шейдеров. Хотите отключить дамп шейдеров?", "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", - "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновлений для выбранного приложения", "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", - "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "DialogSettingsBackendThreadingWarningMessage": "Для применения этой настройки необходимо перезапустить Ryujinx. В зависимости от используемой вами операционной системы вам может потребоваться вручную отключить многопоточность драйвера при использовании Ryujinx.", "DialogModManagerDeletionWarningMessage": "Вы сейчас удалите мод: {0}\n\nВы уверены, что хотите продолжить?", - "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные файлы для этой игры.\n\nВы уверены, что хотите продолжить?", + "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные моды для этой игры.\n\nВы уверены, что хотите продолжить?", "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", "CommonAuto": "Автоматически", - "CommonOff": "Выключен", - "CommonOn": "Включен", + "CommonOff": "Выключено", + "CommonOn": "Включено", "InputDialogYes": "Да", "InputDialogNo": "Нет", "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", - "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsPauseEmulation": "Пауза эмуляции", "MenuBarOptionsResumeEmulation": "Продолжить", "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx", "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", @@ -427,8 +427,8 @@ "AmiiboScanButtonLabel": "Сканировать", "AmiiboOptionsShowAllLabel": "Показать все Amiibo", "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", - "DlcManagerTableHeadingEnabledLabel": "Велючено", - "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", + "DlcManagerTableHeadingEnabledLabel": "Включено", + "DlcManagerTableHeadingTitleIdLabel": "ID приложения", "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", "DlcManagerTableHeadingFullPathLabel": "Полный путь", "DlcManagerRemoveAllButton": "Удалить все", @@ -439,17 +439,17 @@ "MenuBarShowFileTypes": "Показывать форматы файлов", "CommonSort": "Сортировка", "CommonShowNames": "Показывать названия", - "CommonFavorite": "Избранные", + "CommonFavorite": "Избранное", "OrderAscending": "По возрастанию", "OrderDescending": "По убыванию", "SettingsTabGraphicsFeatures": "Функции", "ErrorWindowTitle": "Окно ошибки", "ToggleDiscordTooltip": "Включает или отключает отображение в Discord статуса \"Сейчас играет\"", "AddGameDirBoxTooltip": "Введите папку игры для добавления в список", - "AddGameDirTooltip": "AДобавить папку с игрой в список", + "AddGameDirTooltip": "Добавить папку с играми в список", "RemoveGameDirTooltip": "Удалить выбранную папку игры", - "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", - "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы", + "CustomThemePathTooltip": "Путь к пользовательской теме для интерфейса", "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", "DirectKeyboardTooltip": "Поддержка прямого ввода с клавиатуры (HID). Предоставляет игре прямой доступ к клавиатуре в качестве устройства ввода текста.\nРаботает только с играми, которые изначально поддерживают использование клавиатуры с Switch.\nРекомендуется оставить выключенным.", @@ -459,24 +459,24 @@ "TimezoneTooltip": "Изменение часового пояса прошивки", "TimeTooltip": "Изменение системного времени", "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", - "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", + "PptcToggleTooltip": "Сохранение преобразованных JIT-функций для того, чтобы не преобразовывать их по новой каждый раз при запуске игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", - "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", + "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. \n\nРекомендуется использование SDL2.", "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", "UseHypervisorTooltip": "Использует Hypervisor вместо JIT. Значительно увеличивает производительность, но может работать нестабильно.", "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch в режиме разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", - "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon в новых прошивках. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", - "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", - "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для этих игр вам может потребоваться найти моды, которые убирают сглаживание или увеличивают разрешение рендеринга этих игр. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", + "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, для уменьшения статтеров при последующих запусках.\n\nРекомендуется оставить включенным.", + "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для таких игр вам может потребоваться установить моды, которые убирают сглаживание или увеличивают разрешение рендеринга. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", - "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать значение в игре по умолчанию.", - "AspectRatioTooltip": "Соотношение сторон применимое к окну рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", - "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", + "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать в игре значение по умолчанию игре.", + "AspectRatioTooltip": "Соотношение сторон окна рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", + "ShaderDumpPathTooltip": "Путь с дампами графических шейдеров", "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", "InfoLogTooltip": "Включает вывод сообщений информационного журнала в консоль. Не влияет на производительность.", @@ -484,15 +484,15 @@ "ErrorLogTooltip": "Включает вывод сообщений журнала ошибок. Не влияет на производительность.", "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", "GuestLogTooltip": "Включает вывод сообщений гостевого журнала. Не влияет на производительность.", - "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам", + "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам.", "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", "DeveloperOptionTooltip": "Используйте с осторожностью", "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", - "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", - "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются логи", + "OpenRyujinxFolderTooltip": "Открывает папку с файлами Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку в которую записываются логи", "ExitTooltip": "Выйти из Ryujinx", "OpenSettingsTooltip": "Открыть окно параметров", "OpenProfileManagerTooltip": "Открыть менеджер учетных записей", @@ -521,10 +521,10 @@ "SettingsTabCpuCache": "Кэш ЦП", "SettingsTabCpuMemory": "Память ЦП", "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", - "UpdaterDisabledWarningTitle": "Обновление выключено!", + "UpdaterDisabledWarningTitle": "Средство обновления отключено", "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", "IconSize": "Размер обложек", - "IconSizeTooltip": "Изменить размер игровых иконок", + "IconSizeTooltip": "Изменить размер обложек", "MenuBarOptionsShowConsole": "Показать консоль", "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", "UserErrorNoKeys": "Ключи не найдены", @@ -536,9 +536,9 @@ "UserErrorNoKeysDescription": "Ryujinx не удалось найти ваш 'prod.keys' файл", "UserErrorNoFirmwareDescription": "Ryujinx не удалось найти ни одной установленной прошивки", "UserErrorFirmwareParsingFailedDescription": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.", - "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти действительное приложение по указанному пути.", - "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", - "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", + "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти валидное приложение по указанному пути.", + "UserErrorUnknownDescription": "Произошла неизвестная ошибка", + "UserErrorUndefinedDescription": "Произошла неизвестная ошибка. Этого не должно происходить, пожалуйста, свяжитесь с разработчиками.", "OpenSetupGuideMessage": "Открыть руководство по установке", "NoUpdate": "Без обновлений", "TitleUpdateVersionLabel": "Version {0} - {1}", @@ -564,7 +564,7 @@ "Game": "Игра", "Docked": "Стационарный режим", "Handheld": "Портативный режим", - "ConnectionError": "Ошибка соединения!", + "ConnectionError": "Ошибка соединения", "AboutPageDeveloperListMore": "{0} и другие...", "ApiError": "Ошибка API.", "LoadingHeading": "Загрузка {0}", @@ -577,10 +577,10 @@ "RyujinxUpdater": "Ryujinx - Обновление", "SettingsTabHotkeys": "Горячие клавиши", "SettingsTabHotkeysHotkeys": "Горячие клавиши", - "SettingsTabHotkeysToggleVsyncHotkey": "Вкл./выкл. VSync:", + "SettingsTabHotkeysToggleVsyncHotkey": "Вертикальная синхронизация:", "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", - "SettingsTabHotkeysShowUiHotkey": "Показать UI:", - "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysShowUiHotkey": "Показать интерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза эмуляции:", "SettingsTabHotkeysToggleMuteHotkey": "Выключить звук:", "ControllerMotionTitle": "Настройки управления движением", "ControllerRumbleTitle": "Настройки вибрации", @@ -592,7 +592,7 @@ "Usage": "Применение", "Writable": "Доступно для записи", "SelectDlcDialogTitle": "Выберите файлы DLC", - "SelectUpdateDialogTitle": "Выберите файлы обновления", + "SelectUpdateDialogTitle": "Выберите файлы обновлений", "SelectModDialogTitle": "Выбрать папку с модами", "UserProfileWindowTitle": "Менеджер учетных записей", "CheatWindowTitle": "Менеджер читов", @@ -601,26 +601,26 @@ "CheatWindowHeading": "Доступные читы для {0} [{1}]", "BuildId": "ID версии:", "DlcWindowHeading": "{0} DLC", - "ModWindowHeading": "Мод(ы) {0} ", + "ModWindowHeading": "Моды для {0} ", "UserProfilesEditProfile": "Изменить выбранные", "Cancel": "Отмена", "Save": "Сохранить", "Discard": "Отменить", "Paused": "Приостановлено", - "UserProfilesSetProfileImage": "Установить изображение профиля", + "UserProfilesSetProfileImage": "Установить аватар профиля", "UserProfileEmptyNameError": "Имя обязательно", - "UserProfileNoImageError": "Изображение профиля должно быть установлено", - "GameUpdateWindowHeading": "Обновление доступно для {0} ({1})", + "UserProfileNoImageError": "Необходимо установить аватар", + "GameUpdateWindowHeading": "Доступные обновления для {0} ({1})", "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", "UserProfilesName": "Имя:", "UserProfilesUserId": "ID пользователя:", "SettingsTabGraphicsBackend": "Графический бэкенд", - "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будут больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", + "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будет больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", "SettingsEnableTextureRecompression": "Включить пережатие текстур", - "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур, включая Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", + "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур: Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Выберите графический процессор, которая будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на графический процессор, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберите GPU, который будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на GPU, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", @@ -633,7 +633,7 @@ "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкенд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", "VolumeShort": "Громкость", "UserProfilesManageSaves": "Управление сохранениями", - "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", + "DeleteUserSave": "Вы хотите удалить сохранения для этой игры?", "IrreversibleActionNote": "Данное действие является необратимым.", "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", "SaveManagerTitle": "Менеджер сохранений", @@ -647,7 +647,10 @@ "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", "GraphicsAALabel": "Сглаживание:", "GraphicsScalingFilterLabel": "Интерполяция:", - "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Точный\" рекомендуется для пиксельных .\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", + "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Ступенчатая\" рекомендуется для пиксельных игр.\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", + "GraphicsScalingFilterBilinear": "Билинейная", + "GraphicsScalingFilterNearest": "Ступенчатая", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Уровень", "GraphicsScalingFilterLevelTooltip": "Выбор режима работы FSR 1.0. Выше - четче.", "SmaaLow": "SMAA Низкое", @@ -656,13 +659,15 @@ "SmaaUltra": "SMAA Ультра", "UserEditorTitle": "Редактирование пользователя", "UserEditorTitleCreate": "Создание пользователя", - "SettingsTabNetworkInterface": "Сетевой Интерфейс", - "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nВ сочетании с VPN или XLink Kai и игрой с поддержкой LAN, может использоваться для создания локальной сети через интернет.\n\nРекомендуется использовать \"По умолчанию\".", + "SettingsTabNetworkInterface": "Сетевой интерфейс:", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nМожет использоваться для игры через интернет в сочетании с VPN или XLink Kai и игрой с поддержкой LAN.\n\nРекомендуется использовать \"По умолчанию\".", "NetworkInterfaceDefault": "По умолчанию", "PackagingShaders": "Упаковка шейдеров", "AboutChangelogButton": "Список изменений на GitHub", "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии", "SettingsTabNetworkMultiplayer": "Мультиплеер", "MultiplayerMode": "Режим:", - "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным." + "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным.", + "MultiplayerModeDisabled": "Отключено", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index e2132f1a4..23745ad94 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -648,6 +648,9 @@ "GraphicsAALabel": "ลดการฉีกขาดของภาพ:", "GraphicsScalingFilterLabel": "ปรับขนาดตัวกรอง:", "GraphicsScalingFilterTooltip": "เลือกตัวกรองสเกลที่จะใช้เมื่อใช้สเกลความละเอียด\n\nBilinear ทำงานได้ดีกับเกม 3D และเป็นตัวเลือกเริ่มต้นที่ปลอดภัย\n\nแนะนำให้ใช้เกมภาพพิกเซลที่ใกล้เคียงที่สุด\n\nFSR 1.0 เป็นเพียงตัวกรองความคมชัด ไม่แนะนำให้ใช้กับ FXAA หรือ SMAA\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "ระดับ", "GraphicsScalingFilterLevelTooltip": "ตั้งค่าระดับความคมชัด FSR 1.0 สูงกว่าจะคมชัดกว่า", "SmaaLow": "SMAA ต่ำ", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดบันทึกการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", "SettingsTabNetworkMultiplayer": "ผู้เล่นหลายคน", "MultiplayerMode": "โหมด:", - "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ" + "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 569ed28b1..bfb5cb53a 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan {0} firmware sürümünü yükledi.\nEmülatör şimdi başlatılacak.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", "DialogInstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla yüklendi!", @@ -648,6 +648,9 @@ "GraphicsAALabel": "Kenar Yumuşatma:", "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Seviye", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Düşük SMAA", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın", "SettingsTabNetworkMultiplayer": "Çok Oyunculu", "MultiplayerMode": "Mod:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index a77135dca..dcf85eae9 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", "MenuBarFileExit": "_Вихід", - "MenuBarOptions": "_Options", + "MenuBarOptions": "_Параметри", "MenuBarOptionsToggleFullscreen": "На весь екран", "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", "MenuBarOptionsStopEmulation": "Зупинити емуляцію", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Керувати типами файлів", "MenuBarToolsInstallFileTypes": "Установити типи файлів", "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", - "MenuBarHelp": "_Help", + "MenuBarHelp": "_Допомога", "MenuBarHelpCheckForUpdates": "Перевірити оновлення", "MenuBarHelpAbout": "Про застосунок", "MenuSearch": "Пошук...", @@ -44,7 +44,7 @@ "GameListHeaderFileExtension": "Розширення файлу", "GameListHeaderFileSize": "Розмір файлу", "GameListHeaderPath": "Шлях", - "GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача", + "GameListContextMenuOpenUserSaveDirectory": "Відкрити теку збереження користувача", "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", @@ -72,11 +72,11 @@ "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", "GameListContextMenuCreateShortcut": "Створити ярлик застосунку", "GameListContextMenuCreateShortcutToolTip": "Створити ярлик на робочому столі, який запускає вибраний застосунок", - "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "Створити ярлик у каталозі macOS програм, що запускає обраний Додаток", "GameListContextMenuOpenModsDirectory": "Відкрити теку з модами", - "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", - "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації Додатків", + "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, що містить модифікації Додатків. Корисно для модифікацій, зроблених для реального обладнання.", "StatusBarGamesLoaded": "{0}/{1} ігор завантажено", "StatusBarSystemVersion": "Версія системи: {0}", "LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті", @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Не рекомендується)", "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -384,10 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Бажаєте скасувати зміни?", "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", - "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", - "DialogModAlreadyExistsMessage": "Mod already exists", - "DialogModInvalidMessage": "The specified directory does not contain a mod!", - "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogLoadFileErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogModAlreadyExistsMessage": "Модифікація вже існує", + "DialogModInvalidMessage": "Вказаний каталог не містить модифікації!", + "DialogModDeleteNoParentMessage": "Не видалено: Не знайдено батьківський каталог для модифікації \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", - "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", - "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionWarningMessage": "Ви збираєтесь видалити модифікацію: {0}\n\nВи дійсно бажаєте продовжити?", + "DialogModManagerDeletionAllWarningMessage": "Ви збираєтесь видалити всі модифікації для цього Додатка.\n\nВи дійсно бажаєте продовжити?", "SettingsTabGraphicsFeaturesOptions": "Особливості", "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", "CommonAuto": "Авто", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "DirectKeyboardTooltip": "Підтримка прямого доступу до клавіатури (HID). Надає іграм доступ до клавіатури для вводу тексту.\n\nПрацює тільки з іграми, які підтримують клавіатуру на обладнанні Switch.\n\nЗалиште вимкненим, якщо не впевнені.", + "DirectMouseTooltip": "Підтримка прямого доступу до миші (HID). Надає іграм доступ до миші, як пристрій вказування.\n\nПрацює тільки з іграми, які підтримують мишу на обладнанні Switch, їх небагато.\n\nФункціонал сенсорного екрана може не працювати, якщо функція ввімкнена.\n\nЗалиште вимкненим, якщо не впевнені.", "RegionTooltip": "Змінити регіон системи", "LanguageTooltip": "Змінити мову системи", "TimezoneTooltip": "Змінити часовий пояс системи", "TimeTooltip": "Змінити час системи", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею (За умовчанням F1). Якщо ви плануєте вимкнути функцію, рекомендуємо зробити це через гарячу клавішу.\n\nЗалиште увімкненим, якщо не впевнені.", "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", - "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleTooltip": "Множить роздільну здатність гри.\n\nДеякі ігри можуть не працювати з цією функцією, і виглядатимуть піксельними; для цих ігор треба знайти модифікації, що зупиняють згладжування або підвищують роздільну здатність. Для останніх модифікацій, вибирайте \"Native\".\n\nЦей параметр можна міняти коли гра запущена кліком на \"Застосувати\"; ви можете перемістити вікно налаштувань і поекспериментувати з видом гри.\n\nМайте на увазі, що 4x це занадто для будь-якого комп'ютера.", "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", - "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "AnisotropyTooltip": "Рівень анізотропної фільтрації. Встановіть на «Авто», щоб використовувати значення, яке вимагає гра.", + "AspectRatioTooltip": "Співвідношення сторін застосовано до вікна рендера.\n\nМіняйте тільки, якщо використовуєте модифікацію співвідношення сторін для гри, інакше графіка буде розтягнута.\n\nЗалиште на \"16:9\", якщо не впевнені.", "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", @@ -553,9 +553,9 @@ "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи", "SoftwareKeyboardModeASCII": "Повинно бути лише ASCII текст", "ControllerAppletControllers": "Підтримувані контролери:", - "ControllerAppletPlayers": "Players:", - "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", - "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "ControllerAppletPlayers": "Гравці:", + "ControllerAppletDescription": "Поточна конфігурація невірна. Відкрийте налаштування та переналаштуйте Ваші дані.", + "ControllerAppletDocked": "Встановлений режим в док-станції. Вимкніть портативні контролери.", "UpdaterRenaming": "Перейменування старих файлів...", "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", "UpdaterAddingFiles": "Додавання нових файлів...", @@ -616,11 +616,11 @@ "UserProfilesName": "Імʼя", "UserProfilesUserId": "ID користувача:", "SettingsTabGraphicsBackend": "Графічний сервер", - "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsTabGraphicsBackendTooltip": "Виберіть backend графіки, що буде використовуватись в емуляторі.\n\n\"Vulkan\" краще для всіх сучасних відеокарт, якщо драйвери вчасно оновлюються. У Vulkan також швидше компілюються шейдери (менше \"заїкання\" зображення) на відеокартах всіх компаній.\n\n\"OpenGL\" може дати кращі результати на старих відеокартах Nvidia, старих відеокартах AMD на Linux, або на відеокартах з маленькою кількістю VRAM, але \"заїкання\" через компіляцію шейдерів будуть частіші.\n\nЯкщо не впевнені, встановіть на \"Vulkan\". Встановіть на \"OpenGL\", якщо Ваша відеокарта не підтримує Vulkan навіть на останніх драйверах.", "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", - "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "Стискає текстури ASTC, щоб зменшити використання VRAM.\n\nЦим форматом текстур користуються такі ігри, як Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder і The Legend of Zelda: Tears of the Kingdom.\n\nЦі ігри, скоріше всього крашнуться на відеокартах з розміром VRAM в 4 Гб і менше.\n\nВмикайте тільки якщо у Вас закінчується VRAM на цих іграх. Залиште на \"Вимкнути\", якщо не впевнені.", "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", + "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nВстановіть графічний процесор, позначений як «dGPU», якщо не впевнені. Якщо такого немає, не чіпайте.", "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", "SettingsGpuBackendRestartSubMessage": "Бажаєте перезапустити зараз?", @@ -644,12 +644,15 @@ "Recover": "Відновити", "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", "UserProfilesRecoverEmptyList": "Немає профілів для відновлення", - "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAATooltip": "Застосовує згладження до рендера гри.\n\nFXAA розмиє більшість зображення, а SMAA спробує знайти нерівні краї та згладити їх.\n\nНе рекомендується використовувати разом з фільтром масштабування FSR.\n\nЦю опцію можна міняти коли гра запущена кліком на \"Застосувати; ви можете відсунути вікно налаштувань і поекспериментувати з видом гри.\n\nЗалиште на \"Немає\", якщо не впевнені.", "GraphicsAALabel": "Згладжування:", "GraphicsScalingFilterLabel": "Фільтр масштабування:", - "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterTooltip": "Виберіть фільтр масштабування, що використається при збільшенні роздільної здатності.\n\n\"Білінійний\" добре виглядає в 3D іграх, і хороше налаштування за умовчуванням.\n\n\"Найближчий\" рекомендується для ігор з піксель-артом.\n\n\"FSR 1.0\" - це просто фільтр різкості, не рекомендується використовувати разом з FXAA або SMAA.\n\nЦю опцію можна міняти коли гра запущена кліком на \"Застосувати; ви можете відсунути вікно налаштувань і поекспериментувати з видом гри.\n\nЗалиште на \"Білінійний\", якщо не впевнені.", + "GraphicsScalingFilterBilinear": "Білінійний", + "GraphicsScalingFilterNearest": "Найближчий", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Рівень", - "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "GraphicsScalingFilterLevelTooltip": "Встановити рівень різкості в FSR 1.0. Чим вище - тим різкіше.", "SmaaLow": "SMAA Низький", "SmaaMedium": "SMAA Середній", "SmaaHigh": "SMAA Високий", @@ -657,12 +660,14 @@ "UserEditorTitle": "Редагувати користувача", "UserEditorTitleCreate": "Створити користувача", "SettingsTabNetworkInterface": "Мережевий інтерфейс:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceTooltip": "Мережевий інтерфейс, що використовується для LAN/LDN.\n\nРазом з VPN або XLink Kai, і грою що підтримує LAN, може імітувати з'єднання в однаковій мережі через Інтернет.", "NetworkInterfaceDefault": "Стандартний", "PackagingShaders": "Пакування шейдерів", "AboutChangelogButton": "Переглянути журнал змін на GitHub", "AboutChangelogButtonTooltipMessage": "Клацніть, щоб відкрити журнал змін для цієї версії у стандартному браузері.", "SettingsTabNetworkMultiplayer": "Мережева гра", "MultiplayerMode": "Режим:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені, ", + "MultiplayerModeDisabled": "Вимкнено", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index fcd76f50d..cc1d583e1 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -11,8 +11,8 @@ "MenuBarFile": "文件", "MenuBarFileOpenFromFile": "加载游戏文件", "MenuBarFileOpenUnpacked": "加载解包后的游戏", - "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统文件夹", - "MenuBarFileOpenLogsFolder": "打开日志文件夹", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统目录", + "MenuBarFileOpenLogsFolder": "打开日志目录", "MenuBarFileExit": "退出", "MenuBarOptions": "选项", "MenuBarOptionsToggleFullscreen": "切换全屏", @@ -24,9 +24,9 @@ "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", "MenuBarActionsScanAmiibo": "扫描 Amiibo", "MenuBarTools": "工具", - "MenuBarToolsInstallFirmware": "安装固件", - "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件", - "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件", + "MenuBarToolsInstallFirmware": "安装系统固件", + "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 文件中安装系统固件", + "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹中安装系统固件", "MenuBarToolsManageFileTypes": "管理文件扩展名", "MenuBarToolsInstallFileTypes": "关联文件扩展名", "MenuBarToolsUninstallFileTypes": "取消关联扩展名", @@ -45,24 +45,24 @@ "GameListHeaderFileSize": "大小", "GameListHeaderPath": "路径", "GameListContextMenuOpenUserSaveDirectory": "打开用户存档目录", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录", - "GameListContextMenuOpenDeviceSaveDirectory": "打开系统目录", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录", - "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏用户存档的目录", + "GameListContextMenuOpenDeviceSaveDirectory": "打开系统数据目录", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开储存游戏系统数据的目录", + "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 数据目录", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开储存游戏 BCAT 数据的目录", "GameListContextMenuManageTitleUpdates": "管理游戏更新", "GameListContextMenuManageTitleUpdatesToolTip": "打开游戏更新管理窗口", "GameListContextMenuManageDlc": "管理 DLC", "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", "GameListContextMenuCacheManagement": "缓存管理", - "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", - "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入CPU目录把 .info 文件一并删除", - "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", - "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录", + "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 缓存文件", + "GameListContextMenuCacheManagementPurgePptcToolTip": "删除游戏的 PPTC 缓存文件,下次启动游戏时重新编译生成 PPTC 缓存文件", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存文件", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存文件,下次启动游戏时重新生成着色器缓存文件", + "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 缓存目录", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开储存游戏 PPTC 缓存文件的目录", "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含游戏着色器缓存的目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开储存游戏着色器缓存文件的目录", "GameListContextMenuExtractData": "提取数据", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", @@ -72,13 +72,13 @@ "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", "GameListContextMenuCreateShortcut": "创建游戏快捷方式", "GameListContextMenuCreateShortcutToolTip": "创建一个直接启动此游戏的桌面快捷方式", - "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序文件夹中创建一个直接启动此游戏的快捷方式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序目录中创建一个直接启动此游戏的快捷方式", "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", "GameListContextMenuOpenSdModsDirectory": "打开大气层系统 MOD 目录", - "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于大气层系统的游戏 MOD 目录,对于为真实硬件打包的 MOD 非常有用", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开存放适用于大气层系统的游戏 MOD 的目录,对于为真实硬件打包的 MOD 非常有用", "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", - "StatusBarSystemVersion": "系统版本:{0}", + "StatusBarSystemVersion": "系统固件版本:{0}", "LinuxVmMaxMapCountDialogTitle": "检测到操作系统内存映射最大数量被设置的过低", "LinuxVmMaxMapCountDialogTextPrimary": "你想要将操作系统 vm.max_map_count 的值增加到 {0} 吗", "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。", @@ -276,7 +276,7 @@ "ProfileNameSelectionWatermark": "输入昵称", "ProfileImageSelectionTitle": "选择头像", "ProfileImageSelectionHeader": "选择合适的头像图片", - "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统中选择预设头像", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统固件中选择预设头像", "ProfileImageSelectionImportImage": "导入图像文件", "ProfileImageSelectionSelectAvatar": "选择预设头像", "InputDialogTitle": "输入对话框", @@ -285,7 +285,7 @@ "InputDialogAddNewProfileTitle": "选择用户名称", "InputDialogAddNewProfileHeader": "请输入账户名称", "InputDialogAddNewProfileSubtext": "(最大长度:{0})", - "AvatarChoose": "选择头像", + "AvatarChoose": "保存选定头像", "AvatarSetBackgroundColor": "设置背景色", "AvatarClose": "关闭", "ControllerSettingsLoadProfileToolTip": "加载配置文件", @@ -297,7 +297,7 @@ "GameListContextMenuRunApplication": "启动游戏", "GameListContextMenuToggleFavorite": "收藏", "GameListContextMenuToggleFavoriteToolTip": "切换游戏的收藏状态", - "SettingsTabGeneralTheme": "主题︰", + "SettingsTabGeneralTheme": "主题:", "SettingsTabGeneralThemeDark": "深色(暗黑)", "SettingsTabGeneralThemeLight": "浅色(亮色)", "ControllerSettingsConfigureGeneral": "配置", @@ -305,7 +305,7 @@ "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", - "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档?", "DialogConfirmationTitle": "Ryujinx - 确认", "DialogUpdaterTitle": "Ryujinx - 更新", "DialogErrorTitle": "Ryujinx - 错误", @@ -340,10 +340,10 @@ "DialogRestartRequiredMessage": "需要重启模拟器", "DialogThemeRestartMessage": "主题设置已保存,需要重启模拟器才能生效。", "DialogThemeRestartSubMessage": "是否要重启模拟器?", - "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的系统固件吗?(固件 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,Ryujinx 已经从当前游戏中安装了系统固件 {0} 。\n模拟器现在可以运行。", - "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", - "DialogFirmwareInstalledMessage": "已安装固件 {0}", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏文件中内嵌的系统固件吗?(固件版本 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Ryujinx 模拟器已经从当前游戏文件中安装了系统固件 {0} 。\n模拟器现在可以正常运行了。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安装系统固件", + "DialogFirmwareInstalledMessage": "已安装系统固件 {0}", "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", "DialogInstallFileTypesErrorMessage": "关联文件类型失败!", "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", @@ -354,7 +354,7 @@ "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的安装指南。", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以查看我们的安装指南。", "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", @@ -364,19 +364,19 @@ "DialogProfileDeleteProfileTitle": "删除配置文件", "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", - "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存时出错:{1}", - "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", - "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存时出错:{1}", + "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存文件\n\n确定吗?", + "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存文件时出错:{1}", + "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存文件\n\n确定吗?", + "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存文件时出错:{1}", "DialogRyujinxErrorMessage": "Ryujinx 模拟器发生错误", "DialogInvalidTitleIdErrorMessage": "用户界面错误:所选游戏没有有效的游戏 ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的系统固件。", - "DialogFirmwareInstallerFirmwareInstallTitle": "安装固件 {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的 Switch 系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安装系统固件 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统固件版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统固件版本 {0} 。", "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否继续?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装系统固件中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统固件版本 {0} 。", "DialogUserProfileDeletionWarningMessage": "删除后将没有可用的账户", "DialogUserProfileDeletionConfirmMessage": "是否删除所选账户", "DialogUserProfileUnsavedChangesTitle": "警告 - 有未保存的更改", @@ -386,7 +386,7 @@ "DialogControllerSettingsModifiedConfirmSubMessage": "是否保存?", "DialogLoadFileErrorMessage": "{0}. 错误的文件:{1}", "DialogModAlreadyExistsMessage": "MOD 已存在", - "DialogModInvalidMessage": "指定的目录找不到 MOD!", + "DialogModInvalidMessage": "指定的目录找不到 MOD 文件!", "DialogModDeleteNoParentMessage": "删除失败:找不到 MOD 的父目录“{0}”!", "DialogDlcNoDlcErrorMessage": "选择的文件不是当前游戏的 DLC!", "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,该功能仅供开发人员使用。", @@ -429,7 +429,7 @@ "AmiiboOptionsUsRandomTagLabel": "修改:使用随机生成的Amiibo ID", "DlcManagerTableHeadingEnabledLabel": "已启用", "DlcManagerTableHeadingTitleIdLabel": "游戏 ID", - "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", + "DlcManagerTableHeadingContainerPathLabel": "容器路径", "DlcManagerTableHeadingFullPathLabel": "完整路径", "DlcManagerRemoveAllButton": "全部删除", "DlcManagerEnableAllButton": "全部启用", @@ -440,8 +440,8 @@ "CommonSort": "排序", "CommonShowNames": "显示名称", "CommonFavorite": "收藏", - "OrderAscending": "从小到大", - "OrderDescending": "从大到小", + "OrderAscending": "升序", + "OrderDescending": "降序", "SettingsTabGraphicsFeatures": "功能与优化", "ErrorWindowTitle": "错误窗口", "ToggleDiscordTooltip": "选择是否在 Discord 中显示您的游玩状态", @@ -451,7 +451,7 @@ "CustomThemeCheckTooltip": "使用自定义的 Avalonia 主题作为模拟器菜单的外观", "CustomThemePathTooltip": "自定义主题的目录", "CustomThemeBrowseTooltip": "查找自定义主题", - "DockModeToggleTooltip": "启用 Switch 的主机模式,可以模拟 Switch 连接底座的情况,此时绝大多数游戏画质会提高,略微增加性能消耗。\n若禁用主机模式,则使用 Switch 的掌机模式,可以模拟手持 Switch 运行游戏的情况,游戏画质会降低,性能消耗也会降低。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", + "DockModeToggleTooltip": "启用 Switch 的主机模式(电视模式、底座模式),就是模拟 Switch 连接底座的情况;若禁用主机模式,则使用 Switch 的掌机模式,就是模拟手持 Switch 运行游戏的情况。\n对于绝大多数游戏而言,主机模式会比掌机模式,画质更高,同时性能消耗也更高。\n\n简而言之,想要更好画质则启用主机模式;电脑硬件性能不足则禁用主机模式。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", "DirectKeyboardTooltip": "直接键盘访问(HID)支持,游戏可以直接访问键盘作为文本输入设备。\n\n仅适用于在 Switch 硬件上原生支持键盘的游戏。\n\n如果不确定,请保持关闭状态。", "DirectMouseTooltip": "直接鼠标访问(HID)支持,游戏可以直接访问鼠标作为指针输入设备。\n\n只适用于在 Switch 硬件上原生支持鼠标控制的游戏,这种游戏很少。\n\n启用后,触屏功能可能无法正常工作。\n\n如果不确定,请保持关闭状态。", "RegionTooltip": "更改系统区域", @@ -459,19 +459,19 @@ "TimezoneTooltip": "更改系统时区", "TimeTooltip": "更改系统时间", "VSyncToggleTooltip": "模拟控制台的垂直同步,开启后会降低大部分游戏的帧率。关闭后,可以获得更高的帧率,但也可能导致游戏画面加载耗时更长或卡住。\n\n在游戏中可以使用热键进行切换(默认为 F1 键)。\n\n如果不确定,请保持开启状态。", - "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", + "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n可以减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", "FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。", "AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。", - "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器CPU的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", - "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最准确但是速度最慢。", + "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器 CPU 的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", + "MemoryManagerSoftwareTooltip": "使用软件内存页进行内存地址映射,最准确但是速度最慢。", "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。", "MemoryManagerUnsafeTooltip": "直接映射内存页到电脑内存,并且不检查内存溢出,使得效率更高,但牺牲了安全。\n游戏程序可以访问模拟器内存的任意地址,所以不安全。\n建议此模式下只运行您信任的游戏程序。", "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译,在可用的情况下能大幅提高性能,但目前可能还不稳定。", "DRamTooltip": "模拟 Switch 开发机的内存布局。\n\n不会提高性能,某些高清纹理包或 4k 分辨率 MOD 可能需要使用此选项。\n\n如果不确定,请保持关闭状态。", "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。", - "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", - "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", - "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,可以减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", "ResolutionScaleTooltip": "将游戏的渲染分辨率乘以一个倍数。\n\n有些游戏可能不适用这项设置,而且即使提高了分辨率仍然看起来像素化;对于这些游戏,您可能需要找到移除抗锯齿或提高内部渲染分辨率的 MOD。当使用这些 MOD 时,建议设置为“原生”。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n请记住,对于几乎所有人而言,4倍分辨率都是过度的。", "ResolutionScaleEntryTooltip": "建议设置为整数倍,带小数的分辨率缩放倍数(例如1.5),非整数倍的缩放容易导致问题或闪退。", "AnisotropyTooltip": "各向异性过滤等级,可以提高倾斜视角纹理的清晰度。\n当设置为“自动”时,使用游戏自身设定的等级。", @@ -526,22 +526,22 @@ "IconSize": "图标尺寸", "IconSizeTooltip": "更改游戏图标的显示尺寸", "MenuBarOptionsShowConsole": "显示控制台", - "ShaderCachePurgeError": "清除 {0} 的着色器缓存时出错:{1}", + "ShaderCachePurgeError": "清除 {0} 的着色器缓存文件时出错:{1}", "UserErrorNoKeys": "找不到密钥Keys", - "UserErrorNoFirmware": "找不到固件", - "UserErrorFirmwareParsingFailed": "固件解析出错", + "UserErrorNoFirmware": "未安装系统固件", + "UserErrorFirmwareParsingFailed": "固件文件解析出错", "UserErrorApplicationNotFound": "找不到游戏程序", "UserErrorUnknown": "未知错误", "UserErrorUndefined": "未定义错误", "UserErrorNoKeysDescription": "Ryujinx 模拟器找不到“prod.keys”密钥文件", - "UserErrorNoFirmwareDescription": "Ryujinx 模拟器找不到任何已安装的固件", + "UserErrorNoFirmwareDescription": "Ryujinx 模拟器未安装 Switch 系统固件", "UserErrorFirmwareParsingFailedDescription": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。", "UserErrorApplicationNotFoundDescription": "Ryujinx 模拟器在所选路径中找不到有效的游戏程序。", "UserErrorUnknownDescription": "出现未知错误!", "UserErrorUndefinedDescription": "出现未定义错误!此类错误不应出现,请联系开发者!", "OpenSetupGuideMessage": "打开安装指南", - "NoUpdate": "没有可用更新", - "TitleUpdateVersionLabel": "版本 {0}", + "NoUpdate": "无更新(或不加载游戏更新)", + "TitleUpdateVersionLabel": "游戏更新的版本 {0}", "RyujinxInfo": "Ryujinx - 信息", "RyujinxConfirm": "Ryujinx - 确认", "FileDialogAllTypes": "全部类型", @@ -555,7 +555,7 @@ "ControllerAppletControllers": "支持的手柄:", "ControllerAppletPlayers": "玩家:", "ControllerAppletDescription": "您当前的输入配置无效。打开设置并重新设置您的输入选项。", - "ControllerAppletDocked": "已经设置为主机模式,掌机手柄操控已经被禁用", + "ControllerAppletDocked": "已经设置为主机模式,应禁用掌机手柄操控。", "UpdaterRenaming": "正在重命名旧文件...", "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", "UpdaterAddingFiles": "安装更新中...", @@ -644,10 +644,13 @@ "Recover": "恢复", "UserProfilesRecoverHeading": "找到了这些用户的存档数据", "UserProfilesRecoverEmptyList": "没有可以恢复的用户数据", - "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,一种图像缩放过滤器)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", + "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,超级分辨率锐画技术)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", "GraphicsAALabel": "抗锯齿:", "GraphicsScalingFilterLabel": "缩放过滤:", - "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR 1.0 只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear”。", + "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR(超级分辨率锐画)只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear(双线性过滤)”。", + "GraphicsScalingFilterBilinear": "Bilinear(双线性过滤)", + "GraphicsScalingFilterNearest": "Nearest(最近邻过滤)", + "GraphicsScalingFilterFsr": "FSR(超级分辨率锐画技术)", "GraphicsScalingFilterLevelLabel": "等级", "GraphicsScalingFilterLevelTooltip": "设置 FSR 1.0 的锐化等级,数值越高,图像越锐利。", "SmaaLow": "SMAA 低质量", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "点击这里在浏览器中打开此版本的更新日志。", "SettingsTabNetworkMultiplayer": "多人联机游玩", "MultiplayerMode": "联机模式:", - "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nLdnMitm 将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 模块的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。" + "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。", + "MultiplayerModeDisabled": "禁用", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 4d3a78d1a..301a55856 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -144,13 +144,13 @@ "SettingsTabGraphics": "圖形", "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", - "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", "SettingsTabGraphicsAnisotropicFiltering16x": "16 倍", - "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsResolutionScale": "解析度比例:", "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", @@ -164,7 +164,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "拉伸以適應視窗", "SettingsTabGraphicsDeveloperOptions": "開發者選項", - "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", "SettingsTabLogging": "日誌", "SettingsTabLoggingLogging": "日誌", "SettingsTabLoggingEnableLoggingToFile": "啟用日誌到檔案", @@ -417,7 +417,7 @@ "AboutGithubUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 GitHub 網頁。", "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", - "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutTitle": "關於:", "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", "AboutRyujinxMaintainersTitle": "維護者:", "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", @@ -426,7 +426,7 @@ "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "掃描", "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", - "AmiiboOptionsUsRandomTagLabel": "補釘修正: 使用隨機標記的 Uuid", + "AmiiboOptionsUsRandomTagLabel": "補釘修正:使用隨機標記的 Uuid", "DlcManagerTableHeadingEnabledLabel": "已啟用", "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", "DlcManagerTableHeadingContainerPathLabel": "容器路徑", @@ -503,7 +503,7 @@ "GridSizeTooltip": "調整網格的大小", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙文", "AboutRyujinxContributorsButtonHeader": "查看所有貢獻者", - "SettingsTabSystemAudioVolume": "音量: ", + "SettingsTabSystemAudioVolume": "音量:", "AudioVolumeTooltip": "調節音量", "SettingsTabSystemEnableInternetAccess": "訪客網際網路存取/區域網路模式", "EnableInternetAccessTooltip": "允許模擬應用程式連線網際網路。\n\n當啟用此功能且系統連線到同一接入點時,具有區域網路模式的遊戲可相互連線。這也包括真正的遊戲機。\n\n不允許連接 Nintendo 伺服器。可能會導致某些嘗試連線網際網路的遊戲崩潰。\n\n如果不確定,請保持關閉狀態。", @@ -564,7 +564,7 @@ "Game": "遊戲", "Docked": "底座模式", "Handheld": "手提模式", - "ConnectionError": "連接錯誤。", + "ConnectionError": "連線錯誤。", "AboutPageDeveloperListMore": "{0} 等人...", "ApiError": "API 錯誤。", "LoadingHeading": "正在載入 {0}", @@ -619,7 +619,7 @@ "SettingsTabGraphicsBackendTooltip": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。", "SettingsEnableTextureRecompression": "開啟材質重新壓縮", "SettingsEnableTextureRecompressionTooltip": "壓縮 ASTC 紋理,以減少 VRAM 占用。\n\n使用這種紋理格式的遊戲包括 Astral Chain、Bayonetta 3、Fire Emblem Engage、Metroid Prime Remastered、Super Mario Bros. Wonder 和 The Legend of Zelda: Tears of the Kingdom。\n\n使用 4GB 或更低 VRAM 的顯示卡在執行這些遊戲時可能會崩潰。\n\n只有在上述遊戲的 VRAM 即將耗盡時才啟用。如果不確定,請保持關閉狀態。", - "SettingsTabGraphicsPreferredGpu": "首選 GPU", + "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", "SettingsTabGraphicsPreferredGpuTooltip": "選擇將與 Vulkan 圖形後端一起使用的顯示卡。\n\n不會影響 OpenGL 將使用的 GPU。\n\n如果不確定,請設定為標記為「dGPU」的 GPU。如果沒有,則保持原狀。", "SettingsAppRequiredRestartMessage": "需要重新啟動 Ryujinx", "SettingsGpuBackendRestartMessage": "圖形後端或 GPU 設定已修改。這需要重新啟動才能套用。", @@ -648,6 +648,9 @@ "GraphicsAALabel": "反鋸齒:", "GraphicsScalingFilterLabel": "縮放過濾器:", "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\nBilinear (雙線性) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用 Nearest (最近性) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持 Bilinear (雙線) 狀態。", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "日誌等級", "GraphicsScalingFilterLevelTooltip": "設定 FSR 1.0 銳化等級。越高越清晰。", "SmaaLow": "低階 SMAA", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "在預設瀏覽器中開啟此版本的更新日誌。", "SettingsTabNetworkMultiplayer": "多人遊戲", "MultiplayerMode": "模式:", - "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。" + "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index aecc585fc..89e895e81 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Ava public static string Version { get; private set; } public static string ConfigurationPath { get; private set; } public static bool PreviewerDetached { get; private set; } + public static bool UseHardwareAcceleration { get; private set; } [LibraryImport("user32.dll", SetLastError = true)] public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); @@ -60,12 +61,16 @@ namespace Ryujinx.Ava EnableMultiTouch = true, EnableIme = true, EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", - RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, + RenderingMode = UseHardwareAcceleration ? + new[] { X11RenderingMode.Glx, X11RenderingMode.Software } : + new[] { X11RenderingMode.Software }, }) .With(new Win32PlatformOptions { WinUICompositionBackdropCornerRadius = 8.0f, - RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software }, + RenderingMode = UseHardwareAcceleration ? + new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } : + new[] { Win32RenderingMode.Software }, }) .UseSkia(); } @@ -161,6 +166,8 @@ namespace Ryujinx.Ava } } + UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value; + // Check if graphics backend was overridden if (CommandLineState.OverrideGraphicsBackend != null) { @@ -191,6 +198,12 @@ namespace Ryujinx.Ava _ => ConfigurationState.Instance.HideCursor.Value, }; } + + // Check if hardware-acceleration was overridden. + if (CommandLineState.OverrideHardwareAcceleration != null) + { + UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value; + } } private static void PrintSystemInfo() diff --git a/src/Ryujinx/UI/Models/StatusInitEventArgs.cs b/src/Ryujinx/UI/Models/StatusInitEventArgs.cs new file mode 100644 index 000000000..4b08737e9 --- /dev/null +++ b/src/Ryujinx/UI/Models/StatusInitEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Ava.UI.Models +{ + internal class StatusInitEventArgs : EventArgs + { + public string GpuBackend { get; } + public string GpuName { get; } + + public StatusInitEventArgs(string gpuBackend, string gpuName) + { + GpuBackend = gpuBackend; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs index 7f04c0eed..ee5648faf 100644 --- a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs +++ b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs @@ -6,23 +6,19 @@ namespace Ryujinx.Ava.UI.Models { public bool VSyncEnabled { get; } public string VolumeStatus { get; } - public string GpuBackend { get; } public string AspectRatio { get; } public string DockedMode { get; } public string FifoStatus { get; } public string GameStatus { get; } - public string GpuName { get; } - public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus) { VSyncEnabled = vSyncEnabled; VolumeStatus = volumeStatus; - GpuBackend = gpuBackend; DockedMode = dockedMode; AspectRatio = aspectRatio; GameStatus = gameStatus; FifoStatus = fifoStatus; - GpuName = gpuName; } } } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 17bd69b14..130e708c7 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -36,6 +36,7 @@ using SixLabors.ImageSharp.PixelFormats; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -980,7 +981,14 @@ namespace Ryujinx.Ava.UI.ViewModels { if (arg is ApplicationData app) { - return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower()); + if (string.IsNullOrWhiteSpace(_searchText)) + { + return true; + } + + CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo; + + return compareInfo.IndexOf(app.TitleName, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; } return false; @@ -1164,6 +1172,7 @@ namespace Ryujinx.Ava.UI.ViewModels { RendererHostControl.WindowCreated += RendererHost_Created; + AppHost.StatusInitEvent += Init_StatusBar; AppHost.StatusUpdatedEvent += Update_StatusBar; AppHost.AppExit += AppHost_AppExit; @@ -1190,6 +1199,18 @@ namespace Ryujinx.Ava.UI.ViewModels } } + private void Init_StatusBar(object sender, StatusInitEventArgs args) + { + if (ShowMenuAndStatusBar && !ShowLoadProgress) + { + Dispatcher.UIThread.InvokeAsync(() => + { + GpuNameText = args.GpuName; + BackendText = args.GpuBackend; + }); + } + } + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) { if (ShowMenuAndStatusBar && !ShowLoadProgress) @@ -1212,8 +1233,6 @@ namespace Ryujinx.Ava.UI.ViewModels GameStatusText = args.GameStatus; VolumeStatusText = args.VolumeStatus; FifoStatusText = args.FifoStatus; - GpuNameText = args.GpuName; - BackendText = args.GpuBackend; ShowStatusSeparator = true; }); diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 233de61f0..0750221c1 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -285,7 +285,7 @@ namespace Ryujinx.Ava.UI.Windows { _deferLoad = false; - ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait(); + await ViewModel.LoadApplication(_launchPath, _startFullscreen); } } else diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs index d9ae0d4f3..98f694db8 100644 --- a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs @@ -38,7 +38,7 @@ namespace Ryujinx.Ava.UI.Windows SecondaryButtonText = "", CloseButtonText = "", Content = new ModManagerWindow(titleId), - Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], titleName, titleId.ToString("X16")), + Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowTitle], titleName, titleId.ToString("X16")), }; Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType());