diff --git a/Ryujinx.Common/Utilities/Buffers.cs b/Ryujinx.Common/Utilities/Buffers.cs
new file mode 100644
index 0000000000..d614bfc43b
--- /dev/null
+++ b/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));
+        }
+    }
+}
diff --git a/Ryujinx.Common/Utilities/SpanHelpers.cs b/Ryujinx.Common/Utilities/SpanHelpers.cs
new file mode 100644
index 0000000000..84c130233f
--- /dev/null
+++ b/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>());
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 11855592f5..b939e0b274 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/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,
diff --git a/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs
index 2f24fd1e53..4ba332d07b 100644
--- a/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs
+++ b/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs
@@ -1,20 +1,179 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
+using Ryujinx.Common.Utilities;
+using System;
 using System.Diagnostics;
-using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.Texture.Astc
 {
     // https://github.com/GammaUNC/FasTC/blob/master/ASTCEncoder/src/Decompressor.cpp
-    public static class AstcDecoder
+    public class AstcDecoder
     {
+        private ReadOnlyMemory<byte> InputBuffer { get; }
+        private Memory<byte> OutputBuffer { get; }
+
+        private int BlockSizeX { get; }
+        private int BlockSizeY { get; }
+
+        private AstcLevel[] Levels { get; }
+
+        private bool Success { get; set; }
+
+        public int TotalBlockCount { get; }
+
+        public AstcDecoder(
+            ReadOnlyMemory<byte> inputBuffer,
+            Memory<byte> outputBuffer,
+            int blockWidth,
+            int blockHeight,
+            int width,
+            int height,
+            int depth,
+            int levels)
+        {
+            if ((uint)blockWidth > 12 || (uint)blockHeight > 12)
+            {
+                throw new AstcDecoderException("Invalid block size.");
+            }
+
+            InputBuffer = inputBuffer;
+            OutputBuffer = outputBuffer;
+
+            BlockSizeX = blockWidth;
+            BlockSizeY = blockHeight;
+
+            Levels = new AstcLevel[levels];
+
+            TotalBlockCount = 0;
+
+            int currentInputBlock = 0;
+            int currentOutputOffset = 0;
+
+            for (int i = 0; i < levels; i++)
+            {
+                ref AstcLevel level = ref Levels[i];
+
+                level.ImageSizeX = Math.Max(1, width >> i);
+                level.ImageSizeY = Math.Max(1, height >> i);
+                level.ImageSizeZ = Math.Max(1, depth >> i);
+
+                level.BlockCountX = (level.ImageSizeX + blockWidth - 1) / blockWidth;
+                level.BlockCountY = (level.ImageSizeY + blockHeight - 1) / blockHeight;
+
+                level.StartBlock = currentInputBlock;
+                level.OutputByteOffset = currentOutputOffset;
+
+                currentInputBlock += level.TotalBlockCount;
+                currentOutputOffset += level.PixelCount * 4;
+            }
+
+            TotalBlockCount = currentInputBlock;
+        }
+
+        private struct AstcLevel
+        {
+            public int ImageSizeX { get; set; }
+            public int ImageSizeY { get; set; }
+            public int ImageSizeZ { get; set; }
+
+            public int BlockCountX { get; set; }
+            public int BlockCountY { get; set; }
+
+            public int StartBlock { get; set; }
+            public int OutputByteOffset { get; set; }
+
+            public int TotalBlockCount => BlockCountX * BlockCountY * ImageSizeZ;
+            public int PixelCount => ImageSizeX * ImageSizeY * ImageSizeZ;
+        }
+
+        public static int QueryDecompressedSize(int sizeX, int sizeY, int sizeZ, int levelCount)
+        {
+            int size = 0;
+
+            for (int i = 0; i < levelCount; i++)
+            {
+                int levelSizeX = Math.Max(1, sizeX >> i);
+                int levelSizeY = Math.Max(1, sizeY >> i);
+                int levelSizeZ = Math.Max(1, sizeZ >> i);
+
+                size += levelSizeX * levelSizeY * levelSizeZ;
+            }
+
+            return size * 4;
+        }
+
+        public void ProcessBlock(int index)
+        {
+            Buffer16 inputBlock = MemoryMarshal.Cast<byte, Buffer16>(InputBuffer.Span)[index];
+
+            Span<int> decompressedData = stackalloc int[144];
+
+            try
+            {
+                DecompressBlock(inputBlock, decompressedData, BlockSizeX, BlockSizeY);
+            }
+            catch (Exception)
+            {
+                Success = false;
+            }
+
+            Span<byte> decompressedBytes = MemoryMarshal.Cast<int, byte>(decompressedData);
+
+            AstcLevel levelInfo = GetLevelInfo(index);
+
+            WriteDecompressedBlock(decompressedBytes, OutputBuffer.Span.Slice(levelInfo.OutputByteOffset),
+                index - levelInfo.StartBlock, levelInfo);
+        }
+
+        private AstcLevel GetLevelInfo(int blockIndex)
+        {
+            foreach (AstcLevel levelInfo in Levels)
+            {
+                if (blockIndex < levelInfo.StartBlock + levelInfo.TotalBlockCount)
+                {
+                    return levelInfo;
+                }
+            }
+
+            throw new AstcDecoderException("Invalid block index.");
+        }
+
+        private void WriteDecompressedBlock(ReadOnlySpan<byte> block, Span<byte> outputBuffer, int blockIndex, AstcLevel level)
+        {
+            int stride = level.ImageSizeX * 4;
+
+            int blockCordX = blockIndex % level.BlockCountX;
+            int blockCordY = blockIndex / level.BlockCountX;
+
+            int pixelCordX = blockCordX * BlockSizeX;
+            int pixelCordY = blockCordY * BlockSizeY;
+
+            int outputPixelsX = Math.Min(pixelCordX + BlockSizeX, level.ImageSizeX) - pixelCordX;
+            int outputPixelsY = Math.Min(pixelCordY + BlockSizeY, level.ImageSizeY * level.ImageSizeZ) - pixelCordY;
+
+            int outputStart = pixelCordX * 4 + pixelCordY * stride;
+            int outputOffset = outputStart;
+
+            int inputOffset = 0;
+
+            for (int i = 0; i < outputPixelsY; i++)
+            {
+                ReadOnlySpan<byte> blockRow = block.Slice(inputOffset, outputPixelsX * 4);
+                Span<byte> outputRow = outputBuffer.Slice(outputOffset);
+                blockRow.CopyTo(outputRow);
+
+                inputOffset += BlockSizeX * 4;
+                outputOffset += stride;
+            }
+        }
+
         struct TexelWeightParams
         {
-            public int  Width;
-            public int  Height;
+            public int Width;
+            public int Height;
+            public int MaxWeight;
             public bool DualPlane;
-            public int  MaxWeight;
             public bool Error;
             public bool VoidExtentLdr;
             public bool VoidExtentHdr;
@@ -48,96 +207,106 @@ namespace Ryujinx.Graphics.Texture.Astc
         }
 
         public static bool TryDecodeToRgba8(
-            Span<byte>     data,
-            int            blockWidth,
-            int            blockHeight,
-            int            width,
-            int            height,
-            int            depth,
-            int            levels,
+            ReadOnlyMemory<byte> data,
+            int blockWidth,
+            int blockHeight,
+            int width,
+            int height,
+            int depth,
+            int levels,
             out Span<byte> decoded)
         {
-            bool success = true;
+            byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels)];
 
-            using (MemoryStream inputStream = new MemoryStream(data.ToArray()))
+            AstcDecoder decoder = new AstcDecoder(data, output, blockWidth, blockHeight, width, height, depth, levels);
+
+            for (int i = 0; i < decoder.TotalBlockCount; i++)
             {
-                BinaryReader binReader = new BinaryReader(inputStream);
-
-                using (MemoryStream outputStream = new MemoryStream())
-                {
-                    int blockIndex = 0;
-
-                    int mipOffset = 0;
-
-                    for (int l = 0; l < levels; l++)
-                    {
-                        int sliceSize = width * height * 4;
-
-                        for (int k = 0; k < depth;  k++)
-                        for (int j = 0; j < height; j += blockHeight)
-                        for (int i = 0; i < width;  i += blockWidth)
-                        {
-                            int[] decompressedData = new int[144];
-
-                            try
-                            {
-                                DecompressBlock(binReader.ReadBytes(0x10), decompressedData, blockWidth, blockHeight);
-                            }
-                            catch (Exception)
-                            {
-                                success = false;
-                            }
-
-                            int decompressedWidth  = Math.Min(blockWidth,  width  - i);
-                            int decompressedHeight = Math.Min(blockHeight, height - j);
-
-                            int baseOffset = mipOffset + k * sliceSize + (j * width + i) * 4;
-
-                            for (int jj = 0; jj < decompressedHeight; jj++)
-                            {
-                                outputStream.Seek(baseOffset + jj * width * 4, SeekOrigin.Begin);
-
-                                byte[] outputBuffer = new byte[decompressedData.Length * sizeof(int)];
-
-                                Buffer.BlockCopy(decompressedData, 0, outputBuffer, 0, outputBuffer.Length);
-
-                                outputStream.Write(outputBuffer, jj * blockWidth * 4, decompressedWidth * 4);
-                            }
-
-                            blockIndex++;
-                        }
-
-                        mipOffset += sliceSize * depth;
-
-                        width  = Math.Max(1, width  >> 1);
-                        height = Math.Max(1, height >> 1);
-                        depth  = Math.Max(1, depth  >> 1);
-                    }
-
-                    decoded = outputStream.ToArray();
-                }
+                decoder.ProcessBlock(i);
             }
 
-            return success;
+            decoded = output;
+
+            return decoder.Success;
+        }
+
+        public static bool TryDecodeToRgba8(
+            ReadOnlyMemory<byte> data,
+            Memory<byte> outputBuffer,
+            int blockWidth,
+            int blockHeight,
+            int width,
+            int height,
+            int depth,
+            int levels)
+        {
+            AstcDecoder decoder = new AstcDecoder(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels);
+
+            for (int i = 0; i < decoder.TotalBlockCount; i++)
+            {
+                decoder.ProcessBlock(i);
+            }
+
+            return decoder.Success;
+        }
+
+        public static bool TryDecodeToRgba8P(
+            ReadOnlyMemory<byte> data,
+            Memory<byte> outputBuffer,
+            int blockWidth,
+            int blockHeight,
+            int width,
+            int height,
+            int depth,
+            int levels)
+        {
+            AstcDecoder decoder = new AstcDecoder(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels);
+
+            // Lazy parallelism
+            Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x));
+
+            return decoder.Success;
+        }
+
+        public static bool TryDecodeToRgba8P(
+            ReadOnlyMemory<byte> data,
+            int blockWidth,
+            int blockHeight,
+            int width,
+            int height,
+            int depth,
+            int levels,
+            out Span<byte> decoded)
+        {
+            byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels)];
+
+            AstcDecoder decoder = new AstcDecoder(data, output, blockWidth, blockHeight, width, height, depth, levels);
+
+            Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x));
+
+            decoded = output;
+
+            return decoder.Success;
         }
 
         public static bool DecompressBlock(
-            byte[] inputBuffer,
-            int[]  outputBuffer,
-            int    blockWidth,
-            int    blockHeight)
+            Buffer16 inputBlock,
+            Span<int> outputBuffer,
+            int blockWidth,
+            int blockHeight)
         {
-            BitArrayStream    bitStream   = new BitArrayStream(new BitArray(inputBuffer));
-            TexelWeightParams texelParams = DecodeBlockInfo(bitStream);
+            BitStream128 bitStream = new BitStream128(inputBlock);
+
+            DecodeBlockInfo(ref bitStream, out TexelWeightParams texelParams);
 
             if (texelParams.Error)
             {
-                throw new AstcDecoderException("Invalid block mode.");
+                throw new AstcDecoderException("Invalid block mode");
             }
 
             if (texelParams.VoidExtentLdr)
             {
-                FillVoidExtentLdr(bitStream, outputBuffer, blockWidth, blockHeight);
+                FillVoidExtentLdr(ref bitStream, outputBuffer, blockWidth, blockHeight);
 
                 return true;
             }
@@ -170,11 +339,12 @@ namespace Ryujinx.Graphics.Texture.Astc
             // each partition.
 
             // Determine partitions, partition index, and color endpoint modes
-            int    planeIndices      = -1;
-            int    partitionIndex;
-            uint[] colorEndpointMode = { 0, 0, 0, 0 };
+            int planeIndices;
+            int partitionIndex;
 
-            BitArrayStream colorEndpointStream = new BitArrayStream(new BitArray(16 * 8));
+            Span<uint> colorEndpointMode = stackalloc uint[4];
+
+            BitStream128 colorEndpointStream = new BitStream128();
 
             // Read extra config data...
             uint baseColorEndpointMode = 0;
@@ -182,11 +352,11 @@ namespace Ryujinx.Graphics.Texture.Astc
             if (numberPartitions == 1)
             {
                 colorEndpointMode[0] = (uint)bitStream.ReadBits(4);
-                partitionIndex       = 0;
+                partitionIndex = 0;
             }
             else
             {
-                partitionIndex        = bitStream.ReadBits(10);
+                partitionIndex = bitStream.ReadBits(10);
                 baseColorEndpointMode = (uint)bitStream.ReadBits(6);
             }
 
@@ -194,7 +364,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
             // Remaining bits are color endpoint data...
             int numberWeightBits = texelParams.GetPackedBitSize();
-            int remainingBits    = 128 - numberWeightBits - bitStream.Position;
+            int remainingBits = bitStream.BitsLeft - numberWeightBits;
 
             // Consider extra bits prior to texel data...
             uint extraColorEndpointModeBits = 0;
@@ -203,9 +373,9 @@ namespace Ryujinx.Graphics.Texture.Astc
             {
                 switch (numberPartitions)
                 {
-                    case 2:  extraColorEndpointModeBits += 2; break;
-                    case 3:  extraColorEndpointModeBits += 5; break;
-                    case 4:  extraColorEndpointModeBits += 8; break;
+                    case 2: extraColorEndpointModeBits += 2; break;
+                    case 3: extraColorEndpointModeBits += 5; break;
+                    case 4: extraColorEndpointModeBits += 8; break;
                     default: Debug.Assert(false); break;
                 }
             }
@@ -240,10 +410,10 @@ namespace Ryujinx.Graphics.Texture.Astc
             if (baseMode != 0)
             {
                 uint extraColorEndpointMode = (uint)bitStream.ReadBits((int)extraColorEndpointModeBits);
-                uint tempColorEndpointMode  = (extraColorEndpointMode << 6) | baseColorEndpointMode;
-                tempColorEndpointMode     >>= 2;
+                uint tempColorEndpointMode = (extraColorEndpointMode << 6) | baseColorEndpointMode;
+                tempColorEndpointMode >>= 2;
 
-                bool[] c = new bool[4];
+                Span<bool> c = stackalloc bool[4];
 
                 for (int i = 0; i < numberPartitions; i++)
                 {
@@ -251,7 +421,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     tempColorEndpointMode >>= 1;
                 }
 
-                byte[] m = new byte[4];
+                Span<byte> m = stackalloc byte[4];
 
                 for (int i = 0; i < numberPartitions; i++)
                 {
@@ -272,7 +442,7 @@ namespace Ryujinx.Graphics.Texture.Astc
             {
                 uint tempColorEndpointMode = baseColorEndpointMode >> 2;
 
-                for (uint i = 0; i < numberPartitions; i++)
+                for (int i = 0; i < numberPartitions; i++)
                 {
                     colorEndpointMode[i] = tempColorEndpointMode;
                 }
@@ -283,27 +453,24 @@ namespace Ryujinx.Graphics.Texture.Astc
             {
                 Debug.Assert(colorEndpointMode[i] < 16);
             }
-            Debug.Assert(bitStream.Position + texelParams.GetPackedBitSize() == 128);
+            Debug.Assert(bitStream.BitsLeft == texelParams.GetPackedBitSize());
 
             // Decode both color data and texel weight data
-            int[] colorValues = new int[32]; // Four values * two endpoints * four maximum partitions
-            DecodeColorValues(colorValues, colorEndpointStream.ToByteArray(), colorEndpointMode, numberPartitions, colorDataBits);
+            Span<int> colorValues = stackalloc int[32]; // Four values * two endpoints * four maximum partitions
+            DecodeColorValues(colorValues, ref colorEndpointStream, colorEndpointMode, numberPartitions, colorDataBits);
 
-            AstcPixel[][] endPoints = new AstcPixel[4][];
-            endPoints[0] = new AstcPixel[2];
-            endPoints[1] = new AstcPixel[2];
-            endPoints[2] = new AstcPixel[2];
-            endPoints[3] = new AstcPixel[2];
+            EndPointSet endPoints;
+            unsafe { _ = &endPoints; } // Skip struct initialization
 
             int colorValuesPosition = 0;
 
             for (int i = 0; i < numberPartitions; i++)
             {
-                ComputeEndpoints(endPoints[i], colorValues, colorEndpointMode[i], ref colorValuesPosition);
+                ComputeEndpoints(endPoints.Get(i), colorValues, colorEndpointMode[i], ref colorValuesPosition);
             }
 
             // Read the texel weight data.
-            byte[] texelWeightData = (byte[])inputBuffer.Clone();
+            Buffer16 texelWeightData = inputBlock;
 
             // Reverse everything
             for (int i = 0; i < 8; i++)
@@ -311,28 +478,32 @@ namespace Ryujinx.Graphics.Texture.Astc
                 byte a = ReverseByte(texelWeightData[i]);
                 byte b = ReverseByte(texelWeightData[15 - i]);
 
-                texelWeightData[i]      = b;
+                texelWeightData[i] = b;
                 texelWeightData[15 - i] = a;
             }
 
             // Make sure that higher non-texel bits are set to zero
-            int clearByteStart                   = (texelParams.GetPackedBitSize() >> 3) + 1;
+            int clearByteStart = (texelParams.GetPackedBitSize() >> 3) + 1;
             texelWeightData[clearByteStart - 1] &= (byte)((1 << (texelParams.GetPackedBitSize() % 8)) - 1);
 
             int cLen = 16 - clearByteStart;
             for (int i = clearByteStart; i < clearByteStart + cLen; i++) texelWeightData[i] = 0;
 
-            List<IntegerEncoded> texelWeightValues = new List<IntegerEncoded>();
-            BitArrayStream weightBitStream         = new BitArrayStream(new BitArray(texelWeightData));
+            IntegerSequence texelWeightValues;
+            unsafe { _ = &texelWeightValues; } // Skip struct initialization
+            texelWeightValues.Reset();
 
-            IntegerEncoded.DecodeIntegerSequence(texelWeightValues, weightBitStream, texelParams.MaxWeight, texelParams.GetNumWeightValues());
+            BitStream128 weightBitStream = new BitStream128(texelWeightData);
+
+            IntegerEncoded.DecodeIntegerSequence(ref texelWeightValues, ref weightBitStream, texelParams.MaxWeight, texelParams.GetNumWeightValues());
 
             // Blocks can be at most 12x12, so we can have as many as 144 weights
-            int[][] weights = new int[2][];
-            weights[0] = new int[144];
-            weights[1] = new int[144];
+            Weights weights;
+            unsafe { _ = &weights; } // Skip struct initialization
 
-            UnquantizeTexelWeights(weights, texelWeightValues, texelParams, blockWidth, blockHeight);
+            UnquantizeTexelWeights(ref weights, ref texelWeightValues, ref texelParams, blockWidth, blockHeight);
+
+            ushort[] table = Bits.Replicate8_16Table;
 
             // Now that we have endpoints and weights, we can interpolate and generate
             // the proper decoding...
@@ -343,13 +514,13 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int partition = Select2dPartition(partitionIndex, i, j, numberPartitions, ((blockHeight * blockWidth) < 32));
                     Debug.Assert(partition < numberPartitions);
 
-                    AstcPixel pixel = new AstcPixel(0, 0, 0, 0);
+                    AstcPixel pixel = new AstcPixel();
                     for (int component = 0; component < 4; component++)
                     {
-                        int component0 = endPoints[partition][0].GetComponent(component);
-                        component0     = BitArrayStream.Replicate(component0, 8, 16);
-                        int component1 = endPoints[partition][1].GetComponent(component);
-                        component1     = BitArrayStream.Replicate(component1, 8, 16);
+                        int component0 = endPoints.Get(partition)[0].GetComponent(component);
+                        component0 = table[component0];
+                        int component1 = endPoints.Get(partition)[1].GetComponent(component);
+                        component1 = table[component1];
 
                         int plane = 0;
 
@@ -358,7 +529,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                             plane = 1;
                         }
 
-                        int weight = weights[plane][j * blockWidth + i];
+                        int weight = weights.Get(plane)[j * blockWidth + i];
                         int finalComponent = (component0 * (64 - weight) + component1 * weight + 32) / 64;
 
                         if (finalComponent == 65535)
@@ -379,6 +550,38 @@ namespace Ryujinx.Graphics.Texture.Astc
             return true;
         }
 
+        // Blocks can be at most 12x12, so we can have as many as 144 weights
+        [StructLayout(LayoutKind.Sequential, Size = 144 * sizeof(int) * Count)]
+        private struct Weights
+        {
+            private int _start;
+
+            public const int Count = 2;
+
+            public Span<int> this[int index]
+            {
+                get
+                {
+                    if ((uint)index >= Count)
+                    {
+                        throw new ArgumentOutOfRangeException();
+                    }
+
+                    ref int start = ref Unsafe.Add(ref _start, index * 144);
+
+                    return MemoryMarshal.CreateSpan(ref start, 144);
+                }
+            }
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            public Span<int> Get(int index)
+            {
+                ref int start = ref Unsafe.Add(ref _start, index * 144);
+
+                return MemoryMarshal.CreateSpan(ref start, 144);
+            }
+        }
+
         private static int Select2dPartition(int seed, int x, int y, int partitionCount, bool isSmallBlock)
         {
             return SelectPartition(seed, x, y, 0, partitionCount, isSmallBlock);
@@ -400,19 +603,19 @@ namespace Ryujinx.Graphics.Texture.Astc
 
             seed += (partitionCount - 1) * 1024;
 
-            int  rightNum = Hash52((uint)seed);
-            byte seed01   = (byte)(rightNum & 0xF);
-            byte seed02   = (byte)((rightNum >> 4) & 0xF);
-            byte seed03   = (byte)((rightNum >> 8) & 0xF);
-            byte seed04   = (byte)((rightNum >> 12) & 0xF);
-            byte seed05   = (byte)((rightNum >> 16) & 0xF);
-            byte seed06   = (byte)((rightNum >> 20) & 0xF);
-            byte seed07   = (byte)((rightNum >> 24) & 0xF);
-            byte seed08   = (byte)((rightNum >> 28) & 0xF);
-            byte seed09   = (byte)((rightNum >> 18) & 0xF);
-            byte seed10   = (byte)((rightNum >> 22) & 0xF);
-            byte seed11   = (byte)((rightNum >> 26) & 0xF);
-            byte seed12   = (byte)(((rightNum >> 30) | (rightNum << 2)) & 0xF);
+            int rightNum = Hash52((uint)seed);
+            byte seed01 = (byte)(rightNum & 0xF);
+            byte seed02 = (byte)((rightNum >> 4) & 0xF);
+            byte seed03 = (byte)((rightNum >> 8) & 0xF);
+            byte seed04 = (byte)((rightNum >> 12) & 0xF);
+            byte seed05 = (byte)((rightNum >> 16) & 0xF);
+            byte seed06 = (byte)((rightNum >> 20) & 0xF);
+            byte seed07 = (byte)((rightNum >> 24) & 0xF);
+            byte seed08 = (byte)((rightNum >> 28) & 0xF);
+            byte seed09 = (byte)((rightNum >> 18) & 0xF);
+            byte seed10 = (byte)((rightNum >> 22) & 0xF);
+            byte seed11 = (byte)((rightNum >> 26) & 0xF);
+            byte seed12 = (byte)(((rightNum >> 30) | (rightNum << 2)) & 0xF);
 
             seed01 *= seed01; seed02 *= seed02;
             seed03 *= seed03; seed04 *= seed04;
@@ -459,50 +662,56 @@ namespace Ryujinx.Graphics.Texture.Astc
         static int Hash52(uint val)
         {
             val ^= val >> 15; val -= val << 17; val += val << 7; val += val << 4;
-            val ^= val >> 5;  val += val << 16; val ^= val >> 7; val ^= val >> 3;
-            val ^= val << 6;  val ^= val >> 17;
+            val ^= val >> 5; val += val << 16; val ^= val >> 7; val ^= val >> 3;
+            val ^= val << 6; val ^= val >> 17;
 
             return (int)val;
         }
 
         static void UnquantizeTexelWeights(
-            int[][]              outputBuffer,
-            List<IntegerEncoded> weights,
-            TexelWeightParams    texelParams,
-            int                  blockWidth,
-            int                  blockHeight)
+            ref Weights outputBuffer,
+            ref IntegerSequence weights,
+            ref TexelWeightParams texelParams,
+            int blockWidth,
+            int blockHeight)
         {
-            int weightIndices   = 0;
-            int[][] unquantized = new int[2][];
-            unquantized[0]      = new int[144];
-            unquantized[1]      = new int[144];
+            int weightIndices = 0;
+            Weights unquantized;
+            unsafe { _ = &unquantized; } // Skip struct initialization
 
-            for (int i = 0; i < weights.Count; i++)
+            Span<IntegerEncoded> weightsList = weights.List;
+            Span<int> unquantized0 = unquantized[0];
+            Span<int> unquantized1 = unquantized[1];
+
+            for (int i = 0; i < weightsList.Length; i++)
             {
-                unquantized[0][weightIndices] = UnquantizeTexelWeight(weights[i]);
+                unquantized0[weightIndices] = UnquantizeTexelWeight(weightsList[i]);
 
                 if (texelParams.DualPlane)
                 {
                     i++;
-                    unquantized[1][weightIndices] = UnquantizeTexelWeight(weights[i]);
+                    unquantized1[weightIndices] = UnquantizeTexelWeight(weightsList[i]);
 
-                    if (i == weights.Count)
+                    if (i == weightsList.Length)
                     {
                         break;
                     }
                 }
 
-                if (++weightIndices >= (texelParams.Width * texelParams.Height)) break;
+                if (++weightIndices >= texelParams.Width * texelParams.Height) break;
             }
 
             // Do infill if necessary (Section C.2.18) ...
-            int ds = (1024 + (blockWidth / 2)) / (blockWidth - 1);
-            int dt = (1024 + (blockHeight / 2)) / (blockHeight - 1);
+            int ds = (1024 + blockWidth / 2) / (blockWidth - 1);
+            int dt = (1024 + blockHeight / 2) / (blockHeight - 1);
 
             int planeScale = texelParams.DualPlane ? 2 : 1;
 
             for (int plane = 0; plane < planeScale; plane++)
             {
+                Span<int> unquantizedSpan = unquantized.Get(plane);
+                Span<int> outputSpan = outputBuffer.Get(plane);
+
                 for (int t = 0; t < blockHeight; t++)
                 {
                     for (int s = 0; s < blockWidth; s++)
@@ -520,38 +729,34 @@ namespace Ryujinx.Graphics.Texture.Astc
                         int ft = gt & 0x0F;
 
                         int w11 = (fs * ft + 8) >> 4;
-                        int w10 = ft - w11;
-                        int w01 = fs - w11;
-                        int w00 = 16 - fs - ft + w11;
 
                         int v0 = js + jt * texelParams.Width;
 
-                        int p00 = 0;
-                        int p01 = 0;
-                        int p10 = 0;
-                        int p11 = 0;
+                        int weight = 8;
 
-                        if (v0 < (texelParams.Width * texelParams.Height))
+                        int wxh = texelParams.Width * texelParams.Height;
+
+                        if (v0 < wxh)
                         {
-                            p00 = unquantized[plane][v0];
+                            weight += unquantizedSpan[v0] * (16 - fs - ft + w11);
+
+                            if (v0 + 1 < wxh)
+                            {
+                                weight += unquantizedSpan[v0 + 1] * (fs - w11);
+                            }
                         }
 
-                        if (v0 + 1 < (texelParams.Width * texelParams.Height))
+                        if (v0 + texelParams.Width < wxh)
                         {
-                            p01 = unquantized[plane][v0 + 1];
+                            weight += unquantizedSpan[v0 + texelParams.Width] * (ft - w11);
+
+                            if (v0 + texelParams.Width + 1 < wxh)
+                            {
+                                weight += unquantizedSpan[v0 + texelParams.Width + 1] * w11;
+                            }
                         }
 
-                        if (v0 + texelParams.Width < (texelParams.Width * texelParams.Height))
-                        {
-                            p10 = unquantized[plane][v0 + texelParams.Width];
-                        }
-
-                        if (v0 + texelParams.Width + 1 < (texelParams.Width * texelParams.Height))
-                        {
-                            p11 = unquantized[plane][v0 + texelParams.Width + 1];
-                        }
-
-                        outputBuffer[plane][t * blockWidth + s] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4;
+                        outputSpan[t * blockWidth + s] = weight >> 4;
                     }
                 }
             }
@@ -559,10 +764,10 @@ namespace Ryujinx.Graphics.Texture.Astc
 
         static int UnquantizeTexelWeight(IntegerEncoded intEncoded)
         {
-            int bitValue  = intEncoded.BitValue;
+            int bitValue = intEncoded.BitValue;
             int bitLength = intEncoded.NumberBits;
 
-            int a = BitArrayStream.Replicate(bitValue & 1, 1, 7);
+            int a = Bits.Replicate1_7(bitValue & 1);
             int b = 0, c = 0, d = 0;
 
             int result = 0;
@@ -570,7 +775,7 @@ namespace Ryujinx.Graphics.Texture.Astc
             switch (intEncoded.GetEncoding())
             {
                 case IntegerEncoded.EIntegerEncoding.JustBits:
-                    result = BitArrayStream.Replicate(bitValue, bitLength, 6);
+                    result = Bits.Replicate(bitValue, bitLength, 6);
                     break;
 
                 case IntegerEncoded.EIntegerEncoding.Trit:
@@ -582,8 +787,13 @@ namespace Ryujinx.Graphics.Texture.Astc
                     {
                         case 0:
                         {
-                            int[] results = { 0, 32, 63 };
-                            result = results[d];
+                            result = d switch
+                            {
+                                0 => 0,
+                                1 => 32,
+                                2 => 63,
+                                _ => 0
+                            };
 
                             break;
                         }
@@ -628,8 +838,15 @@ namespace Ryujinx.Graphics.Texture.Astc
                     {
                         case 0:
                         {
-                            int[] results = { 0, 16, 32, 47, 63 };
-                            result = results[d];
+                            result = d switch
+                            {
+                                0 => 0,
+                                1 => 16,
+                                2 => 32,
+                                3 => 47,
+                                4 => 63,
+                                _ => 0
+                            };
 
                             break;
                         }
@@ -661,9 +878,9 @@ namespace Ryujinx.Graphics.Texture.Astc
             if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits && bitLength > 0)
             {
                 // Decode the value...
-                result  = d * c + b;
+                result = d * c + b;
                 result ^= a;
-                result  = (a & 0x20) | (result >> 2);
+                result = (a & 0x20) | (result >> 2);
             }
 
             Debug.Assert(result < 64);
@@ -683,41 +900,35 @@ namespace Ryujinx.Graphics.Texture.Astc
             return (byte)((((b) * 0x80200802L) & 0x0884422110L) * 0x0101010101L >> 32);
         }
 
-        static uint[] ReadUintColorValues(int number, int[] colorValues, ref int colorValuesPosition)
+        static Span<uint> ReadUintColorValues(int number, Span<int> colorValues, ref int colorValuesPosition)
         {
-            uint[] ret = new uint[number];
+            Span<int> ret = colorValues.Slice(colorValuesPosition, number);
 
-            for (int i = 0; i < number; i++)
-            {
-                ret[i] = (uint)colorValues[colorValuesPosition++];
-            }
+            colorValuesPosition += number;
 
-            return ret;
+            return MemoryMarshal.Cast<int, uint>(ret);
         }
 
-        static int[] ReadIntColorValues(int number, int[] colorValues, ref int colorValuesPosition)
+        static Span<int> ReadIntColorValues(int number, Span<int> colorValues, ref int colorValuesPosition)
         {
-            int[] ret = new int[number];
+            Span<int> ret = colorValues.Slice(colorValuesPosition, number);
 
-            for (int i = 0; i < number; i++)
-            {
-                ret[i] = colorValues[colorValuesPosition++];
-            }
+            colorValuesPosition += number;
 
             return ret;
         }
 
         static void ComputeEndpoints(
-            AstcPixel[] endPoints,
-            int[]       colorValues,
-            uint        colorEndpointMode,
-            ref int     colorValuesPosition)
+            Span<AstcPixel> endPoints,
+            Span<int> colorValues,
+            uint colorEndpointMode,
+            ref int colorValuesPosition)
         {
             switch (colorEndpointMode)
             {
                 case 0:
                 {
-                    uint[] val = ReadUintColorValues(2, colorValues, ref colorValuesPosition);
+                    Span<uint> val = ReadUintColorValues(2, colorValues, ref colorValuesPosition);
 
                     endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[0], (short)val[0]);
                     endPoints[1] = new AstcPixel(0xFF, (short)val[1], (short)val[1], (short)val[1]);
@@ -728,9 +939,9 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 1:
                 {
-                    uint[] val = ReadUintColorValues(2, colorValues, ref colorValuesPosition);
-                    int l0     = (int)((val[0] >> 2) | (val[1] & 0xC0));
-                    int l1     = (int)Math.Max(l0 + (val[1] & 0x3F), 0xFFU);
+                    Span<uint> val = ReadUintColorValues(2, colorValues, ref colorValuesPosition);
+                    int l0 = (int)((val[0] >> 2) | (val[1] & 0xC0));
+                    int l1 = (int)Math.Max(l0 + (val[1] & 0x3F), 0xFFU);
 
                     endPoints[0] = new AstcPixel(0xFF, (short)l0, (short)l0, (short)l0);
                     endPoints[1] = new AstcPixel(0xFF, (short)l1, (short)l1, (short)l1);
@@ -740,7 +951,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 4:
                 {
-                    uint[] val = ReadUintColorValues(4, colorValues, ref colorValuesPosition);
+                    Span<uint> val = ReadUintColorValues(4, colorValues, ref colorValuesPosition);
 
                     endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]);
                     endPoints[1] = new AstcPixel((short)val[3], (short)val[1], (short)val[1], (short)val[1]);
@@ -750,10 +961,10 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 5:
                 {
-                    int[] val = ReadIntColorValues(4, colorValues, ref colorValuesPosition);
+                    Span<int> val = ReadIntColorValues(4, colorValues, ref colorValuesPosition);
 
-                    BitArrayStream.BitTransferSigned(ref val[1], ref val[0]);
-                    BitArrayStream.BitTransferSigned(ref val[3], ref val[2]);
+                    Bits.BitTransferSigned(ref val[1], ref val[0]);
+                    Bits.BitTransferSigned(ref val[3], ref val[2]);
 
                     endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]);
                     endPoints[1] = new AstcPixel((short)(val[2] + val[3]), (short)(val[0] + val[1]), (short)(val[0] + val[1]), (short)(val[0] + val[1]));
@@ -766,7 +977,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 6:
                 {
-                    uint[] val = ReadUintColorValues(4, colorValues, ref colorValuesPosition);
+                    Span<uint> val = ReadUintColorValues(4, colorValues, ref colorValuesPosition);
 
                     endPoints[0] = new AstcPixel(0xFF, (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8));
                     endPoints[1] = new AstcPixel(0xFF, (short)val[0], (short)val[1], (short)val[2]);
@@ -776,7 +987,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 8:
                 {
-                    uint[] val = ReadUintColorValues(6, colorValues, ref colorValuesPosition);
+                    Span<uint> val = ReadUintColorValues(6, colorValues, ref colorValuesPosition);
 
                     if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4])
                     {
@@ -794,11 +1005,11 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 9:
                 {
-                    int[] val = ReadIntColorValues(6, colorValues, ref colorValuesPosition);
+                    Span<int> val = ReadIntColorValues(6, colorValues, ref colorValuesPosition);
 
-                    BitArrayStream.BitTransferSigned(ref val[1], ref val[0]);
-                    BitArrayStream.BitTransferSigned(ref val[3], ref val[2]);
-                    BitArrayStream.BitTransferSigned(ref val[5], ref val[4]);
+                    Bits.BitTransferSigned(ref val[1], ref val[0]);
+                    Bits.BitTransferSigned(ref val[3], ref val[2]);
+                    Bits.BitTransferSigned(ref val[5], ref val[4]);
 
                     if (val[1] + val[3] + val[5] >= 0)
                     {
@@ -819,7 +1030,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 10:
                 {
-                    uint[] val = ReadUintColorValues(6, colorValues, ref colorValuesPosition);
+                    Span<uint> val = ReadUintColorValues(6, colorValues, ref colorValuesPosition);
 
                     endPoints[0] = new AstcPixel((short)val[4], (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8));
                     endPoints[1] = new AstcPixel((short)val[5], (short)val[0], (short)val[1], (short)val[2]);
@@ -829,7 +1040,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 12:
                 {
-                    uint[] val = ReadUintColorValues(8, colorValues, ref colorValuesPosition);
+                    Span<uint> val = ReadUintColorValues(8, colorValues, ref colorValuesPosition);
 
                     if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4])
                     {
@@ -847,12 +1058,12 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 13:
                 {
-                    int[] val = ReadIntColorValues(8, colorValues, ref colorValuesPosition);
+                    Span<int> val = ReadIntColorValues(8, colorValues, ref colorValuesPosition);
 
-                    BitArrayStream.BitTransferSigned(ref val[1], ref val[0]);
-                    BitArrayStream.BitTransferSigned(ref val[3], ref val[2]);
-                    BitArrayStream.BitTransferSigned(ref val[5], ref val[4]);
-                    BitArrayStream.BitTransferSigned(ref val[7], ref val[6]);
+                    Bits.BitTransferSigned(ref val[1], ref val[0]);
+                    Bits.BitTransferSigned(ref val[3], ref val[2]);
+                    Bits.BitTransferSigned(ref val[5], ref val[4]);
+                    Bits.BitTransferSigned(ref val[7], ref val[6]);
 
                     if (val[1] + val[3] + val[5] >= 0)
                     {
@@ -877,11 +1088,11 @@ namespace Ryujinx.Graphics.Texture.Astc
         }
 
         static void DecodeColorValues(
-            int[]  outputValues,
-            byte[] inputData,
-            uint[] modes,
-            int    numberPartitions,
-            int    numberBitsForColorData)
+            Span<int> outputValues,
+            ref BitStream128 colorBitStream,
+            Span<uint> modes,
+            int numberPartitions,
+            int numberBitsForColorData)
         {
             // First figure out how many color values we have
             int numberValues = 0;
@@ -898,7 +1109,7 @@ namespace Ryujinx.Graphics.Texture.Astc
             while (--range > 0)
             {
                 IntegerEncoded intEncoded = IntegerEncoded.CreateEncoding(range);
-                int bitLength             = intEncoded.GetBitLength(numberValues);
+                int bitLength = intEncoded.GetBitLength(numberValues);
 
                 if (bitLength <= numberBitsForColorData)
                 {
@@ -919,31 +1130,32 @@ namespace Ryujinx.Graphics.Texture.Astc
             }
 
             // We now have enough to decode our integer sequence.
-            List<IntegerEncoded> integerEncodedSequence = new List<IntegerEncoded>();
-            BitArrayStream colorBitStream               = new BitArrayStream(new BitArray(inputData));
+            IntegerSequence integerEncodedSequence;
+            unsafe { _ = &integerEncodedSequence; } // Skip struct initialization
+            integerEncodedSequence.Reset();
 
-            IntegerEncoded.DecodeIntegerSequence(integerEncodedSequence, colorBitStream, range, numberValues);
+            IntegerEncoded.DecodeIntegerSequence(ref integerEncodedSequence, ref colorBitStream, range, numberValues);
 
             // Once we have the decoded values, we need to dequantize them to the 0-255 range
             // This procedure is outlined in ASTC spec C.2.13
             int outputIndices = 0;
 
-            foreach (IntegerEncoded intEncoded in integerEncodedSequence)
+            foreach (ref IntegerEncoded intEncoded in integerEncodedSequence.List)
             {
                 int bitLength = intEncoded.NumberBits;
-                int bitValue  = intEncoded.BitValue;
+                int bitValue = intEncoded.BitValue;
 
                 Debug.Assert(bitLength >= 1);
 
                 int a = 0, b = 0, c = 0, d = 0;
                 // A is just the lsb replicated 9 times.
-                a = BitArrayStream.Replicate(bitValue & 1, 1, 9);
+                a = Bits.Replicate(bitValue & 1, 1, 9);
 
                 switch (intEncoded.GetEncoding())
                 {
                     case IntegerEncoded.EIntegerEncoding.JustBits:
                     {
-                        outputValues[outputIndices++] = BitArrayStream.Replicate(bitValue, bitLength, 8);
+                        outputValues[outputIndices++] = Bits.Replicate(bitValue, bitLength, 8);
 
                         break;
                     }
@@ -1082,8 +1294,8 @@ namespace Ryujinx.Graphics.Texture.Astc
                 if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits)
                 {
                     int T = d * c + b;
-                    T    ^= a;
-                    T     = (a & 0x80) | (T >> 2);
+                    T ^= a;
+                    T = (a & 0x80) | (T >> 2);
 
                     outputValues[outputIndices++] = T;
                 }
@@ -1096,7 +1308,7 @@ namespace Ryujinx.Graphics.Texture.Astc
             }
         }
 
-        static void FillVoidExtentLdr(BitArrayStream bitStream, int[] outputBuffer, int blockWidth, int blockHeight)
+        static void FillVoidExtentLdr(ref BitStream128 bitStream, Span<int> outputBuffer, int blockWidth, int blockHeight)
         {
             // Don't actually care about the void extent, just read the bits...
             for (int i = 0; i < 4; ++i)
@@ -1121,9 +1333,9 @@ namespace Ryujinx.Graphics.Texture.Astc
             }
         }
 
-        static TexelWeightParams DecodeBlockInfo(BitArrayStream bitStream)
+        static void DecodeBlockInfo(ref BitStream128 bitStream, out TexelWeightParams texelParams)
         {
-            TexelWeightParams texelParams = new TexelWeightParams();
+            texelParams = new TexelWeightParams();
 
             // Read the entire block mode all at once
             ushort modeBits = (ushort)bitStream.ReadBits(11);
@@ -1146,14 +1358,15 @@ namespace Ryujinx.Graphics.Texture.Astc
                     texelParams.Error = true;
                 }
 
-                return texelParams;
+                return;
             }
 
             // First check if the last four bits are zero
             if ((modeBits & 0xF) == 0)
             {
                 texelParams.Error = true;
-                return texelParams;
+
+                return;
             }
 
             // If the last two bits are zero, then if bits
@@ -1162,14 +1375,14 @@ namespace Ryujinx.Graphics.Texture.Astc
             {
                 texelParams.Error = true;
 
-                return texelParams;
+                return;
             }
 
             // Otherwise, there is no error... Figure out the layout
             // of the block mode. Layout is determined by a number
             // between 0 and 9 corresponding to table C.2.8 of the
             // ASTC spec.
-            int layout = 0;
+            int layout;
 
             if ((modeBits & 0x1) != 0 || (modeBits & 0x2) != 0)
             {
@@ -1269,7 +1482,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int a = (modeBits >> 5) & 0x3;
                     int b = (modeBits >> 7) & 0x3;
 
-                    texelParams.Width  = b + 4;
+                    texelParams.Width = b + 4;
                     texelParams.Height = a + 2;
 
                     break;
@@ -1280,7 +1493,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int a = (modeBits >> 5) & 0x3;
                     int b = (modeBits >> 7) & 0x3;
 
-                    texelParams.Width  = b + 8;
+                    texelParams.Width = b + 8;
                     texelParams.Height = a + 2;
 
                     break;
@@ -1291,7 +1504,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int a = (modeBits >> 5) & 0x3;
                     int b = (modeBits >> 7) & 0x3;
 
-                    texelParams.Width  = a + 2;
+                    texelParams.Width = a + 2;
                     texelParams.Height = b + 8;
 
                     break;
@@ -1302,7 +1515,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int a = (modeBits >> 5) & 0x3;
                     int b = (modeBits >> 7) & 0x1;
 
-                    texelParams.Width  = a + 2;
+                    texelParams.Width = a + 2;
                     texelParams.Height = b + 6;
 
                     break;
@@ -1313,7 +1526,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int a = (modeBits >> 5) & 0x3;
                     int b = (modeBits >> 7) & 0x1;
 
-                    texelParams.Width  = b + 2;
+                    texelParams.Width = b + 2;
                     texelParams.Height = a + 2;
 
                     break;
@@ -1323,7 +1536,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                 {
                     int a = (modeBits >> 5) & 0x3;
 
-                    texelParams.Width  = 12;
+                    texelParams.Width = 12;
                     texelParams.Height = a + 2;
 
                     break;
@@ -1333,7 +1546,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                 {
                     int a = (modeBits >> 5) & 0x3;
 
-                    texelParams.Width  = a + 2;
+                    texelParams.Width = a + 2;
                     texelParams.Height = 12;
 
                     break;
@@ -1341,7 +1554,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 7:
                 {
-                    texelParams.Width  = 6;
+                    texelParams.Width = 6;
                     texelParams.Height = 10;
 
                     break;
@@ -1349,7 +1562,7 @@ namespace Ryujinx.Graphics.Texture.Astc
 
                 case 8:
                 {
-                    texelParams.Width  = 10;
+                    texelParams.Width = 10;
                     texelParams.Height = 6;
                     break;
                 }
@@ -1359,7 +1572,7 @@ namespace Ryujinx.Graphics.Texture.Astc
                     int a = (modeBits >> 5) & 0x3;
                     int b = (modeBits >> 9) & 0x3;
 
-                    texelParams.Width  = a + 6;
+                    texelParams.Width = a + 6;
                     texelParams.Height = b + 6;
 
                     break;
@@ -1378,18 +1591,16 @@ namespace Ryujinx.Graphics.Texture.Astc
 
             if (h)
             {
-                int[] maxWeights = { 9, 11, 15, 19, 23, 31 };
+                ReadOnlySpan<byte> maxWeights = new byte[] { 9, 11, 15, 19, 23, 31 };
                 texelParams.MaxWeight = maxWeights[r - 2];
             }
             else
             {
-                int[] maxWeights = { 1, 2, 3, 4, 5, 7 };
+                ReadOnlySpan<byte> maxWeights = new byte[] { 1, 2, 3, 4, 5, 7 };
                 texelParams.MaxWeight = maxWeights[r - 2];
             }
 
             texelParams.DualPlane = d;
-
-            return texelParams;
         }
     }
 }
diff --git a/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs b/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs
index 7d12787823..13197714e4 100644
--- a/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs
+++ b/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
diff --git a/Ryujinx.Graphics.Texture/Astc/BitArrayStream.cs b/Ryujinx.Graphics.Texture/Astc/BitArrayStream.cs
deleted file mode 100644
index 3c472a3c16..0000000000
--- a/Ryujinx.Graphics.Texture/Astc/BitArrayStream.cs
+++ /dev/null
@@ -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;
-        }
-    }
-}
diff --git a/Ryujinx.Graphics.Texture/Astc/BitStream128.cs b/Ryujinx.Graphics.Texture/Astc/BitStream128.cs
new file mode 100644
index 0000000000..3bf9769f53
--- /dev/null
+++ b/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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Texture/Astc/Bits.cs b/Ryujinx.Graphics.Texture/Astc/Bits.cs
new file mode 100644
index 0000000000..b140a20a02
--- /dev/null
+++ b/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;
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs b/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs
new file mode 100644
index 0000000000..45e61ca2ca
--- /dev/null
+++ b/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);
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs b/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs
index 5aa610e742..065de46be9 100644
--- a/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs
+++ b/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;
+            encoded |= bitStream.ReadBits(1) << 7;
 
-            int c = 0;
+            ReadOnlySpan<byte> encodings = GetTritEncoding(encoded);
 
-            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);
-                }
-            }
-
-            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)
         {
+            ReadOnlySpan<byte> interleavedBits = new byte[] { 3, 2, 2 };
+
             // Implement the algorithm in section C.2.12
-            int[] m = new int[3];
-            int[] qa = new int[3];
-            int q;
+            Span<int> m = stackalloc int[3];
+            ulong encoded = 0;
+            int encodedBitsRead = 0;
 
-            // 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;
-
-            BitArrayStream qb = new BitArrayStream(new BitArray(new int[] { q }));
-            if (qb.ReadBits(1, 2) == 3 && qb.ReadBits(5, 6) == 0)
+            for (int i = 0; i < m.Length; i++)
             {
-                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
-            {
-                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
+        };
     }
 }
diff --git a/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs b/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs
new file mode 100644
index 0000000000..367b68095e
--- /dev/null
+++ b/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;
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj b/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
index f446f16ccb..6ec98a8d29 100644
--- a/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
+++ b/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
@@ -6,6 +6,7 @@
 
   <PropertyGroup>
     <TargetFramework>netcoreapp3.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
 </Project>