Browse Source

Port common utils and alpha decoder

pull/2933/head
James Jackson-South 1 year ago
parent
commit
1a91ec9d86
  1. 64
      src/ImageSharp/Common/Helpers/Vector128Utilities.cs
  2. 43
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  3. 7
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  4. 86
      src/ImageSharp/Formats/Webp/WebpCommonUtils.cs

64
src/ImageSharp/Common/Helpers/Vector128Utilities.cs

@ -1300,4 +1300,68 @@ internal static class Vector128_
// Narrow back to signed bytes // Narrow back to signed bytes
return Vector128.Narrow(diffLo, diffHi); return Vector128.Narrow(diffLo, diffHi);
} }
/// <summary>
/// Create mask from the most significant bit of each 8-bit element in <paramref name="value"/>, and store the result.
/// </summary>
/// <param name="value">
/// The vector containing packed 8-bit integers from which to create the mask.
/// </param>
/// <returns>
/// A 16-bit integer mask where each bit corresponds to the most significant bit of each 8-bit element
/// in <paramref name="value"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MoveMask(Vector128<byte> value)
{
if (Sse2.IsSupported)
{
return Sse2.MoveMask(value);
}
if (AdvSimd.IsSupported)
{
// https://stackoverflow.com/questions/11870910/sse-mm-movemask-epi8-equivalent-method-for-arm-neon
Vector128<byte> powers = Vector128.Create(1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128);
Vector128<byte> masked = value & powers;
Vector128<ushort> sum8 = AdvSimd.AddPairwiseWidening(masked);
Vector128<uint> sum16 = AdvSimd.AddPairwiseWidening(sum8);
Vector128<ulong> sum32 = AdvSimd.AddPairwiseWidening(sum16);
// Extract lower 8 bits of each 64-bit lane
byte lo = sum32.AsByte().GetElement(0);
byte hi = sum32.AsByte().GetElement(8);
return (hi << 8) | lo;
}
{
// Step 1: isolate MSBs
Vector128<byte> msbMask = Vector128.Create((byte)0x80);
Vector128<byte> masked = value & msbMask;
// Step 2: shift each byte so MSB lands in bit position [0..15]
// i.e. convert: 0x80 → 1 << i
Vector128<ushort> bitShifts = Vector128.Create((ushort)1, 2, 4, 8, 16, 32, 64, 128);
Vector128<ushort> bitShiftsHigh = Vector128.Create(256, 512, 1024, 2048, 4096, 8192, 16384, 32768);
// Step 3: widen to ushort
(Vector128<ushort> lo, Vector128<ushort> hi) = Vector128.Widen(masked);
// Step 4: compare > 0 to get 0xFFFF where MSB was set
lo = Vector128.ConditionalSelect(Vector128.Equals(lo, Vector128<ushort>.Zero), Vector128<ushort>.Zero, bitShifts);
hi = Vector128.ConditionalSelect(Vector128.Equals(hi, Vector128<ushort>.Zero), Vector128<ushort>.Zero, bitShiftsHigh);
// Step 5: bitwise OR the two halves
Vector128<ushort> maskVector = lo | hi;
// Step 6: horizontal OR reduction via shuffles
maskVector |= Vector128.Shuffle(maskVector, Vector128.Create((ushort)4, 5, 6, 7, 0, 1, 2, 3));
maskVector |= Vector128.Shuffle(maskVector, Vector128.Create((ushort)2, 3, 0, 1, 6, 7, 4, 5));
maskVector |= Vector128.Shuffle(maskVector, Vector128.Create((ushort)1, 0, 3, 2, 5, 4, 7, 6));
return maskVector.ToScalar();
}
}
} }

43
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -231,6 +231,27 @@ internal static class Vector256_
return Vector256.Narrow(lefClamped, rightClamped); return Vector256.Narrow(lefClamped, rightClamped);
} }
/// <summary>
/// Packs signed 16-bit integers to signed 8-bit integers and saturates.
/// </summary>
/// <param name="left">The left hand source vector.</param>
/// <param name="right">The right hand source vector.</param>
/// <returns>The <see cref="Vector256{SByte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<sbyte> PackSignedSaturate(Vector256<short> left, Vector256<short> right)
{
if (Avx2.IsSupported)
{
return Avx2.PackSignedSaturate(left, right);
}
Vector256<short> min = Vector256.Create((short)sbyte.MinValue);
Vector256<short> max = Vector256.Create((short)sbyte.MaxValue);
Vector256<short> lefClamped = Clamp(left, min, max);
Vector256<short> rightClamped = Clamp(right, min, max);
return Vector256.Narrow(lefClamped, rightClamped);
}
/// <summary> /// <summary>
/// Restricts a vector between a minimum and a maximum value. /// Restricts a vector between a minimum and a maximum value.
/// </summary> /// </summary>
@ -466,6 +487,28 @@ internal static class Vector256_
Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper())); Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper()));
} }
/// <summary>
/// Create mask from the most significant bit of each 8-bit element in <paramref name="value"/>, and store the result.
/// </summary>
/// <param name="value">
/// The vector containing packed 8-bit integers from which to create the mask.
/// </param>
/// <returns>
/// A 16-bit integer mask where each bit corresponds to the most significant bit of each 8-bit element
/// in <paramref name="value"/>.
/// </returns>
public static int MoveMask(Vector256<byte> value)
{
if (Avx2.IsSupported)
{
return Avx2.MoveMask(value);
}
int loMask = Vector128_.MoveMask(value.GetLower());
int hiMask = Vector128_.MoveMask(value.GetUpper());
return loMask | (hiMask << 16);
}
[DoesNotReturn] [DoesNotReturn]
private static void ThrowUnreachableException() => throw new UnreachableException(); private static void ThrowUnreachableException() => throw new UnreachableException();
} }

7
src/ImageSharp/Formats/Webp/AlphaDecoder.cs

@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.BitReader;
@ -314,7 +313,7 @@ internal class AlphaDecoder : IDisposable
private static void HorizontalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width) private static void HorizontalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
{ {
if ((Sse2.IsSupported || AdvSimd.IsSupported) && width >= 9) if (Vector128.IsHardwareAccelerated && width >= 9)
{ {
dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0])); dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0]));
nuint i; nuint i;
@ -362,7 +361,7 @@ internal class AlphaDecoder : IDisposable
{ {
HorizontalUnfilter(null, input, dst, width); HorizontalUnfilter(null, input, dst, width);
} }
else if (Avx2.IsSupported) else if (Vector256.IsHardwareAccelerated)
{ {
ref byte inputRef = ref MemoryMarshal.GetReference(input); ref byte inputRef = ref MemoryMarshal.GetReference(input);
ref byte prevRef = ref MemoryMarshal.GetReference(prev); ref byte prevRef = ref MemoryMarshal.GetReference(prev);
@ -374,7 +373,7 @@ internal class AlphaDecoder : IDisposable
{ {
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref inputRef, i)); Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref inputRef, i));
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref prevRef, i)); Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref prevRef, i));
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte()); Vector256<byte> c0 = a0.AsByte() + b0.AsByte();
ref byte outputRef = ref Unsafe.Add(ref dstRef, i); ref byte outputRef = ref Unsafe.Add(ref dstRef, i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0; Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
} }

86
src/ImageSharp/Formats/Webp/WebpCommonUtils.cs

@ -3,7 +3,7 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp; namespace SixLabors.ImageSharp.Formats.Webp;
@ -20,7 +20,7 @@ internal static class WebpCommonUtils
/// <returns>Returns true if alpha has non-0xff values.</returns> /// <returns>Returns true if alpha has non-0xff values.</returns>
public static unsafe bool CheckNonOpaque(ReadOnlySpan<Bgra32> row) public static unsafe bool CheckNonOpaque(ReadOnlySpan<Bgra32> row)
{ {
if (Avx2.IsSupported) if (Vector256.IsHardwareAccelerated)
{ {
ReadOnlySpan<byte> rowBytes = MemoryMarshal.AsBytes(row); ReadOnlySpan<byte> rowBytes = MemoryMarshal.AsBytes(row);
int i = 0; int i = 0;
@ -32,19 +32,19 @@ internal static class WebpCommonUtils
for (; i + 128 <= length; i += 128) for (; i + 128 <= length; i += 128)
{ {
Vector256<byte> a0 = Avx.LoadVector256(src + i).AsByte(); Vector256<byte> a0 = Vector256.Load(src + i).AsByte();
Vector256<byte> a1 = Avx.LoadVector256(src + i + 32).AsByte(); Vector256<byte> a1 = Vector256.Load(src + i + 32).AsByte();
Vector256<byte> a2 = Avx.LoadVector256(src + i + 64).AsByte(); Vector256<byte> a2 = Vector256.Load(src + i + 64).AsByte();
Vector256<byte> a3 = Avx.LoadVector256(src + i + 96).AsByte(); Vector256<byte> a3 = Vector256.Load(src + i + 96).AsByte();
Vector256<int> b0 = Avx2.And(a0, alphaMaskVector256).AsInt32(); Vector256<int> b0 = (a0 & alphaMaskVector256).AsInt32();
Vector256<int> b1 = Avx2.And(a1, alphaMaskVector256).AsInt32(); Vector256<int> b1 = (a1 & alphaMaskVector256).AsInt32();
Vector256<int> b2 = Avx2.And(a2, alphaMaskVector256).AsInt32(); Vector256<int> b2 = (a2 & alphaMaskVector256).AsInt32();
Vector256<int> b3 = Avx2.And(a3, alphaMaskVector256).AsInt32(); Vector256<int> b3 = (a3 & alphaMaskVector256).AsInt32();
Vector256<short> c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); Vector256<short> c0 = Vector256_.PackSignedSaturate(b0, b1).AsInt16();
Vector256<short> c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); Vector256<short> c1 = Vector256_.PackSignedSaturate(b2, b3).AsInt16();
Vector256<byte> d = Avx2.PackSignedSaturate(c0, c1).AsByte(); Vector256<byte> d = Vector256_.PackSignedSaturate(c0, c1).AsByte();
Vector256<byte> bits = Avx2.CompareEqual(d, all0x80Vector256); Vector256<byte> bits = Vector256.Equals(d, all0x80Vector256);
int mask = Avx2.MoveMask(bits); int mask = Vector256_.MoveMask(bits);
if (mask != -1) if (mask != -1)
{ {
return true; return true;
@ -53,7 +53,7 @@ internal static class WebpCommonUtils
for (; i + 64 <= length; i += 64) for (; i + 64 <= length; i += 64)
{ {
if (IsNoneOpaque64Bytes(src, i)) if (IsNoneOpaque64BytesVector128(src, i))
{ {
return true; return true;
} }
@ -61,7 +61,7 @@ internal static class WebpCommonUtils
for (; i + 32 <= length; i += 32) for (; i + 32 <= length; i += 32)
{ {
if (IsNoneOpaque32Bytes(src, i)) if (IsNonOpaque32BytesVector128(src, i))
{ {
return true; return true;
} }
@ -76,7 +76,7 @@ internal static class WebpCommonUtils
} }
} }
} }
else if (Sse2.IsSupported) else if (Vector128.IsHardwareAccelerated)
{ {
ReadOnlySpan<byte> rowBytes = MemoryMarshal.AsBytes(row); ReadOnlySpan<byte> rowBytes = MemoryMarshal.AsBytes(row);
int i = 0; int i = 0;
@ -85,7 +85,7 @@ internal static class WebpCommonUtils
{ {
for (; i + 64 <= length; i += 64) for (; i + 64 <= length; i += 64)
{ {
if (IsNoneOpaque64Bytes(src, i)) if (IsNoneOpaque64BytesVector128(src, i))
{ {
return true; return true;
} }
@ -93,7 +93,7 @@ internal static class WebpCommonUtils
for (; i + 32 <= length; i += 32) for (; i + 32 <= length; i += 32)
{ {
if (IsNoneOpaque32Bytes(src, i)) if (IsNonOpaque32BytesVector128(src, i))
{ {
return true; return true;
} }
@ -122,38 +122,38 @@ internal static class WebpCommonUtils
return false; return false;
} }
private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i) private static unsafe bool IsNoneOpaque64BytesVector128(byte* src, int i)
{ {
Vector128<byte> alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); Vector128<byte> alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
Vector128<byte> a0 = Sse2.LoadVector128(src + i).AsByte(); Vector128<byte> a0 = Vector128.Load(src + i).AsByte();
Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte(); Vector128<byte> a1 = Vector128.Load(src + i + 16).AsByte();
Vector128<byte> a2 = Sse2.LoadVector128(src + i + 32).AsByte(); Vector128<byte> a2 = Vector128.Load(src + i + 32).AsByte();
Vector128<byte> a3 = Sse2.LoadVector128(src + i + 48).AsByte(); Vector128<byte> a3 = Vector128.Load(src + i + 48).AsByte();
Vector128<int> b0 = Sse2.And(a0, alphaMask).AsInt32(); Vector128<int> b0 = (a0 & alphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, alphaMask).AsInt32(); Vector128<int> b1 = (a1 & alphaMask).AsInt32();
Vector128<int> b2 = Sse2.And(a2, alphaMask).AsInt32(); Vector128<int> b2 = (a2 & alphaMask).AsInt32();
Vector128<int> b3 = Sse2.And(a3, alphaMask).AsInt32(); Vector128<int> b3 = (a3 & alphaMask).AsInt32();
Vector128<short> c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); Vector128<short> c0 = Vector128_.PackSignedSaturate(b0, b1).AsInt16();
Vector128<short> c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); Vector128<short> c1 = Vector128_.PackSignedSaturate(b2, b3).AsInt16();
Vector128<byte> d = Sse2.PackSignedSaturate(c0, c1).AsByte(); Vector128<byte> d = Vector128_.PackSignedSaturate(c0, c1).AsByte();
Vector128<byte> bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte()); Vector128<byte> bits = Vector128.Equals(d, Vector128.Create((byte)0x80).AsByte());
int mask = Sse2.MoveMask(bits); int mask = Vector128_.MoveMask(bits);
return mask != 0xFFFF; return mask != 0xFFFF;
} }
private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i) private static unsafe bool IsNonOpaque32BytesVector128(byte* src, int i)
{ {
Vector128<byte> alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); Vector128<byte> alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);
Vector128<byte> a0 = Sse2.LoadVector128(src + i).AsByte(); Vector128<byte> a0 = Vector128.Load(src + i).AsByte();
Vector128<byte> a1 = Sse2.LoadVector128(src + i + 16).AsByte(); Vector128<byte> a1 = Vector128.Load(src + i + 16).AsByte();
Vector128<int> b0 = Sse2.And(a0, alphaMask).AsInt32(); Vector128<int> b0 = (a0 & alphaMask).AsInt32();
Vector128<int> b1 = Sse2.And(a1, alphaMask).AsInt32(); Vector128<int> b1 = (a1 & alphaMask).AsInt32();
Vector128<short> c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); Vector128<short> c = Vector128_.PackSignedSaturate(b0, b1).AsInt16();
Vector128<byte> d = Sse2.PackSignedSaturate(c, c).AsByte(); Vector128<byte> d = Vector128_.PackSignedSaturate(c, c).AsByte();
Vector128<byte> bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte()); Vector128<byte> bits = Vector128.Equals(d, Vector128.Create((byte)0x80).AsByte());
int mask = Sse2.MoveMask(bits); int mask = Vector128_.MoveMask(bits);
return mask != 0xFFFF; return mask != 0xFFFF;
} }
} }

Loading…
Cancel
Save