Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3d1a0bf374 | ||
|
c20f3fbebd | ||
|
0d3b82477e | ||
|
470be03c2f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -125,6 +125,9 @@ ClientBin/
|
||||
packages/*
|
||||
*.config
|
||||
|
||||
# Include nuget.config
|
||||
!nuget.config
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
|
@@ -80,10 +80,7 @@ namespace ARMeilleure.Common
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
lock (_pages)
|
||||
{
|
||||
@@ -100,10 +97,7 @@ namespace ARMeilleure.Common
|
||||
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
|
||||
public AddressTable(Level[] levels)
|
||||
{
|
||||
if (levels == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(levels));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(levels);
|
||||
|
||||
if (levels.Length < 2)
|
||||
{
|
||||
@@ -141,10 +135,7 @@ namespace ARMeilleure.Common
|
||||
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
|
||||
public ref TEntry GetValue(ulong address)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (!IsValid(address))
|
||||
{
|
||||
|
@@ -49,10 +49,7 @@ namespace ARMeilleure.Common
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return ref _countTable.GetValue(_index);
|
||||
}
|
||||
|
@@ -53,10 +53,7 @@ namespace ARMeilleure.Common
|
||||
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
||||
public int Allocate()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
lock (_allocated)
|
||||
{
|
||||
@@ -83,10 +80,7 @@ namespace ARMeilleure.Common
|
||||
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
||||
public void Free(int index)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
lock (_allocated)
|
||||
{
|
||||
@@ -108,10 +102,7 @@ namespace ARMeilleure.Common
|
||||
/// <exception cref="ArgumentException">Entry at <paramref name="index"/> is not allocated</exception>
|
||||
public ref TEntry GetValue(int index)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
lock (_allocated)
|
||||
{
|
||||
|
@@ -48,10 +48,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
|
||||
public void AddSuccessor(BasicBlock block)
|
||||
{
|
||||
if (block == null)
|
||||
{
|
||||
ThrowNull(nameof(block));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(block);
|
||||
|
||||
if ((uint)_succCount + 1 > MaxSuccessors)
|
||||
{
|
||||
@@ -100,10 +97,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
|
||||
public void SetSuccessor(int index, BasicBlock block)
|
||||
{
|
||||
if (block == null)
|
||||
{
|
||||
ThrowNull(nameof(block));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(block);
|
||||
|
||||
if ((uint)index >= (uint)_succCount)
|
||||
{
|
||||
@@ -144,7 +138,6 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowNull(string name) => throw new ArgumentNullException(name);
|
||||
private static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name);
|
||||
private static void ThrowSuccessorOverflow() => throw new OverflowException($"BasicBlock can only have {MaxSuccessors} successors.");
|
||||
|
||||
|
@@ -25,10 +25,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
public static Delegate GetDelegate(MethodInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
Type[] parameters = info.GetParameters().Select(pI => pI.ParameterType).ToArray();
|
||||
Type returnType = info.ReturnType;
|
||||
|
@@ -35,10 +35,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
public static IntPtr GetDelegateFuncPtr(MethodInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
string key = GetKey(info);
|
||||
|
||||
@@ -52,10 +49,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
public static int GetDelegateIndex(MethodInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
string key = GetKey(info);
|
||||
|
||||
|
@@ -67,10 +67,7 @@ namespace ARMeilleure.Translation
|
||||
/// <returns>True if the value was added, false if the start key was already in the dictionary</returns>
|
||||
public bool AddOrUpdate(K start, K end, V value, Func<K, V, V> updateFactoryCallback)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
return BSTInsert(start, end, value, updateFactoryCallback, out IntervalTreeNode<K, V> node);
|
||||
}
|
||||
@@ -85,10 +82,7 @@ namespace ARMeilleure.Translation
|
||||
/// <returns><paramref name="value"/> if <paramref name="start"/> is not yet on the tree, or the existing value otherwise</returns>
|
||||
public V GetOrAdd(K start, K end, V value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
BSTInsert(start, end, value, null, out IntervalTreeNode<K, V> node);
|
||||
return node.Value;
|
||||
@@ -152,10 +146,7 @@ namespace ARMeilleure.Translation
|
||||
/// <returns>Node reference in the tree</returns>
|
||||
private IntervalTreeNode<K, V> GetNode(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
IntervalTreeNode<K, V> node = _root;
|
||||
while (node != null)
|
||||
|
@@ -30,10 +30,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _dispatchStub.Value;
|
||||
}
|
||||
@@ -47,10 +44,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _slowDispatchStub.Value;
|
||||
}
|
||||
@@ -64,10 +58,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _dispatchLoop.Value;
|
||||
}
|
||||
@@ -81,7 +72,9 @@ namespace ARMeilleure.Translation
|
||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||
public TranslatorStubs(Translator translator)
|
||||
{
|
||||
_translator = translator ?? throw new ArgumentNullException(nameof(translator));
|
||||
ArgumentNullException.ThrowIfNull(translator);
|
||||
|
||||
_translator = translator;
|
||||
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
||||
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
||||
}
|
||||
|
@@ -51,6 +51,40 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
};
|
||||
}
|
||||
|
||||
private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat)
|
||||
{
|
||||
if (_realDriver.SupportsSampleFormat(targetSampleFormat))
|
||||
{
|
||||
return targetSampleFormat;
|
||||
}
|
||||
|
||||
// Attempt conversion from PCM16.
|
||||
if (targetSampleFormat == SampleFormat.PcmInt16)
|
||||
{
|
||||
// Prefer PCM32 if we need to convert.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt32))
|
||||
{
|
||||
return SampleFormat.PcmInt32;
|
||||
}
|
||||
|
||||
// If not supported, PCM float provides the best quality with a cost lower than PCM24.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmFloat))
|
||||
{
|
||||
return SampleFormat.PcmFloat;
|
||||
}
|
||||
|
||||
// TODO: Implement PCM24 conversion.
|
||||
|
||||
// If nothing is truly supported, attempt PCM8 at the cost of loosing quality.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
|
||||
{
|
||||
return SampleFormat.PcmInt8;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("No valid sample format configuration found!");
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
@@ -77,15 +111,26 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
||||
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
||||
|
||||
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount, volume);
|
||||
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
|
||||
|
||||
if (hardwareChannelCount == channelCount)
|
||||
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
|
||||
{
|
||||
return realSession;
|
||||
}
|
||||
|
||||
if (hardwareSampleFormat != sampleFormat)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, $"{sampleFormat} isn't supported by the audio device, conversion to {hardwareSampleFormat} will happen.");
|
||||
|
||||
if (hardwareSampleFormat < sampleFormat)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, $"{hardwareSampleFormat} has lower quality than {sampleFormat}, expect some loss in audio fidelity.");
|
||||
}
|
||||
}
|
||||
|
||||
if (direction == Direction.Input)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, $"The selected audio backend doesn't support the requested audio input configuration, fallback to dummy...");
|
||||
@@ -103,7 +148,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
}
|
||||
|
||||
// If we need to do post processing before sending to the hardware device, wrap around it.
|
||||
return new CompatLayerHardwareDeviceSession(realSessionOutputBase, channelCount);
|
||||
return new CompatLayerHardwareDeviceSession(realSessionOutputBase, sampleFormat, channelCount);
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -8,11 +9,13 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
class CompatLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private HardwareDeviceSessionOutputBase _realSession;
|
||||
private SampleFormat _userSampleFormat;
|
||||
private uint _userChannelCount;
|
||||
|
||||
public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount)
|
||||
public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, SampleFormat userSampleFormat, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount)
|
||||
{
|
||||
_realSession = realSession;
|
||||
_userSampleFormat = userSampleFormat;
|
||||
_userChannelCount = userChannelCount;
|
||||
}
|
||||
|
||||
@@ -38,53 +41,86 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
|
||||
public override void QueueBuffer(AudioBuffer buffer)
|
||||
{
|
||||
SampleFormat realSampleFormat = _realSession.RequestedSampleFormat;
|
||||
|
||||
if (_userSampleFormat != realSampleFormat)
|
||||
{
|
||||
if (_userSampleFormat != SampleFormat.PcmInt16)
|
||||
{
|
||||
throw new NotImplementedException("Converting formats other than PCM16 is not supported.");
|
||||
}
|
||||
|
||||
int userSampleCount = buffer.Data.Length / BackendHelper.GetSampleSize(_userSampleFormat);
|
||||
|
||||
ReadOnlySpan<short> samples = MemoryMarshal.Cast<byte, short>(buffer.Data);
|
||||
byte[] convertedSamples = new byte[BackendHelper.GetSampleSize(realSampleFormat) * userSampleCount];
|
||||
|
||||
switch (realSampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt8:
|
||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
|
||||
break;
|
||||
case SampleFormat.PcmInt32:
|
||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Sample format conversion from {_userSampleFormat} to {realSampleFormat} not implemented.");
|
||||
}
|
||||
|
||||
buffer.Data = convertedSamples;
|
||||
}
|
||||
|
||||
_realSession.QueueBuffer(buffer);
|
||||
}
|
||||
|
||||
public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples)
|
||||
{
|
||||
if (RequestedSampleFormat != SampleFormat.PcmInt16)
|
||||
{
|
||||
throw new NotImplementedException("Downmixing formats other than PCM16 is not supported.");
|
||||
}
|
||||
|
||||
if (samples == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
short[] downmixedBufferPCM16;
|
||||
if (_userChannelCount != _realSession.RequestedChannelCount)
|
||||
{
|
||||
if (_userSampleFormat != SampleFormat.PcmInt16)
|
||||
{
|
||||
throw new NotImplementedException("Downmixing formats other than PCM16 is not supported.");
|
||||
}
|
||||
|
||||
ReadOnlySpan<short> samplesPCM16 = MemoryMarshal.Cast<byte, short>(samples);
|
||||
|
||||
if (_userChannelCount == 6)
|
||||
{
|
||||
downmixedBufferPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16);
|
||||
samplesPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16);
|
||||
|
||||
if (_realSession.RequestedChannelCount == 1)
|
||||
{
|
||||
downmixedBufferPCM16 = Downmixing.DownMixStereoToMono(downmixedBufferPCM16);
|
||||
samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
|
||||
}
|
||||
}
|
||||
else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1)
|
||||
{
|
||||
downmixedBufferPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
|
||||
samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented.");
|
||||
}
|
||||
|
||||
byte[] downmixedBuffer = MemoryMarshal.Cast<short, byte>(downmixedBufferPCM16).ToArray();
|
||||
samples = MemoryMarshal.Cast<short, byte>(samplesPCM16).ToArray();
|
||||
}
|
||||
|
||||
AudioBuffer fakeBuffer = new AudioBuffer
|
||||
{
|
||||
BufferTag = buffer.BufferTag,
|
||||
DataPointer = buffer.DataPointer,
|
||||
DataSize = (ulong)downmixedBuffer.Length
|
||||
DataSize = (ulong)samples.Length
|
||||
};
|
||||
|
||||
bool result = _realSession.RegisterBuffer(fakeBuffer, downmixedBuffer);
|
||||
bool result = _realSession.RegisterBuffer(fakeBuffer, samples);
|
||||
|
||||
if (result)
|
||||
{
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
@@ -23,6 +24,44 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ConvertSampleToPcmFloat(short sample)
|
||||
{
|
||||
return (float)sample / short.MaxValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static short ConvertSampleToPcmInt16(float sample)
|
||||
{
|
||||
return Saturate(sample * short.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TOutput ConvertSample<TInput, TOutput>(TInput value) where TInput: INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
||||
{
|
||||
TInput conversionRate = TInput.CreateSaturating(TOutput.MaxValue / TOutput.CreateSaturating(TInput.MaxValue));
|
||||
|
||||
return TOutput.CreateSaturating(value * conversionRate);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Convert<TInput, TOutput>(Span<TOutput> output, ReadOnlySpan<TInput> input) where TInput : INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i] = ConvertSample<TInput, TOutput>(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ConvertSampleToPcmFloat(Span<float> output, ReadOnlySpan<short> input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i] = ConvertSampleToPcmFloat(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<short> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
|
||||
{
|
||||
@@ -53,7 +92,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
|
||||
for (int i = 0; i < decodedCount; i++)
|
||||
{
|
||||
output[i] = (short)(input[i * channelCount + channelIndex] * short.MaxValue);
|
||||
output[i] = ConvertSampleToPcmInt16(input[i * channelCount + channelIndex]);
|
||||
}
|
||||
|
||||
return decodedCount;
|
||||
|
@@ -24,10 +24,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public int Get(K key, ref V[] overlaps)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
IntervalTreeNode<K, V> node = GetNode(key);
|
||||
|
||||
@@ -61,15 +58,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="start"/> or <paramref name="end"/> is null</exception>
|
||||
public int Get(K start, K end, ref V[] overlaps, int overlapCount = 0)
|
||||
{
|
||||
if (start == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(start));
|
||||
}
|
||||
|
||||
if (end == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(end));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(start);
|
||||
ArgumentNullException.ThrowIfNull(end);
|
||||
|
||||
GetValues(Root, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
@@ -85,20 +75,9 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="start"/>, <paramref name="end"/> or <paramref name="value"/> are null</exception>
|
||||
public void Add(K start, K end, V value)
|
||||
{
|
||||
if (start == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(start));
|
||||
}
|
||||
|
||||
if (end == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(end));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(start);
|
||||
ArgumentNullException.ThrowIfNull(end);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
Insert(start, end, value);
|
||||
}
|
||||
@@ -112,10 +91,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <returns>Number of deleted values</returns>
|
||||
public int Remove(K key, V value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
int removed = Delete(key, value);
|
||||
|
||||
@@ -168,10 +144,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private IntervalTreeNode<K, V> GetNode(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
IntervalTreeNode<K, V> node = Root;
|
||||
while (node != null)
|
||||
@@ -462,10 +435,8 @@ namespace Ryujinx.Common.Collections
|
||||
|
||||
public bool ContainsKey(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return GetNode(key) != null;
|
||||
}
|
||||
}
|
||||
|
@@ -17,10 +17,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
public void Add(T node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
Insert(node);
|
||||
}
|
||||
@@ -32,10 +29,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
public void Remove(T node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
if (Delete(node) != null)
|
||||
{
|
||||
Count--;
|
||||
@@ -50,10 +45,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="searchNode"/> is null</exception>
|
||||
public T GetNode(T searchNode)
|
||||
{
|
||||
if (searchNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(searchNode));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(searchNode);
|
||||
|
||||
T node = Root;
|
||||
while (node != null)
|
||||
|
@@ -92,10 +92,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
protected static T Minimum(T node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
T tmp = node;
|
||||
while (tmp.Left != null)
|
||||
{
|
||||
|
@@ -22,10 +22,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public V Get(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> node = GetNode(key);
|
||||
|
||||
@@ -47,14 +44,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> are null</exception>
|
||||
public void Add(K key, V value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
Insert(key, value);
|
||||
}
|
||||
@@ -66,10 +57,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public void Remove(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
if (Delete(key) != null)
|
||||
{
|
||||
Count--;
|
||||
@@ -217,10 +206,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private Node<K, V> GetNode(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> node = Root;
|
||||
while (node != null)
|
||||
@@ -370,10 +356,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private Node<K, V> FloorNode(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> tmp = Root;
|
||||
|
||||
while (tmp != null)
|
||||
@@ -424,10 +408,8 @@ namespace Ryujinx.Common.Collections
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private Node<K, V> CeilingNode(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> tmp = Root;
|
||||
|
||||
while (tmp != null)
|
||||
@@ -477,10 +459,8 @@ namespace Ryujinx.Common.Collections
|
||||
// Method descriptions are not provided as they are already included as part of the interface.
|
||||
public bool ContainsKey(K key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return GetNode(key) != null;
|
||||
}
|
||||
|
||||
@@ -493,10 +473,8 @@ namespace Ryujinx.Common.Collections
|
||||
|
||||
public bool TryGetValue(K key, [MaybeNullWhen(false)] out V value)
|
||||
{
|
||||
if (null == key)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> node = GetNode(key);
|
||||
value = node != null ? node.Value : default;
|
||||
return node != null;
|
||||
@@ -504,10 +482,7 @@ namespace Ryujinx.Common.Collections
|
||||
|
||||
public void Add(KeyValuePair<K, V> item)
|
||||
{
|
||||
if (item.Key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item.Key));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(item.Key);
|
||||
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
@@ -18,7 +18,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool Supports3DTextureCompression;
|
||||
public readonly bool SupportsBgraFormat;
|
||||
public readonly bool SupportsR4G4Format;
|
||||
public readonly bool SupportsR4G4B4A4Format;
|
||||
public readonly bool SupportsSnormBufferTextureFormat;
|
||||
public readonly bool Supports5BitComponentFormat;
|
||||
public readonly bool SupportsFragmentShaderInterlock;
|
||||
public readonly bool SupportsFragmentShaderOrderingIntel;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
@@ -55,7 +57,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supports3DTextureCompression,
|
||||
bool supportsBgraFormat,
|
||||
bool supportsR4G4Format,
|
||||
bool supportsR4G4B4A4Format,
|
||||
bool supportsSnormBufferTextureFormat,
|
||||
bool supports5BitComponentFormat,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsFragmentShaderOrderingIntel,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
@@ -89,7 +93,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
Supports3DTextureCompression = supports3DTextureCompression;
|
||||
SupportsBgraFormat = supportsBgraFormat;
|
||||
SupportsR4G4Format = supportsR4G4Format;
|
||||
SupportsR4G4B4A4Format = supportsR4G4B4A4Format;
|
||||
SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat;
|
||||
Supports5BitComponentFormat = supports5BitComponentFormat;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
|
@@ -448,6 +448,27 @@ namespace Ryujinx.Graphics.GAL
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is 16 bit packed.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the texture format is 16 bit packed, false otherwise</returns>
|
||||
public static bool Is16BitPacked(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.B5G6R5Unorm:
|
||||
case Format.B5G5R5A1Unorm:
|
||||
case Format.R5G5B5X1Unorm:
|
||||
case Format.R5G5B5A1Unorm:
|
||||
case Format.R5G6B5Unorm:
|
||||
case Format.R4G4B4A4Unorm:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is an ASTC format.
|
||||
/// </summary>
|
||||
|
@@ -911,7 +911,40 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
||||
{
|
||||
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result);
|
||||
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width);
|
||||
|
||||
if (!_context.Capabilities.SupportsR4G4B4A4Format)
|
||||
{
|
||||
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
|
||||
}
|
||||
}
|
||||
else if (Format == Format.R4G4B4A4Unorm)
|
||||
{
|
||||
if (!_context.Capabilities.SupportsR4G4B4A4Format)
|
||||
{
|
||||
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
|
||||
}
|
||||
}
|
||||
else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked())
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case Format.B5G6R5Unorm:
|
||||
case Format.R5G6B5Unorm:
|
||||
result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width);
|
||||
break;
|
||||
case Format.B5G5R5A1Unorm:
|
||||
case Format.R5G5B5X1Unorm:
|
||||
case Format.R5G5B5A1Unorm:
|
||||
result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm);
|
||||
break;
|
||||
case Format.A1B5G5R5Unorm:
|
||||
result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width);
|
||||
break;
|
||||
case Format.R4G4B4A4Unorm:
|
||||
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@@ -131,9 +131,28 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm)
|
||||
{
|
||||
if (caps.SupportsR4G4B4A4Format)
|
||||
{
|
||||
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (info.FormatInfo.Format == Format.R4G4B4A4Unorm)
|
||||
{
|
||||
if (!caps.SupportsR4G4B4A4Format)
|
||||
{
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked())
|
||||
{
|
||||
return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
|
||||
return info.FormatInfo;
|
||||
}
|
||||
|
@@ -114,7 +114,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supports3DTextureCompression: false,
|
||||
supportsBgraFormat: false,
|
||||
supportsR4G4Format: false,
|
||||
supportsR4G4B4A4Format: true,
|
||||
supportsSnormBufferTextureFormat: false,
|
||||
supports5BitComponentFormat: true,
|
||||
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
|
||||
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
|
||||
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||
|
@@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
{
|
||||
public static class LayoutConverter
|
||||
{
|
||||
private const int HostStrideAlignment = 4;
|
||||
public const int HostStrideAlignment = 4;
|
||||
|
||||
public static void ConvertBlockLinearToLinear(
|
||||
Span<byte> dst,
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
@@ -7,9 +8,27 @@ namespace Ryujinx.Graphics.Texture
|
||||
{
|
||||
public static class PixelConverter
|
||||
{
|
||||
public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data)
|
||||
private static (int remainder, int outRemainder, int height) GetLineRemainders(int length, int width, int bpp, int outBpp)
|
||||
{
|
||||
int stride = BitUtils.AlignUp(width * bpp, LayoutConverter.HostStrideAlignment);
|
||||
int remainder = stride / bpp - width;
|
||||
|
||||
int outStride = BitUtils.AlignUp(width * outBpp, LayoutConverter.HostStrideAlignment);
|
||||
int outRemainder = outStride / outBpp - width;
|
||||
|
||||
return (remainder, outRemainder, length / stride);
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
byte[] output = new byte[data.Length * 2];
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2);
|
||||
|
||||
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output);
|
||||
|
||||
if (remainder == 0)
|
||||
{
|
||||
int start = 0;
|
||||
|
||||
if (Sse41.IsSupported)
|
||||
@@ -26,12 +45,170 @@ namespace Ryujinx.Graphics.Texture
|
||||
}
|
||||
}
|
||||
|
||||
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output);
|
||||
|
||||
for (int i = start; i < data.Length; i++)
|
||||
{
|
||||
outputSpan[i] = (ushort)data[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
outputSpan[outOffset++] = data[offset++];
|
||||
}
|
||||
|
||||
offset += remainder;
|
||||
outOffset += outRemainder;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
byte[] output = new byte[data.Length * 2];
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
uint packed = inputSpan[offset++];
|
||||
|
||||
uint outputPacked = 0xff000000;
|
||||
outputPacked |= (packed << 3) & 0x000000f8;
|
||||
outputPacked |= (packed << 8) & 0x00f80000;
|
||||
|
||||
// Replicate 5 bit components.
|
||||
outputPacked |= (outputPacked >> 5) & 0x00070007;
|
||||
|
||||
// Include and replicate 6 bit component.
|
||||
outputPacked |= ((packed << 5) & 0x0000fc00) | ((packed >> 1) & 0x00000300);
|
||||
|
||||
outputSpan[outOffset++] = outputPacked;
|
||||
}
|
||||
|
||||
offset += remainder;
|
||||
outOffset += outRemainder;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
|
||||
{
|
||||
byte[] output = new byte[data.Length * 2];
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
uint packed = inputSpan[offset++];
|
||||
|
||||
uint a = forceAlpha ? 1 : (packed >> 15);
|
||||
|
||||
uint outputPacked = a * 0xff000000;
|
||||
outputPacked |= (packed << 3) & 0x000000f8;
|
||||
outputPacked |= (packed << 6) & 0x0000f800;
|
||||
outputPacked |= (packed << 9) & 0x00f80000;
|
||||
|
||||
// Replicate 5 bit components.
|
||||
outputPacked |= (outputPacked >> 5) & 0x00070707;
|
||||
|
||||
outputSpan[outOffset++] = outputPacked;
|
||||
}
|
||||
|
||||
offset += remainder;
|
||||
outOffset += outRemainder;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
byte[] output = new byte[data.Length * 2];
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
uint packed = inputSpan[offset++];
|
||||
|
||||
uint a = packed >> 15;
|
||||
|
||||
uint outputPacked = a * 0xff000000;
|
||||
outputPacked |= (packed >> 8) & 0x000000f8;
|
||||
outputPacked |= (packed << 5) & 0x0000f800;
|
||||
outputPacked |= (packed << 18) & 0x00f80000;
|
||||
|
||||
// Replicate 5 bit components.
|
||||
outputPacked |= (outputPacked >> 5) & 0x00070707;
|
||||
|
||||
outputSpan[outOffset++] = outputPacked;
|
||||
}
|
||||
|
||||
offset += remainder;
|
||||
outOffset += outRemainder;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
byte[] output = new byte[data.Length * 2];
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
uint packed = inputSpan[offset++];
|
||||
|
||||
uint outputPacked = packed & 0x0000000f;
|
||||
outputPacked |= (packed << 4) & 0x00000f00;
|
||||
outputPacked |= (packed << 8) & 0x000f0000;
|
||||
outputPacked |= (packed << 12) & 0x0f000000;
|
||||
|
||||
outputSpan[outOffset++] = outputPacked * 0x11;
|
||||
}
|
||||
|
||||
offset += remainder;
|
||||
outOffset += outRemainder;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
@@ -396,6 +396,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GAL.Format.Etc2RgbSrgb,
|
||||
GAL.Format.Etc2RgbUnorm);
|
||||
|
||||
bool supports5BitComponentFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
|
||||
GAL.Format.R5G6B5Unorm,
|
||||
GAL.Format.R5G5B5A1Unorm,
|
||||
GAL.Format.R5G5B5X1Unorm,
|
||||
GAL.Format.B5G6R5Unorm,
|
||||
GAL.Format.B5G5R5A1Unorm,
|
||||
GAL.Format.A1B5G5R5Unorm);
|
||||
|
||||
bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
|
||||
GAL.Format.R4G4B4A4Unorm);
|
||||
|
||||
PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features
|
||||
@@ -425,7 +436,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supports3DTextureCompression: true,
|
||||
supportsBgraFormat: true,
|
||||
supportsR4G4Format: false,
|
||||
supportsR4G4B4A4Format: supportsR4G4B4A4Format,
|
||||
supportsSnormBufferTextureFormat: true,
|
||||
supports5BitComponentFormat: supports5BitComponentFormat,
|
||||
supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock,
|
||||
supportsFragmentShaderOrderingIntel: false,
|
||||
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
|
||||
|
@@ -32,20 +32,9 @@ namespace Ryujinx.HLE
|
||||
|
||||
public Switch(HLEConfiguration configuration)
|
||||
{
|
||||
if (configuration.GpuRenderer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration.GpuRenderer));
|
||||
}
|
||||
|
||||
if (configuration.AudioDeviceDriver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration.AudioDeviceDriver));
|
||||
}
|
||||
|
||||
if (configuration.UserChannelPersistence == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration.UserChannelPersistence));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(configuration.GpuRenderer);
|
||||
ArgumentNullException.ThrowIfNull(configuration.AudioDeviceDriver);
|
||||
ArgumentNullException.ThrowIfNull(configuration.UserChannelPersistence);
|
||||
|
||||
Configuration = configuration;
|
||||
FileSystem = Configuration.VirtualFileSystem;
|
||||
|
@@ -77,7 +77,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
|
||||
{
|
||||
AutoResetEvent invoked = new AutoResetEvent(false);
|
||||
|
||||
|
@@ -279,10 +279,7 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
IntPtr ptr = _pointer;
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
ThrowObjectDisposed();
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(ptr == IntPtr.Zero, this);
|
||||
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
|
||||
@@ -312,10 +309,7 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
IntPtr ptr = _pointer;
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
ThrowObjectDisposed();
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(ptr == IntPtr.Zero, this);
|
||||
|
||||
ulong endOffset = offset + size;
|
||||
|
||||
@@ -454,7 +448,6 @@ namespace Ryujinx.Memory
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
|
||||
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
|
||||
}
|
||||
}
|
||||
|
@@ -411,10 +411,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
_disposed = true;
|
||||
|
||||
|
7
nuget.config
Normal file
7
nuget.config
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
Reference in New Issue
Block a user