Просмотр исходного кода

ASTC optimizations (#845)

* ASTC optimizations

* Move code to Ryujinx.Common

* Support 3D textures

* Address feedback

* Remove ASTC logging

* Use stackalloc instead of a Buffer20 struct

* Code style and cleanup

* Respond to feedback

* Rearrange public/private property ordering
Alex Barney 6 лет назад
Родитель
Сommit
d1ab9fb42c

+ 59 - 0
Ryujinx.Common/Utilities/Buffers.cs

@@ -0,0 +1,59 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+    [DebuggerDisplay("{ToString()}")]
+    [StructLayout(LayoutKind.Sequential, Size = 16)]
+    public struct Buffer16
+    {
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
+
+        public byte this[int i]
+        {
+            get => Bytes[i];
+            set => Bytes[i] = value;
+        }
+
+        public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
+
+        // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef()
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static implicit operator Span<byte>(in Buffer16 value)
+        {
+            return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value));
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static implicit operator ReadOnlySpan<byte>(in Buffer16 value)
+        {
+            return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value));
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref T As<T>() where T : unmanaged
+        {
+            if (Unsafe.SizeOf<T>() > (uint)Unsafe.SizeOf<Buffer16>())
+            {
+                throw new ArgumentException();
+            }
+
+            return ref MemoryMarshal.GetReference(AsSpan<T>());
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Span<T> AsSpan<T>() where T : unmanaged
+        {
+            return SpanHelpers.AsSpan<Buffer16, T>(ref this);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly ReadOnlySpan<T> AsReadOnlySpan<T>() where T : unmanaged
+        {
+            return SpanHelpers.AsReadOnlySpan<Buffer16, T>(ref Unsafe.AsRef(in this));
+        }
+    }
+}

+ 61 - 0
Ryujinx.Common/Utilities/SpanHelpers.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+    public static class SpanHelpers
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<T> CreateSpan<T>(ref T reference, int length)
+        {
+            return MemoryMarshal.CreateSpan(ref reference, length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<T> AsSpan<T>(ref T reference) where T : unmanaged
+        {
+            return CreateSpan(ref reference, 1);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<TSpan> AsSpan<TStruct, TSpan>(ref TStruct reference)
+            where TStruct : unmanaged where TSpan : unmanaged
+        {
+            return CreateSpan(ref Unsafe.As<TStruct, TSpan>(ref reference),
+                Unsafe.SizeOf<TStruct>() / Unsafe.SizeOf<TSpan>());
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static Span<byte> AsByteSpan<T>(ref T reference) where T : unmanaged
+        {
+            return CreateSpan(ref Unsafe.As<T, byte>(ref reference), Unsafe.SizeOf<T>());
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length)
+        {
+            return MemoryMarshal.CreateReadOnlySpan(ref reference, length);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<T> AsReadOnlySpan<T>(ref T reference) where T : unmanaged
+        {
+            return CreateReadOnlySpan(ref reference, 1);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<TSpan> AsReadOnlySpan<TStruct, TSpan>(ref TStruct reference)
+            where TStruct : unmanaged where TSpan : unmanaged
+        {
+            return CreateReadOnlySpan(ref Unsafe.As<TStruct, TSpan>(ref reference),
+                Unsafe.SizeOf<TStruct>() / Unsafe.SizeOf<TSpan>());
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<byte> AsReadOnlyByteSpan<T>(ref T reference) where T : unmanaged
+        {
+            return CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref reference), Unsafe.SizeOf<T>());
+        }
+    }
+}

+ 2 - 1
Ryujinx.Graphics.Gpu/Image/Texture.cs

@@ -7,6 +7,7 @@ using Ryujinx.Graphics.Texture.Astc;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using Ryujinx.Common.Logging;
 
 namespace Ryujinx.Graphics.Gpu.Image
 {
@@ -246,7 +247,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             if (!_context.Capabilities.SupportsAstcCompression && _info.FormatInfo.Format.IsAstc())
             {
                 if (!AstcDecoder.TryDecodeToRgba8(
-                    data,
+                    data.ToArray(),
                     _info.FormatInfo.BlockWidth,
                     _info.FormatInfo.BlockHeight,
                     _info.Width,

Разница между файлами не показана из-за своего большого размера
+ 425 - 214
Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs


+ 21 - 91
Ryujinx.Graphics.Texture/Astc/AstcPixel.cs

@@ -1,16 +1,23 @@
 using System;
-using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.Texture.Astc
 {
-    class AstcPixel
+    [StructLayout(LayoutKind.Sequential)]
+    struct AstcPixel
     {
-        public short R { get; set; }
-        public short G { get; set; }
-        public short B { get; set; }
-        public short A { get; set; }
+        internal const int StructSize = 12;
 
-        byte[] _bitDepth = new byte[4];
+        public short A;
+        public short R;
+        public short G;
+        public short B;
+
+        private uint _bitDepthInt;
+
+        private Span<byte> BitDepth => MemoryMarshal.CreateSpan(ref Unsafe.As<uint, byte>(ref _bitDepthInt), 4);
+        private Span<short> Components => MemoryMarshal.CreateSpan(ref A, 4);
 
         public AstcPixel(short a, short r, short g, short b)
         {
@@ -19,8 +26,7 @@ namespace Ryujinx.Graphics.Texture.Astc
             G = g;
             B = b;
 
-            for (int i = 0; i < 4; i++)
-                _bitDepth[i] = 8;
+            _bitDepthInt = 0x08080808;
         }
 
         public void ClampByte()
@@ -33,96 +39,20 @@ namespace Ryujinx.Graphics.Texture.Astc
 
         public short GetComponent(int index)
         {
-            switch(index)
-            {
-                case 0: return A;
-                case 1: return R;
-                case 2: return G;
-                case 3: return B;
-            }
-
-            return 0;
+            return Components[index];
         }
 
         public void SetComponent(int index, int value)
         {
-            switch (index)
-            {
-                case 0:
-                    A = (short)value;
-                    break;
-                case 1:
-                    R = (short)value;
-                    break;
-                case 2:
-                    G = (short)value;
-                    break;
-                case 3:
-                    B = (short)value;
-                    break;
-            }
-        }
-
-        public void ChangeBitDepth(byte[] depth)
-        {
-            for (int i = 0; i< 4; i++)
-            {
-                int value = ChangeBitDepth(GetComponent(i), _bitDepth[i], depth[i]);
-
-                SetComponent(i, value);
-                _bitDepth[i] = depth[i];
-            }
-        }
-
-        short ChangeBitDepth(short value, byte oldDepth, byte newDepth)
-        {
-            Debug.Assert(newDepth <= 8);
-            Debug.Assert(oldDepth <= 8);
-
-            if (oldDepth == newDepth)
-            {
-                // Do nothing
-                return value;
-            }
-            else if (oldDepth == 0 && newDepth != 0)
-            {
-                return (short)((1 << newDepth) - 1);
-            }
-            else if (newDepth > oldDepth)
-            {
-                return (short)BitArrayStream.Replicate(value, oldDepth, newDepth);
-            }
-            else
-            {
-                // oldDepth > newDepth
-                if (newDepth == 0)
-                {
-                    return 0xFF;
-                }
-                else
-                {
-                    byte bitsWasted = (byte)(oldDepth - newDepth);
-                    short tempValue = value;
-
-                    tempValue = (short)((tempValue + (1 << (bitsWasted - 1))) >> bitsWasted);
-                    tempValue = Math.Min(Math.Max((short)0, tempValue), (short)((1 << newDepth) - 1));
-
-                    return (byte)(tempValue);
-                }
-            }
+            Components[index] = (short)value;
         }
 
         public int Pack()
         {
-            AstcPixel newPixel   = new AstcPixel(A, R, G, B);
-            byte[] eightBitDepth = { 8, 8, 8, 8 };
-
-            newPixel.ChangeBitDepth(eightBitDepth);
-
-            return (byte)newPixel.A << 24 |
-                   (byte)newPixel.B << 16 |
-                   (byte)newPixel.G << 8  |
-                   (byte)newPixel.R << 0;
+            return A << 24 |
+                   B << 16 |
+                   G << 8 |
+                   R << 0;
         }
 
         // Adds more precision to the blue channel as described

+ 0 - 121
Ryujinx.Graphics.Texture/Astc/BitArrayStream.cs

@@ -1,121 +0,0 @@
-using System;
-using System.Collections;
-
-namespace Ryujinx.Graphics.Texture.Astc
-{
-    public class BitArrayStream
-    {
-        public BitArray BitsArray;
-
-        public int Position { get; private set; }
-
-        public BitArrayStream(BitArray bitArray)
-        {
-            BitsArray = bitArray;
-            Position  = 0;
-        }
-
-        public short ReadBits(int length)
-        {
-            int retValue = 0;
-            for (int i = Position; i < Position + length; i++)
-            {
-                if (BitsArray[i])
-                {
-                    retValue |= 1 << (i - Position);
-                }
-            }
-
-            Position += length;
-            return (short)retValue;
-        }
-
-        public int ReadBits(int start, int end)
-        {
-            int retValue = 0;
-            for (int i = start; i <= end; i++)
-            {
-                if (BitsArray[i])
-                {
-                    retValue |= 1 << (i - start);
-                }
-            }
-
-            return retValue;
-        }
-
-        public int ReadBit(int index)
-        {
-            return Convert.ToInt32(BitsArray[index]);
-        }
-
-        public void WriteBits(int value, int length)
-        {
-            for (int i = Position; i < Position + length; i++)
-            {
-                BitsArray[i] = ((value >> (i - Position)) & 1) != 0;
-            }
-
-            Position += length;
-        }
-
-        public byte[] ToByteArray()
-        {
-            byte[] retArray = new byte[(BitsArray.Length + 7) / 8];
-            BitsArray.CopyTo(retArray, 0);
-            return retArray;
-        }
-
-        public static int Replicate(int value, int numberBits, int toBit)
-        {
-            if (numberBits == 0) return 0;
-            if (toBit == 0) return 0;
-
-            int tempValue = value & ((1 << numberBits) - 1);
-            int retValue  = tempValue;
-            int resLength = numberBits;
-
-            while (resLength < toBit)
-            {
-                int comp = 0;
-                if (numberBits > toBit - resLength)
-                {
-                    int newShift = toBit - resLength;
-                    comp         = numberBits - newShift;
-                    numberBits   = newShift;
-                }
-                retValue <<= numberBits;
-                retValue  |= tempValue >> comp;
-                resLength += numberBits;
-            }
-            return retValue;
-        }
-
-        public static int PopCnt(int number)
-        {
-            int counter;
-            for (counter = 0; number != 0; counter++)
-            {
-                number &= number - 1;
-            }
-            return counter;
-        }
-
-        public static void Swap<T>(ref T lhs, ref T rhs)
-        {
-            T temp = lhs;
-            lhs = rhs;
-            rhs = temp;
-        }
-
-        // Transfers a bit as described in C.2.14
-        public static void BitTransferSigned(ref int a, ref int b)
-        {
-            b >>= 1;
-            b |= a & 0x80;
-            a >>= 1;
-            a &= 0x3F;
-            if ((a & 0x20) != 0) a -= 0x40;
-        }
-    }
-}

+ 72 - 0
Ryujinx.Graphics.Texture/Astc/BitStream128.cs

@@ -0,0 +1,72 @@
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Texture.Astc
+{
+    public struct BitStream128
+    {
+        private Buffer16 _data;
+        public int BitsLeft { get; set; }
+
+        public BitStream128(Buffer16 data)
+        {
+            _data = data;
+            BitsLeft = 128;
+        }
+
+        public int ReadBits(int bitCount)
+        {
+            Debug.Assert(bitCount < 32);
+
+            if (bitCount == 0)
+            {
+                return 0;
+            }
+
+            int mask = (1 << bitCount) - 1;
+            int value = _data.As<int>() & mask;
+
+            Span<ulong> span = _data.AsSpan<ulong>();
+
+            ulong carry = span[1] << (64 - bitCount);
+            span[0] = (span[0] >> bitCount) | carry;
+            span[1] >>= bitCount;
+
+            BitsLeft -= bitCount;
+
+            return value;
+        }
+
+        public void WriteBits(int value, int bitCount)
+        {
+            Debug.Assert(bitCount < 32);
+
+            if (bitCount == 0) return;
+
+            ulong maskedValue = (uint)(value & ((1 << bitCount) - 1));
+
+            Span<ulong> span = _data.AsSpan<ulong>();
+
+            if (BitsLeft < 64)
+            {
+                ulong lowMask = maskedValue << BitsLeft;
+                span[0] |= lowMask;
+            }
+
+            if (BitsLeft + bitCount > 64)
+            {
+                if (BitsLeft > 64)
+                {
+                    span[1] |= maskedValue << (BitsLeft - 64);
+                }
+                else
+                {
+                    span[1] |= maskedValue >> (64 - BitsLeft);
+                }
+            }
+
+            BitsLeft += bitCount;
+        }
+    }
+}

+ 66 - 0
Ryujinx.Graphics.Texture/Astc/Bits.cs

@@ -0,0 +1,66 @@
+namespace Ryujinx.Graphics.Texture.Astc
+{
+    internal static class Bits
+    {
+        public static readonly ushort[] Replicate8_16Table;
+        public static readonly byte[] Replicate1_7Table;
+
+        static Bits()
+        {
+            Replicate8_16Table = new ushort[0x200];
+            Replicate1_7Table = new byte[0x200];
+
+            for (int i = 0; i < 0x200; i++)
+            {
+                Replicate8_16Table[i] = (ushort)Replicate(i, 8, 16);
+                Replicate1_7Table[i] = (byte)Replicate(i, 1, 7);
+            }
+        }
+
+        public static int Replicate8_16(int value)
+        {
+            return Replicate8_16Table[value];
+        }
+
+        public static int Replicate1_7(int value)
+        {
+            return Replicate1_7Table[value];
+        }
+
+        public static int Replicate(int value, int numberBits, int toBit)
+        {
+            if (numberBits == 0) return 0;
+            if (toBit == 0) return 0;
+
+            int tempValue = value & ((1 << numberBits) - 1);
+            int retValue = tempValue;
+            int resLength = numberBits;
+
+            while (resLength < toBit)
+            {
+                int comp = 0;
+                if (numberBits > toBit - resLength)
+                {
+                    int newShift = toBit - resLength;
+                    comp = numberBits - newShift;
+                    numberBits = newShift;
+                }
+                retValue <<= numberBits;
+                retValue |= tempValue >> comp;
+                resLength += numberBits;
+            }
+
+            return retValue;
+        }
+
+        // Transfers a bit as described in C.2.14
+        public static void BitTransferSigned(ref int a, ref int b)
+        {
+            b >>= 1;
+            b |= a & 0x80;
+            a >>= 1;
+            a &= 0x3F;
+            if ((a & 0x20) != 0) a -= 0x40;
+        }
+    }
+}

+ 23 - 0
Ryujinx.Graphics.Texture/Astc/EndPointSet.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Texture.Astc
+{
+    [StructLayout(LayoutKind.Sequential, Size = AstcPixel.StructSize * 8)]
+    internal struct EndPointSet
+    {
+        private AstcPixel _start;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Span<AstcPixel> Get(int index)
+        {
+            Debug.Assert(index < 4);
+
+            ref AstcPixel start = ref Unsafe.Add(ref _start, index * 2);
+
+            return MemoryMarshal.CreateSpan(ref start, 2);
+        }
+    }
+}

+ 205 - 129
Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs

@@ -1,11 +1,14 @@
-using System.Collections;
-using System.Collections.Generic;
+using System;
+using System.Numerics;
 
 namespace Ryujinx.Graphics.Texture.Astc
 {
-    public struct IntegerEncoded
+    internal struct IntegerEncoded
     {
-        public enum EIntegerEncoding
+        internal const int StructSize = 8;
+        private static readonly IntegerEncoded[] Encodings;
+
+        public enum EIntegerEncoding : byte
         {
             JustBits,
             Quint,
@@ -13,17 +16,27 @@ namespace Ryujinx.Graphics.Texture.Astc
         }
 
         EIntegerEncoding _encoding;
-        public int NumberBits { get; private set; }
-        public int BitValue   { get; private set; }
-        public int TritValue  { get; private set; }
-        public int QuintValue { get; private set; }
+        public byte NumberBits { get; private set; }
+        public byte TritValue { get; private set; }
+        public byte QuintValue { get; private set; }
+        public int BitValue { get; private set; }
+
+        static IntegerEncoded()
+        {
+            Encodings = new IntegerEncoded[0x100];
+
+            for (int i = 0; i < Encodings.Length; i++)
+            {
+                Encodings[i] = CreateEncodingCalc(i);
+            }
+        }
 
         public IntegerEncoded(EIntegerEncoding encoding, int numBits)
         {
-            _encoding  = encoding;
-            NumberBits = numBits;
-            BitValue   = 0;
-            TritValue  = 0;
+            _encoding = encoding;
+            NumberBits = (byte)numBits;
+            BitValue = 0;
+            TritValue = 0;
             QuintValue = 0;
         }
 
@@ -52,6 +65,11 @@ namespace Ryujinx.Graphics.Texture.Astc
         }
 
         public static IntegerEncoded CreateEncoding(int maxVal)
+        {
+            return Encodings[maxVal];
+        }
+
+        private static IntegerEncoded CreateEncodingCalc(int maxVal)
         {
             while (maxVal > 0)
             {
@@ -60,19 +78,19 @@ namespace Ryujinx.Graphics.Texture.Astc
                 // Is maxVal a power of two?
                 if ((check & (check - 1)) == 0)
                 {
-                    return new IntegerEncoded(EIntegerEncoding.JustBits, BitArrayStream.PopCnt(maxVal));
+                    return new IntegerEncoded(EIntegerEncoding.JustBits, BitOperations.PopCount((uint)maxVal));
                 }
 
                 // Is maxVal of the type 3*2^n - 1?
                 if ((check % 3 == 0) && ((check / 3) & ((check / 3) - 1)) == 0)
                 {
-                    return new IntegerEncoded(EIntegerEncoding.Trit, BitArrayStream.PopCnt(check / 3 - 1));
+                    return new IntegerEncoded(EIntegerEncoding.Trit, BitOperations.PopCount((uint)(check / 3 - 1)));
                 }
 
                 // Is maxVal of the type 5*2^n - 1?
                 if ((check % 5 == 0) && ((check / 5) & ((check / 5) - 1)) == 0)
                 {
-                    return new IntegerEncoded(EIntegerEncoding.Quint, BitArrayStream.PopCnt(check / 5 - 1));
+                    return new IntegerEncoded(EIntegerEncoding.Quint, BitOperations.PopCount((uint)(check / 5 - 1)));
                 }
 
                 // Apparently it can't be represented with a bounded integer sequence...
@@ -84,150 +102,78 @@ namespace Ryujinx.Graphics.Texture.Astc
         }
 
         public static void DecodeTritBlock(
-            BitArrayStream       bitStream,
-            List<IntegerEncoded> listIntegerEncoded,
-            int                  numberBitsPerValue)
+            ref BitStream128 bitStream,
+            ref IntegerSequence listIntegerEncoded,
+            int numberBitsPerValue)
         {
             // Implement the algorithm in section C.2.12
-            int[] m = new int[5];
-            int[] t = new int[5];
-            int T;
+            Span<int> m = stackalloc int[5];
 
-            // Read the trit encoded block according to
-            // table C.2.14
             m[0] = bitStream.ReadBits(numberBitsPerValue);
-            T    = bitStream.ReadBits(2);
+            int encoded = bitStream.ReadBits(2);
             m[1] = bitStream.ReadBits(numberBitsPerValue);
-            T   |= bitStream.ReadBits(2) << 2;
+            encoded |= bitStream.ReadBits(2) << 2;
             m[2] = bitStream.ReadBits(numberBitsPerValue);
-            T   |= bitStream.ReadBits(1) << 4;
+            encoded |= bitStream.ReadBits(1) << 4;
             m[3] = bitStream.ReadBits(numberBitsPerValue);
-            T   |= bitStream.ReadBits(2) << 5;
+            encoded |= bitStream.ReadBits(2) << 5;
             m[4] = bitStream.ReadBits(numberBitsPerValue);
-            T   |= bitStream.ReadBits(1) << 7;
-
-            int c = 0;
+            encoded |= bitStream.ReadBits(1) << 7;
 
-            BitArrayStream tb = new BitArrayStream(new BitArray(new int[] { T }));
-            if (tb.ReadBits(2, 4) == 7)
-            {
-                c    = (tb.ReadBits(5, 7) << 2) | tb.ReadBits(0, 1);
-                t[4] = t[3] = 2;
-            }
-            else
-            {
-                c = tb.ReadBits(0, 4);
-                if (tb.ReadBits(5, 6) == 3)
-                {
-                    t[4] = 2;
-                    t[3] = tb.ReadBit(7);
-                }
-                else
-                {
-                    t[4] = tb.ReadBit(7);
-                    t[3] = tb.ReadBits(5, 6);
-                }
-            }
+            ReadOnlySpan<byte> encodings = GetTritEncoding(encoded);
 
-            BitArrayStream cb = new BitArrayStream(new BitArray(new int[] { c }));
-            if (cb.ReadBits(0, 1) == 3)
-            {
-                t[2] = 2;
-                t[1] = cb.ReadBit(4);
-                t[0] = (cb.ReadBit(3) << 1) | (cb.ReadBit(2) & ~cb.ReadBit(3));
-            }
-            else if (cb.ReadBits(2, 3) == 3)
-            {
-                t[2] = 2;
-                t[1] = 2;
-                t[0] = cb.ReadBits(0, 1);
-            }
-            else
-            {
-                t[2] = cb.ReadBit(4);
-                t[1] = cb.ReadBits(2, 3);
-                t[0] = (cb.ReadBit(1) << 1) | (cb.ReadBit(0) & ~cb.ReadBit(1));
-            }
+            IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Trit, numberBitsPerValue);
 
             for (int i = 0; i < 5; i++)
             {
-                IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Trit, numberBitsPerValue)
-                {
-                    BitValue  = m[i],
-                    TritValue = t[i]
-                };
-                listIntegerEncoded.Add(intEncoded);
+                intEncoded.BitValue = m[i];
+                intEncoded.TritValue = encodings[i];
+
+                listIntegerEncoded.Add(ref intEncoded);
             }
         }
 
         public static void DecodeQuintBlock(
-            BitArrayStream       bitStream,
-            List<IntegerEncoded> listIntegerEncoded,
-            int                  numberBitsPerValue)
+            ref BitStream128 bitStream,
+            ref IntegerSequence listIntegerEncoded,
+            int numberBitsPerValue)
         {
-            // Implement the algorithm in section C.2.12
-            int[] m = new int[3];
-            int[] qa = new int[3];
-            int q;
+            ReadOnlySpan<byte> interleavedBits = new byte[] { 3, 2, 2 };
 
-            // Read the trit encoded block according to
-            // table C.2.15
-            m[0] = bitStream.ReadBits(numberBitsPerValue);
-            q    = bitStream.ReadBits(3);
-            m[1] = bitStream.ReadBits(numberBitsPerValue);
-            q   |= bitStream.ReadBits(2) << 3;
-            m[2] = bitStream.ReadBits(numberBitsPerValue);
-            q   |= bitStream.ReadBits(2) << 5;
+            // Implement the algorithm in section C.2.12
+            Span<int> m = stackalloc int[3];
+            ulong encoded = 0;
+            int encodedBitsRead = 0;
 
-            BitArrayStream qb = new BitArrayStream(new BitArray(new int[] { q }));
-            if (qb.ReadBits(1, 2) == 3 && qb.ReadBits(5, 6) == 0)
-            {
-                qa[0] = qa[1] = 4;
-                qa[2] = (qb.ReadBit(0) << 2) | ((qb.ReadBit(4) & ~qb.ReadBit(0)) << 1) | (qb.ReadBit(3) & ~qb.ReadBit(0));
-            }
-            else
+            for (int i = 0; i < m.Length; i++)
             {
-                int c = 0;
-                if (qb.ReadBits(1, 2) == 3)
-                {
-                    qa[2] = 4;
-                    c    = (qb.ReadBits(3, 4) << 3) | ((~qb.ReadBits(5, 6) & 3) << 1) | qb.ReadBit(0);
-                }
-                else
-                {
-                    qa[2] = qb.ReadBits(5, 6);
-                    c    = qb.ReadBits(0, 4);
-                }
+                m[i] = bitStream.ReadBits(numberBitsPerValue);
 
-                BitArrayStream cb = new BitArrayStream(new BitArray(new int[] { c }));
-                if (cb.ReadBits(0, 2) == 5)
-                {
-                    qa[1] = 4;
-                    qa[0] = cb.ReadBits(3, 4);
-                }
-                else
-                {
-                    qa[1] = cb.ReadBits(3, 4);
-                    qa[0] = cb.ReadBits(0, 2);
-                }
+                uint encodedBits = (uint)bitStream.ReadBits(interleavedBits[i]);
+
+                encoded |= encodedBits << encodedBitsRead;
+                encodedBitsRead += interleavedBits[i];
             }
 
+            ReadOnlySpan<byte> encodings = GetQuintEncoding((int)encoded);
+
             for (int i = 0; i < 3; i++)
             {
                 IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Quint, numberBitsPerValue)
                 {
-                    BitValue   = m[i],
-                    QuintValue = qa[i]
+                    BitValue = m[i],
+                    QuintValue = encodings[i]
                 };
-                listIntegerEncoded.Add(intEncoded);
+
+                listIntegerEncoded.Add(ref intEncoded);
             }
         }
 
         public static void DecodeIntegerSequence(
-            List<IntegerEncoded> decodeIntegerSequence,
-            BitArrayStream       bitStream,
-            int                  maxRange,
-            int                  numberValues)
+            ref IntegerSequence decodeIntegerSequence,
+            ref BitStream128 bitStream,
+            int maxRange,
+            int numberValues)
         {
             // Determine encoding parameters
             IntegerEncoded intEncoded = CreateEncoding(maxRange);
@@ -240,7 +186,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                 {
                     case EIntegerEncoding.Quint:
                     {
-                        DecodeQuintBlock(bitStream, decodeIntegerSequence, intEncoded.NumberBits);
+                        DecodeQuintBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits);
                         numberValuesDecoded += 3;
 
                         break;
@@ -248,7 +194,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                     case EIntegerEncoding.Trit:
                     {
-                        DecodeTritBlock(bitStream, decodeIntegerSequence, intEncoded.NumberBits);
+                        DecodeTritBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits);
                         numberValuesDecoded += 5;
 
                         break;
@@ -257,7 +203,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     case EIntegerEncoding.JustBits:
                     {
                         intEncoded.BitValue = bitStream.ReadBits(intEncoded.NumberBits);
-                        decodeIntegerSequence.Add(intEncoded);
+                        decodeIntegerSequence.Add(ref intEncoded);
                         numberValuesDecoded++;
 
                         break;
@@ -265,5 +211,135 @@ namespace Ryujinx.Graphics.Texture.Astc
                 }
             }
         }
+
+        private static ReadOnlySpan<byte> GetTritEncoding(int index)
+        {
+            return TritEncodings.Slice(index * 5, 5);
+        }
+
+        private static ReadOnlySpan<byte> GetQuintEncoding(int index)
+        {
+            return QuintEncodings.Slice(index * 3, 3);
+        }
+
+        private static ReadOnlySpan<byte> TritEncodings => new byte[]
+        {
+            0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
+            0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0,
+            2, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 2, 0, 0, 0,
+            1, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 2, 0, 0,
+            0, 2, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0, 0,
+            2, 0, 2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+            2, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 1, 0, 0,
+            1, 1, 1, 0, 0, 2, 1, 1, 0, 0, 1, 1, 2, 0, 0,
+            0, 2, 1, 0, 0, 1, 2, 1, 0, 0, 2, 2, 1, 0, 0,
+            2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 2, 2,
+            2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 1, 0,
+            1, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 2, 1, 0,
+            0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0,
+            1, 0, 2, 1, 0, 0, 2, 0, 1, 0, 1, 2, 0, 1, 0,
+            2, 2, 0, 1, 0, 2, 0, 2, 1, 0, 0, 2, 2, 1, 0,
+            1, 2, 2, 1, 0, 2, 2, 2, 1, 0, 2, 0, 2, 1, 0,
+            0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 2, 0, 1, 1, 0,
+            0, 1, 2, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
+            2, 1, 1, 1, 0, 1, 1, 2, 1, 0, 0, 2, 1, 1, 0,
+            1, 2, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 2, 1, 0,
+            0, 1, 0, 2, 2, 1, 1, 0, 2, 2, 2, 1, 0, 2, 2,
+            1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 0, 0, 2, 0,
+            2, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 0, 2, 0,
+            1, 1, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 2, 0,
+            0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 2, 0, 2, 0,
+            2, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 2, 2, 0,
+            2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 1, 2, 0,
+            1, 0, 1, 2, 0, 2, 0, 1, 2, 0, 0, 1, 2, 2, 0,
+            0, 1, 1, 2, 0, 1, 1, 1, 2, 0, 2, 1, 1, 2, 0,
+            1, 1, 2, 2, 0, 0, 2, 1, 2, 0, 1, 2, 1, 2, 0,
+            2, 2, 1, 2, 0, 2, 1, 2, 2, 0, 0, 2, 0, 2, 2,
+            1, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2,
+            0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2,
+            0, 0, 2, 0, 2, 0, 1, 0, 0, 2, 1, 1, 0, 0, 2,
+            2, 1, 0, 0, 2, 1, 0, 2, 0, 2, 0, 2, 0, 0, 2,
+            1, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 0, 2, 0, 2,
+            0, 2, 2, 0, 2, 1, 2, 2, 0, 2, 2, 2, 2, 0, 2,
+            2, 0, 2, 0, 2, 0, 0, 1, 0, 2, 1, 0, 1, 0, 2,
+            2, 0, 1, 0, 2, 0, 1, 2, 0, 2, 0, 1, 1, 0, 2,
+            1, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 1, 2, 0, 2,
+            0, 2, 1, 0, 2, 1, 2, 1, 0, 2, 2, 2, 1, 0, 2,
+            2, 1, 2, 0, 2, 0, 2, 2, 2, 2, 1, 2, 2, 2, 2,
+            2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1,
+            1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 2, 0, 1,
+            0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1,
+            1, 0, 2, 0, 1, 0, 2, 0, 0, 1, 1, 2, 0, 0, 1,
+            2, 2, 0, 0, 1, 2, 0, 2, 0, 1, 0, 2, 2, 0, 1,
+            1, 2, 2, 0, 1, 2, 2, 2, 0, 1, 2, 0, 2, 0, 1,
+            0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 2, 0, 1, 0, 1,
+            0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1,
+            2, 1, 1, 0, 1, 1, 1, 2, 0, 1, 0, 2, 1, 0, 1,
+            1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 2, 1, 2, 0, 1,
+            0, 0, 1, 2, 2, 1, 0, 1, 2, 2, 2, 0, 1, 2, 2,
+            0, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1,
+            2, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 1, 0, 1, 1,
+            1, 1, 0, 1, 1, 2, 1, 0, 1, 1, 1, 0, 2, 1, 1,
+            0, 2, 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 1,
+            2, 0, 2, 1, 1, 0, 2, 2, 1, 1, 1, 2, 2, 1, 1,
+            2, 2, 2, 1, 1, 2, 0, 2, 1, 1, 0, 0, 1, 1, 1,
+            1, 0, 1, 1, 1, 2, 0, 1, 1, 1, 0, 1, 2, 1, 1,
+            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1,
+            1, 1, 2, 1, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 1,
+            2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 0, 1, 1, 2, 2,
+            1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2,
+            0, 0, 0, 2, 1, 1, 0, 0, 2, 1, 2, 0, 0, 2, 1,
+            0, 0, 2, 2, 1, 0, 1, 0, 2, 1, 1, 1, 0, 2, 1,
+            2, 1, 0, 2, 1, 1, 0, 2, 2, 1, 0, 2, 0, 2, 1,
+            1, 2, 0, 2, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1,
+            0, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 1,
+            2, 0, 2, 2, 1, 0, 0, 1, 2, 1, 1, 0, 1, 2, 1,
+            2, 0, 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 1, 2, 1,
+            1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1,
+            0, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1,
+            2, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, 2, 1, 2, 2,
+            2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 1, 2,
+            1, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 0, 2, 1, 2,
+            0, 1, 0, 1, 2, 1, 1, 0, 1, 2, 2, 1, 0, 1, 2,
+            1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 2,
+            2, 2, 0, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, 2,
+            1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0, 2, 1, 2,
+            0, 0, 1, 1, 2, 1, 0, 1, 1, 2, 2, 0, 1, 1, 2,
+            0, 1, 2, 1, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 2,
+            2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 0, 2, 1, 1, 2,
+            1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 2,
+            0, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+            2, 1, 2, 2, 2
+        };
+
+        private static ReadOnlySpan<byte> QuintEncodings => new byte[]
+        {
+            0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0,
+            0, 4, 0, 4, 4, 0, 4, 4, 4, 0, 1, 0, 1, 1, 0,
+            2, 1, 0, 3, 1, 0, 4, 1, 0, 1, 4, 0, 4, 4, 1,
+            4, 4, 4, 0, 2, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0,
+            4, 2, 0, 2, 4, 0, 4, 4, 2, 4, 4, 4, 0, 3, 0,
+            1, 3, 0, 2, 3, 0, 3, 3, 0, 4, 3, 0, 3, 4, 0,
+            4, 4, 3, 4, 4, 4, 0, 0, 1, 1, 0, 1, 2, 0, 1,
+            3, 0, 1, 4, 0, 1, 0, 4, 1, 4, 0, 4, 0, 4, 4,
+            0, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 4, 1, 1,
+            1, 4, 1, 4, 1, 4, 1, 4, 4, 0, 2, 1, 1, 2, 1,
+            2, 2, 1, 3, 2, 1, 4, 2, 1, 2, 4, 1, 4, 2, 4,
+            2, 4, 4, 0, 3, 1, 1, 3, 1, 2, 3, 1, 3, 3, 1,
+            4, 3, 1, 3, 4, 1, 4, 3, 4, 3, 4, 4, 0, 0, 2,
+            1, 0, 2, 2, 0, 2, 3, 0, 2, 4, 0, 2, 0, 4, 2,
+            2, 0, 4, 3, 0, 4, 0, 1, 2, 1, 1, 2, 2, 1, 2,
+            3, 1, 2, 4, 1, 2, 1, 4, 2, 2, 1, 4, 3, 1, 4,
+            0, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 2, 4, 2, 2,
+            2, 4, 2, 2, 2, 4, 3, 2, 4, 0, 3, 2, 1, 3, 2,
+            2, 3, 2, 3, 3, 2, 4, 3, 2, 3, 4, 2, 2, 3, 4,
+            3, 3, 4, 0, 0, 3, 1, 0, 3, 2, 0, 3, 3, 0, 3,
+            4, 0, 3, 0, 4, 3, 0, 0, 4, 1, 0, 4, 0, 1, 3,
+            1, 1, 3, 2, 1, 3, 3, 1, 3, 4, 1, 3, 1, 4, 3,
+            0, 1, 4, 1, 1, 4, 0, 2, 3, 1, 2, 3, 2, 2, 3,
+            3, 2, 3, 4, 2, 3, 2, 4, 3, 0, 2, 4, 1, 2, 4,
+            0, 3, 3, 1, 3, 3, 2, 3, 3, 3, 3, 3, 4, 3, 3,
+            3, 4, 3, 0, 3, 4, 1, 3, 4
+        };
     }
 }

+ 31 - 0
Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Texture.Astc
+{
+    [StructLayout(LayoutKind.Sequential, Size = IntegerEncoded.StructSize * Capacity + sizeof(int))]
+    internal struct IntegerSequence
+    {
+        private const int Capacity = 100;
+
+        private int _length;
+        private IntegerEncoded _start;
+
+        public Span<IntegerEncoded> List => MemoryMarshal.CreateSpan(ref _start, _length);
+
+        public void Reset() => _length = 0;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Add(ref IntegerEncoded item)
+        {
+            Debug.Assert(_length < Capacity);
+
+            int oldLength = _length;
+            _length++;
+
+            List[oldLength] = item;
+        }
+    }
+}

+ 1 - 0
Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj

@@ -6,6 +6,7 @@
 
   <PropertyGroup>
     <TargetFramework>netcoreapp3.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
 </Project>

Некоторые файлы не были показаны из-за большого количества измененных файлов