mirror of https://github.com/SixLabors/ImageSharp
10 changed files with 1642 additions and 382 deletions
@ -0,0 +1,524 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.Arm; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png.Filters; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the scalar and SIMD mappings used by the shared PNG filter traversal.
|
||||
|
/// </summary>
|
||||
|
internal interface IPngFilterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the filter type written to the leading result byte.
|
||||
|
/// </summary>
|
||||
|
static abstract FilterType Type { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the predictor reads the left component.
|
||||
|
/// </summary>
|
||||
|
static abstract bool UsesLeft { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the predictor reads the above component.
|
||||
|
/// </summary>
|
||||
|
static abstract bool UsesAbove { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the predictor reads the upper-left component.
|
||||
|
/// </summary>
|
||||
|
static abstract bool UsesUpperLeft { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters one byte from its PNG neighborhood.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The component being filtered.</param>
|
||||
|
/// <param name="left">The corresponding component in the preceding pixel.</param>
|
||||
|
/// <param name="above">The corresponding component in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding component in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residual.</returns>
|
||||
|
static abstract byte Invoke(byte scan, byte left, byte above, byte upperLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters sixteen byte lanes from their PNG neighborhoods.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The components being filtered.</param>
|
||||
|
/// <param name="left">The corresponding components in the preceding pixels.</param>
|
||||
|
/// <param name="above">The corresponding components in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding components in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residuals.</returns>
|
||||
|
static abstract Vector128<byte> Invoke( |
||||
|
Vector128<byte> scan, |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters thirty-two byte lanes from their PNG neighborhoods.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The components being filtered.</param>
|
||||
|
/// <param name="left">The corresponding components in the preceding pixels.</param>
|
||||
|
/// <param name="above">The corresponding components in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding components in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residuals.</returns>
|
||||
|
static abstract Vector256<byte> Invoke( |
||||
|
Vector256<byte> scan, |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters sixty-four byte lanes from their PNG neighborhoods.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scan">The components being filtered.</param>
|
||||
|
/// <param name="left">The corresponding components in the preceding pixels.</param>
|
||||
|
/// <param name="above">The corresponding components in the preceding scanline.</param>
|
||||
|
/// <param name="upperLeft">The preceding components in the preceding scanline.</param>
|
||||
|
/// <returns>The filtered residuals.</returns>
|
||||
|
static abstract Vector512<byte> Invoke( |
||||
|
Vector512<byte> scan, |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the corresponding component in the preceding pixel.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct SubFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Sub; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - left); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke( |
||||
|
Vector128<byte> scan, |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft) |
||||
|
=> scan - left; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke( |
||||
|
Vector256<byte> scan, |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft) |
||||
|
=> scan - left; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke( |
||||
|
Vector512<byte> scan, |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft) |
||||
|
=> scan - left; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the component directly above it.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct UpFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Up; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - above); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke( |
||||
|
Vector128<byte> scan, |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft) |
||||
|
=> scan - above; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke( |
||||
|
Vector256<byte> scan, |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft) |
||||
|
=> scan - above; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke( |
||||
|
Vector512<byte> scan, |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft) |
||||
|
=> scan - above; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the truncated average of its left and above neighbors.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct AverageFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Average; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => false; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) |
||||
|
=> (byte)(scan - ((left + above) >> 1)); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke( |
||||
|
Vector128<byte> scan, |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft) |
||||
|
{ |
||||
|
Vector128<byte> average; |
||||
|
|
||||
|
if (Sse2.IsSupported) |
||||
|
{ |
||||
|
// PAVG rounds upward. Complementing both inputs and the result converts
|
||||
|
// that rounding into the floor((left + above) / 2) required by PNG.
|
||||
|
average = ~Sse2.Average(~left, ~above); |
||||
|
} |
||||
|
else if (AdvSimd.IsSupported) |
||||
|
{ |
||||
|
// ARM's halving add truncates directly and therefore needs no correction.
|
||||
|
average = AdvSimd.FusedAddHalving(left, above); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Portable 128-bit backends use the carry-free average identity. Shared
|
||||
|
// bits supply the integer part while differing bits supply half the remainder.
|
||||
|
average = (left & above) + Vector128.ShiftRightLogical(left ^ above, 1); |
||||
|
} |
||||
|
|
||||
|
return scan - average; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke( |
||||
|
Vector256<byte> scan, |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft) |
||||
|
=> scan - ~Avx2.Average(~left, ~above); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke( |
||||
|
Vector512<byte> scan, |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft) |
||||
|
=> scan - ~Avx512BW.Average(~left, ~above); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps each component to its difference from the nearest Paeth neighbor.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct PaethFilterOperator : IPngFilterOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public static FilterType Type => FilterType.Paeth; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesAbove => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public static bool UsesUpperLeft => true; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) |
||||
|
{ |
||||
|
int p = left + above - upperLeft; |
||||
|
int distanceLeft = Numerics.Abs(p - left); |
||||
|
int distanceAbove = Numerics.Abs(p - above); |
||||
|
int distanceUpperLeft = Numerics.Abs(p - upperLeft); |
||||
|
|
||||
|
// PNG resolves equal distances in left, above, upper-left order.
|
||||
|
byte predictor = distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft |
||||
|
? left |
||||
|
: distanceAbove <= distanceUpperLeft ? above : upperLeft; |
||||
|
|
||||
|
return (byte)(scan - predictor); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector128<byte> Invoke( |
||||
|
Vector128<byte> scan, |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft) |
||||
|
{ |
||||
|
Vector128<byte> predictor = Predict(left, above, upperLeft); |
||||
|
return scan - predictor; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector256<byte> Invoke( |
||||
|
Vector256<byte> scan, |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft) |
||||
|
{ |
||||
|
Vector256<byte> predictor = Predict(left, above, upperLeft); |
||||
|
return scan - predictor; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static Vector512<byte> Invoke( |
||||
|
Vector512<byte> scan, |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft) |
||||
|
{ |
||||
|
Vector512<byte> predictor = Predict(left, above, upperLeft); |
||||
|
return scan - predictor; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth neighbor for sixteen independent byte lanes.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<byte> Predict( |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft) |
||||
|
{ |
||||
|
Vector128<byte> aboveMinusUpper = SubtractSaturate(above, upperLeft); |
||||
|
Vector128<byte> leftMinusUpper = SubtractSaturate(left, upperLeft); |
||||
|
Vector128<byte> distanceLeft = SubtractSaturate(upperLeft, above) | aboveMinusUpper; |
||||
|
Vector128<byte> distanceAbove = SubtractSaturate(upperLeft, left) | leftMinusUpper; |
||||
|
|
||||
|
return SelectPredictor( |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft, |
||||
|
aboveMinusUpper, |
||||
|
leftMinusUpper, |
||||
|
distanceLeft, |
||||
|
distanceAbove); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth neighbor for thirty-two independent byte lanes.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector256<byte> Predict( |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft) |
||||
|
{ |
||||
|
Vector256<byte> aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); |
||||
|
Vector256<byte> leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); |
||||
|
Vector256<byte> distanceLeft = Avx2.SubtractSaturate(upperLeft, above) | aboveMinusUpper; |
||||
|
Vector256<byte> distanceAbove = Avx2.SubtractSaturate(upperLeft, left) | leftMinusUpper; |
||||
|
|
||||
|
return SelectPredictor( |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft, |
||||
|
aboveMinusUpper, |
||||
|
leftMinusUpper, |
||||
|
distanceLeft, |
||||
|
distanceAbove); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth neighbor for sixty-four independent byte lanes.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector512<byte> Predict( |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft) |
||||
|
{ |
||||
|
Vector512<byte> aboveMinusUpper = Avx512BW.SubtractSaturate(above, upperLeft); |
||||
|
Vector512<byte> leftMinusUpper = Avx512BW.SubtractSaturate(left, upperLeft); |
||||
|
Vector512<byte> distanceLeft = Avx512BW.SubtractSaturate(upperLeft, above) | aboveMinusUpper; |
||||
|
Vector512<byte> distanceAbove = Avx512BW.SubtractSaturate(upperLeft, left) | leftMinusUpper; |
||||
|
|
||||
|
return SelectPredictor( |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft, |
||||
|
aboveMinusUpper, |
||||
|
leftMinusUpper, |
||||
|
distanceLeft, |
||||
|
distanceAbove); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies Paeth distance and tie-breaking rules to sixteen lanes.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<byte> SelectPredictor( |
||||
|
Vector128<byte> left, |
||||
|
Vector128<byte> above, |
||||
|
Vector128<byte> upperLeft, |
||||
|
Vector128<byte> aboveMinusUpper, |
||||
|
Vector128<byte> leftMinusUpper, |
||||
|
Vector128<byte> distanceLeft, |
||||
|
Vector128<byte> distanceAbove) |
||||
|
{ |
||||
|
Vector128<byte> sameDirection = Vector128.Equals( |
||||
|
Vector128.Equals(aboveMinusUpper, Vector128<byte>.Zero), |
||||
|
Vector128.Equals(leftMinusUpper, Vector128<byte>.Zero)); |
||||
|
|
||||
|
Vector128<byte> distanceUpper = sameDirection |
||||
|
| SubtractSaturate(distanceAbove, distanceLeft) |
||||
|
| SubtractSaturate(distanceLeft, distanceAbove); |
||||
|
|
||||
|
Vector128<byte> minimumAboveUpper = Vector128.Min(distanceUpper, distanceAbove); |
||||
|
Vector128<byte> aboveOrUpper = Vector128.ConditionalSelect( |
||||
|
Vector128.Equals(minimumAboveUpper, distanceAbove), |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
// Applying the left comparison last preserves PNG's left-first tie rule.
|
||||
|
return Vector128.ConditionalSelect( |
||||
|
Vector128.Equals(Vector128.Min(minimumAboveUpper, distanceLeft), distanceLeft), |
||||
|
left, |
||||
|
aboveOrUpper); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies Paeth distance and tie-breaking rules to thirty-two lanes.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector256<byte> SelectPredictor( |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft, |
||||
|
Vector256<byte> aboveMinusUpper, |
||||
|
Vector256<byte> leftMinusUpper, |
||||
|
Vector256<byte> distanceLeft, |
||||
|
Vector256<byte> distanceAbove) |
||||
|
{ |
||||
|
Vector256<byte> sameDirection = Vector256.Equals( |
||||
|
Vector256.Equals(aboveMinusUpper, Vector256<byte>.Zero), |
||||
|
Vector256.Equals(leftMinusUpper, Vector256<byte>.Zero)); |
||||
|
|
||||
|
Vector256<byte> distanceUpper = sameDirection |
||||
|
| Avx2.SubtractSaturate(distanceAbove, distanceLeft) |
||||
|
| Avx2.SubtractSaturate(distanceLeft, distanceAbove); |
||||
|
|
||||
|
Vector256<byte> minimumAboveUpper = Vector256.Min(distanceUpper, distanceAbove); |
||||
|
Vector256<byte> aboveOrUpper = Vector256.ConditionalSelect( |
||||
|
Vector256.Equals(minimumAboveUpper, distanceAbove), |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
return Vector256.ConditionalSelect( |
||||
|
Vector256.Equals(Vector256.Min(minimumAboveUpper, distanceLeft), distanceLeft), |
||||
|
left, |
||||
|
aboveOrUpper); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies Paeth distance and tie-breaking rules to sixty-four lanes.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector512<byte> SelectPredictor( |
||||
|
Vector512<byte> left, |
||||
|
Vector512<byte> above, |
||||
|
Vector512<byte> upperLeft, |
||||
|
Vector512<byte> aboveMinusUpper, |
||||
|
Vector512<byte> leftMinusUpper, |
||||
|
Vector512<byte> distanceLeft, |
||||
|
Vector512<byte> distanceAbove) |
||||
|
{ |
||||
|
Vector512<byte> sameDirection = Vector512.Equals( |
||||
|
Vector512.Equals(aboveMinusUpper, Vector512<byte>.Zero), |
||||
|
Vector512.Equals(leftMinusUpper, Vector512<byte>.Zero)); |
||||
|
|
||||
|
Vector512<byte> distanceUpper = sameDirection |
||||
|
| Avx512BW.SubtractSaturate(distanceAbove, distanceLeft) |
||||
|
| Avx512BW.SubtractSaturate(distanceLeft, distanceAbove); |
||||
|
|
||||
|
Vector512<byte> minimumAboveUpper = Vector512.Min(distanceUpper, distanceAbove); |
||||
|
Vector512<byte> aboveOrUpper = Vector512.ConditionalSelect( |
||||
|
Vector512.Equals(minimumAboveUpper, distanceAbove), |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
return Vector512.ConditionalSelect( |
||||
|
Vector512.Equals(Vector512.Min(minimumAboveUpper, distanceLeft), distanceLeft), |
||||
|
left, |
||||
|
aboveOrUpper); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an unsigned saturating subtraction using the active 128-bit instruction set.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The minuend lanes.</param>
|
||||
|
/// <param name="right">The subtrahend lanes.</param>
|
||||
|
/// <returns>The saturated lane-wise differences.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<byte> SubtractSaturate(Vector128<byte> left, Vector128<byte> right) |
||||
|
{ |
||||
|
if (Sse2.IsSupported) |
||||
|
{ |
||||
|
return Sse2.SubtractSaturate(left, right); |
||||
|
} |
||||
|
|
||||
|
if (AdvSimd.IsSupported) |
||||
|
{ |
||||
|
return AdvSimd.SubtractSaturate(left, right); |
||||
|
} |
||||
|
|
||||
|
// Subtracting the smaller operand produces max(left - right, 0) without
|
||||
|
// requiring a backend-specific saturating-subtract instruction.
|
||||
|
return left - Vector128.Min(left, right); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,228 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png.Filters; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies PNG filter operators while accumulating the absolute signed residuals used for filter selection.
|
||||
|
/// </summary>
|
||||
|
internal static class PngFilterEncoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Maps a scanline through a filter operator, writes the residuals, and reduces their total variance.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The PNG predictor selected for this closed traversal.</typeparam>
|
||||
|
/// <param name="scanline">The scanline to encode.</param>
|
||||
|
/// <param name="previousScanline">The preceding scanline.</param>
|
||||
|
/// <param name="result">The destination including its leading filter byte.</param>
|
||||
|
/// <param name="bytesPerPixel">The distance to the corresponding component in the preceding pixel.</param>
|
||||
|
/// <param name="sum">The sum of the absolute signed residuals.</param>
|
||||
|
// Inlining closes every static interface call over TOperator. The JIT can then remove
|
||||
|
// source loads ignored by simpler predictors and specialize the active register widths.
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
public static void Encode<TOperator>( |
||||
|
ReadOnlySpan<byte> scanline, |
||||
|
ReadOnlySpan<byte> previousScanline, |
||||
|
Span<byte> result, |
||||
|
uint bytesPerPixel, |
||||
|
out int sum) |
||||
|
where TOperator : struct, IPngFilterOperator |
||||
|
{ |
||||
|
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); |
||||
|
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); |
||||
|
|
||||
|
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); |
||||
|
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); |
||||
|
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); |
||||
|
|
||||
|
resultBaseRef = (byte)TOperator.Type; |
||||
|
sum = 0; |
||||
|
|
||||
|
nuint x = 0; |
||||
|
|
||||
|
// Components in the first pixel have no left or upper-left neighbor. Supplying
|
||||
|
// zeroes expresses the PNG boundary rule directly through the same predictor.
|
||||
|
for (; x < bytesPerPixel; x++) |
||||
|
{ |
||||
|
byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; |
||||
|
|
||||
|
byte filtered = TOperator.Invoke( |
||||
|
Unsafe.Add(ref scanBaseRef, x), |
||||
|
0, |
||||
|
above, |
||||
|
0); |
||||
|
|
||||
|
Unsafe.Add(ref resultBaseRef, x + 1) = filtered; |
||||
|
sum += Numerics.Abs(unchecked((sbyte)filtered)); |
||||
|
} |
||||
|
|
||||
|
Vector128<uint> sum128 = Vector128<uint>.Zero; |
||||
|
|
||||
|
// A single 512-bit register does not amortize folding its SAD accumulator.
|
||||
|
// Leave short rows to the narrower paths, which have lower fixed reduction cost.
|
||||
|
if (Avx512BW.IsSupported && scanline.Length - (int)x >= Vector512<byte>.Count * 2) |
||||
|
{ |
||||
|
Vector512<uint> sum512 = Vector512<uint>.Zero; |
||||
|
int oneRegisterFromEnd = scanline.Length - Vector512<byte>.Count; |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector512<byte>.Count) |
||||
|
{ |
||||
|
// Each byte lane represents one independently filtered component. The
|
||||
|
// four input vectors retain the scan/left/above/upper-left PNG layout.
|
||||
|
// Operator usage flags are constants after generic specialization, so
|
||||
|
// unused predictors do not retain even fault-preserving probe loads.
|
||||
|
Vector512<byte> left = TOperator.UsesLeft |
||||
|
? Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)) |
||||
|
: default; |
||||
|
Vector512<byte> above = TOperator.UsesAbove |
||||
|
? Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref previousBaseRef, x)) |
||||
|
: default; |
||||
|
Vector512<byte> upperLeft = TOperator.UsesUpperLeft |
||||
|
? Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)) |
||||
|
: default; |
||||
|
|
||||
|
Vector512<byte> filtered = TOperator.Invoke( |
||||
|
Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref scanBaseRef, x)), |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector512<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; |
||||
|
x += (uint)Vector512<byte>.Count; |
||||
|
|
||||
|
// PNG scores each residual as abs((sbyte)residual). VPSADBW sums eight
|
||||
|
// byte lanes into every other 32-bit lane without widening each byte.
|
||||
|
Vector512<byte> absolute = Avx512BW.Abs(filtered.AsSByte()); |
||||
|
sum512 += Avx512BW.SumAbsoluteDifferences(absolute, Vector512<byte>.Zero).AsUInt32(); |
||||
|
} |
||||
|
|
||||
|
// Fold only widths that processed data. Short rows therefore avoid both
|
||||
|
// wide accumulator initialization and an otherwise empty reduction.
|
||||
|
Vector256<uint> folded512 = sum512.GetLower() + sum512.GetUpper(); |
||||
|
sum128 += folded512.GetLower() + folded512.GetUpper(); |
||||
|
} |
||||
|
|
||||
|
if (Avx2.IsSupported) |
||||
|
{ |
||||
|
Vector256<uint> sum256 = Vector256<uint>.Zero; |
||||
|
int oneRegisterFromEnd = scanline.Length - Vector256<byte>.Count; |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector256<byte>.Count) |
||||
|
{ |
||||
|
Vector256<byte> left = TOperator.UsesLeft |
||||
|
? Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)) |
||||
|
: default; |
||||
|
Vector256<byte> above = TOperator.UsesAbove |
||||
|
? Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x)) |
||||
|
: default; |
||||
|
Vector256<byte> upperLeft = TOperator.UsesUpperLeft |
||||
|
? Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)) |
||||
|
: default; |
||||
|
|
||||
|
Vector256<byte> filtered = TOperator.Invoke( |
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x)), |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; |
||||
|
x += (uint)Vector256<byte>.Count; |
||||
|
|
||||
|
Vector256<byte> absolute = Avx2.Abs(filtered.AsSByte()); |
||||
|
sum256 += Avx2.SumAbsoluteDifferences(absolute, Vector256<byte>.Zero).AsUInt32(); |
||||
|
} |
||||
|
|
||||
|
sum128 += sum256.GetLower() + sum256.GetUpper(); |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneRegisterFromEnd = scanline.Length - Vector128<byte>.Count; |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector128<byte>.Count) |
||||
|
{ |
||||
|
Vector128<byte> left = TOperator.UsesLeft |
||||
|
? Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)) |
||||
|
: default; |
||||
|
Vector128<byte> above = TOperator.UsesAbove |
||||
|
? Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref previousBaseRef, x)) |
||||
|
: default; |
||||
|
Vector128<byte> upperLeft = TOperator.UsesUpperLeft |
||||
|
? Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)) |
||||
|
: default; |
||||
|
|
||||
|
Vector128<byte> filtered = TOperator.Invoke( |
||||
|
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, x)), |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; |
||||
|
x += (uint)Vector128<byte>.Count; |
||||
|
|
||||
|
sum128 = AccumulateAbsolute(sum128, filtered); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
sum += unchecked((int)Vector128.Sum(sum128)); |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++, x++) |
||||
|
{ |
||||
|
byte left = TOperator.UsesLeft ? Unsafe.Add(ref scanBaseRef, xLeft) : (byte)0; |
||||
|
byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; |
||||
|
byte upperLeft = TOperator.UsesUpperLeft ? Unsafe.Add(ref previousBaseRef, xLeft) : (byte)0; |
||||
|
|
||||
|
byte filtered = TOperator.Invoke( |
||||
|
Unsafe.Add(ref scanBaseRef, x), |
||||
|
left, |
||||
|
above, |
||||
|
upperLeft); |
||||
|
|
||||
|
Unsafe.Add(ref resultBaseRef, x + 1) = filtered; |
||||
|
sum += Numerics.Abs(unchecked((sbyte)filtered)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Accumulates the absolute signed values in a 128-bit residual vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="accumulator">The four-lane unsigned accumulator.</param>
|
||||
|
/// <param name="residuals">The sixteen filtered byte residuals.</param>
|
||||
|
/// <returns>The updated accumulator.</returns>
|
||||
|
[MethodImpl(InliningOptions.AlwaysInline)] |
||||
|
private static Vector128<uint> AccumulateAbsolute(Vector128<uint> accumulator, Vector128<byte> residuals) |
||||
|
{ |
||||
|
if (Sse2.IsSupported) |
||||
|
{ |
||||
|
Vector128<byte> absolute; |
||||
|
|
||||
|
if (Ssse3.IsSupported) |
||||
|
{ |
||||
|
absolute = Ssse3.Abs(residuals.AsSByte()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// SSE2 has no packed signed-byte absolute instruction. The sign mask
|
||||
|
// implements (value + mask) XOR mask, including -128 -> 128.
|
||||
|
Vector128<sbyte> mask = Sse2.CompareGreaterThan(Vector128<sbyte>.Zero, residuals.AsSByte()); |
||||
|
absolute = Sse2.Xor(Sse2.Add(residuals.AsSByte(), mask), mask).AsByte(); |
||||
|
} |
||||
|
|
||||
|
return accumulator + Sse2.SumAbsoluteDifferences(absolute, Vector128<byte>.Zero).AsUInt32(); |
||||
|
} |
||||
|
|
||||
|
Vector128<byte> absoluteArm = Vector128.Abs(residuals.AsSByte()).AsByte(); |
||||
|
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(absoluteArm); |
||||
|
(Vector128<uint> lower0, Vector128<uint> lower1) = Vector128.Widen(lower16); |
||||
|
(Vector128<uint> upper0, Vector128<uint> upper1) = Vector128.Widen(upper16); |
||||
|
|
||||
|
// Four widening additions keep every byte contribution in a 32-bit lane,
|
||||
|
// matching the x86 accumulator's overflow behavior without scalar reduction.
|
||||
|
return accumulator + lower0 + lower1 + upper0 + upper1; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,157 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.Formats.Png; |
||||
|
using SixLabors.ImageSharp.Formats.Png.Filters; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares the shared PNG map/reduce traversal with the filter-specific traversals it replaces.
|
||||
|
/// </summary>
|
||||
|
[Config(typeof(Config.Short))] |
||||
|
public class PngFilterEncode |
||||
|
{ |
||||
|
private const int BytesPerPixel = 4; |
||||
|
|
||||
|
private byte[] scanline; |
||||
|
private byte[] previousScanline; |
||||
|
private byte[] currentResult; |
||||
|
private byte[] baselineResult; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the filter evaluated by each invocation.
|
||||
|
/// </summary>
|
||||
|
[Params(PngFilterMethod.Sub, PngFilterMethod.Up, PngFilterMethod.Average, PngFilterMethod.Paeth)] |
||||
|
public PngFilterMethod Filter { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of scanline bytes.
|
||||
|
/// </summary>
|
||||
|
[Params(64, 1024, 16384)] |
||||
|
public int Count { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates deterministic non-uniform inputs and independent result buffers.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.scanline = new byte[this.Count]; |
||||
|
this.previousScanline = new byte[this.Count]; |
||||
|
this.currentResult = new byte[this.Count + 1]; |
||||
|
this.baselineResult = new byte[this.Count + 1]; |
||||
|
|
||||
|
Random random = new(12345678); |
||||
|
random.NextBytes(this.scanline); |
||||
|
random.NextBytes(this.previousScanline); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the operator-driven map/reduce traversal.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The filter variance sum.</returns>
|
||||
|
[Benchmark] |
||||
|
public int Current() |
||||
|
=> this.Filter switch |
||||
|
{ |
||||
|
PngFilterMethod.Sub => this.EncodeSubCurrent(), |
||||
|
PngFilterMethod.Up => this.EncodeUpCurrent(), |
||||
|
PngFilterMethod.Average => this.EncodeAverageCurrent(), |
||||
|
PngFilterMethod.Paeth => this.EncodePaethCurrent(), |
||||
|
_ => throw new InvalidOperationException() |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the filter-specific traversal being replaced.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The filter variance sum.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
public int Baseline() |
||||
|
{ |
||||
|
int sum; |
||||
|
|
||||
|
switch (this.Filter) |
||||
|
{ |
||||
|
case PngFilterMethod.Sub: |
||||
|
PngFilterEncodeBaseline.EncodeSub( |
||||
|
this.scanline, |
||||
|
this.baselineResult, |
||||
|
BytesPerPixel, |
||||
|
out sum); |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case PngFilterMethod.Up: |
||||
|
PngFilterEncodeBaseline.EncodeUp( |
||||
|
this.scanline, |
||||
|
this.previousScanline, |
||||
|
this.baselineResult, |
||||
|
out sum); |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case PngFilterMethod.Average: |
||||
|
PngFilterEncodeBaseline.EncodeAverage( |
||||
|
this.scanline, |
||||
|
this.previousScanline, |
||||
|
this.baselineResult, |
||||
|
BytesPerPixel, |
||||
|
out sum); |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case PngFilterMethod.Paeth: |
||||
|
PngFilterEncodeBaseline.EncodePaeth( |
||||
|
this.scanline, |
||||
|
this.previousScanline, |
||||
|
this.baselineResult, |
||||
|
BytesPerPixel, |
||||
|
out sum); |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new InvalidOperationException(); |
||||
|
} |
||||
|
|
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the current Sub encoder.
|
||||
|
/// </summary>
|
||||
|
private int EncodeSubCurrent() |
||||
|
{ |
||||
|
SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out int sum); |
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the current Up encoder.
|
||||
|
/// </summary>
|
||||
|
private int EncodeUpCurrent() |
||||
|
{ |
||||
|
UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out int sum); |
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the current Average encoder.
|
||||
|
/// </summary>
|
||||
|
private int EncodeAverageCurrent() |
||||
|
{ |
||||
|
AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); |
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the current Paeth encoder.
|
||||
|
/// </summary>
|
||||
|
private int EncodePaethCurrent() |
||||
|
{ |
||||
|
PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); |
||||
|
return sum; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.Formats.Png.Filters; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes every normalized PNG filter and retained baseline for assembly comparison.
|
||||
|
/// </summary>
|
||||
|
[Config(typeof(Config.Analysis))] |
||||
|
public class PngFilterEncodeAssembly |
||||
|
{ |
||||
|
private const int BytesPerPixel = 4; |
||||
|
private const int Count = 180; |
||||
|
|
||||
|
private byte[] scanline; |
||||
|
private byte[] previousScanline; |
||||
|
private byte[] currentResult; |
||||
|
private byte[] baselineResult; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.scanline = new byte[Count]; |
||||
|
this.previousScanline = new byte[Count]; |
||||
|
this.currentResult = new byte[Count + 1]; |
||||
|
this.baselineResult = new byte[Count + 1]; |
||||
|
|
||||
|
Random random = new(12345678); |
||||
|
random.NextBytes(this.scanline); |
||||
|
random.NextBytes(this.previousScanline); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the normalized Sub encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void Sub() |
||||
|
=> SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the normalized Up encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void Up() |
||||
|
=> UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the normalized Average encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void Average() |
||||
|
=> AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the normalized Paeth encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void Paeth() |
||||
|
=> PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the retained Sub encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void BaselineSub() |
||||
|
=> PngFilterEncodeBaseline.EncodeSub(this.scanline, this.baselineResult, BytesPerPixel, out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the retained Up encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void BaselineUp() |
||||
|
=> PngFilterEncodeBaseline.EncodeUp(this.scanline, this.previousScanline, this.baselineResult, out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the retained Average encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void BaselineAverage() |
||||
|
=> PngFilterEncodeBaseline.EncodeAverage( |
||||
|
this.scanline, |
||||
|
this.previousScanline, |
||||
|
this.baselineResult, |
||||
|
BytesPerPixel, |
||||
|
out _); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the retained Paeth encoder.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
public void BaselinePaeth() |
||||
|
=> PngFilterEncodeBaseline.EncodePaeth( |
||||
|
this.scanline, |
||||
|
this.previousScanline, |
||||
|
this.baselineResult, |
||||
|
BytesPerPixel, |
||||
|
out _); |
||||
|
} |
||||
@ -0,0 +1,470 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Retains the filter-specific PNG encode traversals for direct performance comparison.
|
||||
|
/// </summary>
|
||||
|
internal static class PngFilterEncodeBaseline |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Executes the filter-specific Sub traversal.
|
||||
|
/// </summary>
|
||||
|
public static void EncodeSub( |
||||
|
ReadOnlySpan<byte> scanline, |
||||
|
Span<byte> result, |
||||
|
int bytesPerPixel, |
||||
|
out int sum) |
||||
|
{ |
||||
|
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); |
||||
|
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); |
||||
|
sum = 0; |
||||
|
resultBaseRef = 1; |
||||
|
|
||||
|
nuint x = 0; |
||||
|
|
||||
|
for (; x < (uint)bytesPerPixel;) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = scan; |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
|
||||
|
if (Avx2.IsSupported) |
||||
|
{ |
||||
|
Vector256<byte> zero = Vector256<byte>.Zero; |
||||
|
Vector256<int> accumulator = Vector256<int>.Zero; |
||||
|
|
||||
|
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256<byte>.Count; xLeft += (uint)Vector256<byte>.Count) |
||||
|
{ |
||||
|
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)); |
||||
|
Vector256<byte> residual = Avx2.Subtract(scan, left); |
||||
|
|
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector256<byte>.Count; |
||||
|
accumulator = Avx2.Add( |
||||
|
accumulator, |
||||
|
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); |
||||
|
} |
||||
|
|
||||
|
sum += Numerics.EvenReduceSum(accumulator); |
||||
|
} |
||||
|
else if (Vector.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector<uint> accumulator = Vector<uint>.Zero; |
||||
|
|
||||
|
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector<byte>.Count; xLeft += (uint)Vector<byte>.Count) |
||||
|
{ |
||||
|
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector<byte> left = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)); |
||||
|
Vector<byte> residual = scan - left; |
||||
|
|
||||
|
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector<byte>.Count; |
||||
|
Numerics.Accumulate( |
||||
|
ref accumulator, |
||||
|
Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < Vector<uint>.Count; i++) |
||||
|
{ |
||||
|
sum += (int)accumulator[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
byte left = Unsafe.Add(ref scanBaseRef, xLeft); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = (byte)(scan - left); |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the filter-specific Up traversal.
|
||||
|
/// </summary>
|
||||
|
public static void EncodeUp( |
||||
|
ReadOnlySpan<byte> scanline, |
||||
|
ReadOnlySpan<byte> previousScanline, |
||||
|
Span<byte> result, |
||||
|
out int sum) |
||||
|
{ |
||||
|
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); |
||||
|
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); |
||||
|
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); |
||||
|
sum = 0; |
||||
|
resultBaseRef = 2; |
||||
|
|
||||
|
nuint x = 0; |
||||
|
|
||||
|
if (Avx2.IsSupported) |
||||
|
{ |
||||
|
Vector256<byte> zero = Vector256<byte>.Zero; |
||||
|
Vector256<int> accumulator = Vector256<int>.Zero; |
||||
|
|
||||
|
for (; (int)x <= scanline.Length - Vector256<byte>.Count;) |
||||
|
{ |
||||
|
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x)); |
||||
|
Vector256<byte> residual = Avx2.Subtract(scan, above); |
||||
|
|
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector256<byte>.Count; |
||||
|
accumulator = Avx2.Add( |
||||
|
accumulator, |
||||
|
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); |
||||
|
} |
||||
|
|
||||
|
sum += Numerics.EvenReduceSum(accumulator); |
||||
|
} |
||||
|
else if (Vector.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector<uint> accumulator = Vector<uint>.Zero; |
||||
|
|
||||
|
for (; (int)x <= scanline.Length - Vector<byte>.Count;) |
||||
|
{ |
||||
|
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref previousBaseRef, x)); |
||||
|
Vector<byte> residual = scan - above; |
||||
|
|
||||
|
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector<byte>.Count; |
||||
|
Numerics.Accumulate( |
||||
|
ref accumulator, |
||||
|
Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < Vector<uint>.Count; i++) |
||||
|
{ |
||||
|
sum += (int)accumulator[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; x < (uint)scanline.Length;) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
byte above = Unsafe.Add(ref previousBaseRef, x); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = (byte)(scan - above); |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the filter-specific Average traversal.
|
||||
|
/// </summary>
|
||||
|
public static void EncodeAverage( |
||||
|
ReadOnlySpan<byte> scanline, |
||||
|
ReadOnlySpan<byte> previousScanline, |
||||
|
Span<byte> result, |
||||
|
uint bytesPerPixel, |
||||
|
out int sum) |
||||
|
{ |
||||
|
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); |
||||
|
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); |
||||
|
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); |
||||
|
sum = 0; |
||||
|
resultBaseRef = 3; |
||||
|
|
||||
|
nuint x = 0; |
||||
|
|
||||
|
for (; x < bytesPerPixel;) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
byte above = Unsafe.Add(ref previousBaseRef, x); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = (byte)(scan - (above >> 1)); |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
|
||||
|
if (Avx2.IsSupported) |
||||
|
{ |
||||
|
Vector256<byte> zero = Vector256<byte>.Zero; |
||||
|
Vector256<int> accumulator = Vector256<int>.Zero; |
||||
|
Vector256<byte> allBitsSet = Avx2.CompareEqual(accumulator, accumulator).AsByte(); |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256<byte>.Count; xLeft += (uint)Vector256<byte>.Count) |
||||
|
{ |
||||
|
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)); |
||||
|
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x)); |
||||
|
Vector256<byte> average = Avx2.Xor( |
||||
|
Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), |
||||
|
allBitsSet); |
||||
|
|
||||
|
Vector256<byte> residual = Avx2.Subtract(scan, average); |
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector256<byte>.Count; |
||||
|
accumulator = Avx2.Add( |
||||
|
accumulator, |
||||
|
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); |
||||
|
} |
||||
|
|
||||
|
sum += Numerics.EvenReduceSum(accumulator); |
||||
|
} |
||||
|
else if (Sse2.IsSupported) |
||||
|
{ |
||||
|
Vector128<byte> zero = Vector128<byte>.Zero; |
||||
|
Vector128<int> accumulator = Vector128<int>.Zero; |
||||
|
Vector128<byte> allBitsSet = Sse2.CompareEqual(accumulator, accumulator).AsByte(); |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128<byte>.Count; xLeft += (uint)Vector128<byte>.Count) |
||||
|
{ |
||||
|
Vector128<byte> scan = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector128<byte> left = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)); |
||||
|
Vector128<byte> above = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref previousBaseRef, x)); |
||||
|
Vector128<byte> average = Sse2.Xor( |
||||
|
Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), |
||||
|
allBitsSet); |
||||
|
|
||||
|
Vector128<byte> residual = Sse2.Subtract(scan, average); |
||||
|
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector128<byte>.Count; |
||||
|
|
||||
|
Vector128<byte> absolute; |
||||
|
|
||||
|
if (Ssse3.IsSupported) |
||||
|
{ |
||||
|
absolute = Ssse3.Abs(residual.AsSByte()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Vector128<sbyte> mask = Sse2.CompareGreaterThan(zero.AsSByte(), residual.AsSByte()); |
||||
|
absolute = Sse2.Xor(Sse2.Add(residual.AsSByte(), mask), mask).AsByte(); |
||||
|
} |
||||
|
|
||||
|
accumulator = Sse2.Add( |
||||
|
accumulator, |
||||
|
Sse2.SumAbsoluteDifferences(absolute, zero).AsInt32()); |
||||
|
} |
||||
|
|
||||
|
sum += Numerics.EvenReduceSum(accumulator); |
||||
|
} |
||||
|
|
||||
|
for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
byte left = Unsafe.Add(ref scanBaseRef, xLeft); |
||||
|
byte above = Unsafe.Add(ref previousBaseRef, x); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = (byte)(scan - ((left + above) >> 1)); |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Executes the filter-specific Paeth traversal.
|
||||
|
/// </summary>
|
||||
|
public static void EncodePaeth( |
||||
|
ReadOnlySpan<byte> scanline, |
||||
|
ReadOnlySpan<byte> previousScanline, |
||||
|
Span<byte> result, |
||||
|
int bytesPerPixel, |
||||
|
out int sum) |
||||
|
{ |
||||
|
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); |
||||
|
ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); |
||||
|
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); |
||||
|
sum = 0; |
||||
|
resultBaseRef = 4; |
||||
|
|
||||
|
nuint x = 0; |
||||
|
|
||||
|
for (; x < (uint)bytesPerPixel;) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
byte above = Unsafe.Add(ref previousBaseRef, x); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = (byte)(scan - above); |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
|
||||
|
if (Avx2.IsSupported) |
||||
|
{ |
||||
|
Vector256<byte> zero = Vector256<byte>.Zero; |
||||
|
Vector256<int> accumulator = Vector256<int>.Zero; |
||||
|
|
||||
|
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256<byte>.Count; xLeft += (uint)Vector256<byte>.Count) |
||||
|
{ |
||||
|
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)); |
||||
|
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, x)); |
||||
|
Vector256<byte> upperLeft = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)); |
||||
|
Vector256<byte> residual = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); |
||||
|
|
||||
|
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector256<byte>.Count; |
||||
|
accumulator = Avx2.Add( |
||||
|
accumulator, |
||||
|
Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); |
||||
|
} |
||||
|
|
||||
|
sum += Numerics.EvenReduceSum(accumulator); |
||||
|
} |
||||
|
else if (Vector.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector<uint> accumulator = Vector<uint>.Zero; |
||||
|
|
||||
|
for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector<byte>.Count; xLeft += (uint)Vector<byte>.Count) |
||||
|
{ |
||||
|
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x)); |
||||
|
Vector<byte> left = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft)); |
||||
|
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref previousBaseRef, x)); |
||||
|
Vector<byte> upperLeft = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref previousBaseRef, xLeft)); |
||||
|
Vector<byte> residual = scan - PaethPredictor(left, above, upperLeft); |
||||
|
|
||||
|
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; |
||||
|
x += (uint)Vector<byte>.Count; |
||||
|
Numerics.Accumulate( |
||||
|
ref accumulator, |
||||
|
Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < Vector<uint>.Count; i++) |
||||
|
{ |
||||
|
sum += (int)accumulator[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) |
||||
|
{ |
||||
|
byte scan = Unsafe.Add(ref scanBaseRef, x); |
||||
|
byte left = Unsafe.Add(ref scanBaseRef, xLeft); |
||||
|
byte above = Unsafe.Add(ref previousBaseRef, x); |
||||
|
byte upperLeft = Unsafe.Add(ref previousBaseRef, xLeft); |
||||
|
x++; |
||||
|
ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); |
||||
|
residual = (byte)(scan - PaethPredictor(left, above, upperLeft)); |
||||
|
sum += Numerics.Abs(unchecked((sbyte)residual)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the scalar Paeth predictor.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static byte PaethPredictor(byte left, byte above, byte upperLeft) |
||||
|
{ |
||||
|
int p = left + above - upperLeft; |
||||
|
int distanceLeft = Numerics.Abs(p - left); |
||||
|
int distanceAbove = Numerics.Abs(p - above); |
||||
|
int distanceUpperLeft = Numerics.Abs(p - upperLeft); |
||||
|
|
||||
|
if (distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft) |
||||
|
{ |
||||
|
return left; |
||||
|
} |
||||
|
|
||||
|
return distanceAbove <= distanceUpperLeft ? above : upperLeft; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the AVX2 Paeth predictor.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<byte> PaethPredictor( |
||||
|
Vector256<byte> left, |
||||
|
Vector256<byte> above, |
||||
|
Vector256<byte> upperLeft) |
||||
|
{ |
||||
|
Vector256<byte> zero = Vector256<byte>.Zero; |
||||
|
Vector256<byte> aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); |
||||
|
Vector256<byte> leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); |
||||
|
Vector256<byte> distanceLeft = |
||||
|
Avx2.Or(Avx2.SubtractSaturate(upperLeft, above), aboveMinusUpper); |
||||
|
|
||||
|
Vector256<byte> distanceAbove = |
||||
|
Avx2.Or(Avx2.SubtractSaturate(upperLeft, left), leftMinusUpper); |
||||
|
|
||||
|
Vector256<byte> sameDirection = Avx2.CompareEqual( |
||||
|
Avx2.CompareEqual(aboveMinusUpper, zero), |
||||
|
Avx2.CompareEqual(leftMinusUpper, zero)); |
||||
|
|
||||
|
Vector256<byte> distanceUpper = Avx2.Or( |
||||
|
sameDirection, |
||||
|
Avx2.Or( |
||||
|
Avx2.SubtractSaturate(distanceAbove, distanceLeft), |
||||
|
Avx2.SubtractSaturate(distanceLeft, distanceAbove))); |
||||
|
|
||||
|
Vector256<byte> minimumAboveUpper = Avx2.Min(distanceUpper, distanceAbove); |
||||
|
Vector256<byte> aboveOrUpper = Avx2.BlendVariable( |
||||
|
upperLeft, |
||||
|
above, |
||||
|
Avx2.CompareEqual(minimumAboveUpper, distanceAbove)); |
||||
|
|
||||
|
return Avx2.BlendVariable( |
||||
|
aboveOrUpper, |
||||
|
left, |
||||
|
Avx2.CompareEqual(Avx2.Min(minimumAboveUpper, distanceLeft), distanceLeft)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the portable vector Paeth predictor.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector<byte> PaethPredictor( |
||||
|
Vector<byte> left, |
||||
|
Vector<byte> above, |
||||
|
Vector<byte> upperLeft) |
||||
|
{ |
||||
|
Vector.Widen(left, out Vector<ushort> leftLow, out Vector<ushort> leftHigh); |
||||
|
Vector.Widen(above, out Vector<ushort> aboveLow, out Vector<ushort> aboveHigh); |
||||
|
Vector.Widen(upperLeft, out Vector<ushort> upperLow, out Vector<ushort> upperHigh); |
||||
|
|
||||
|
Vector<short> lower = PaethPredictor( |
||||
|
Vector.AsVectorInt16(leftLow), |
||||
|
Vector.AsVectorInt16(aboveLow), |
||||
|
Vector.AsVectorInt16(upperLow)); |
||||
|
|
||||
|
Vector<short> upper = PaethPredictor( |
||||
|
Vector.AsVectorInt16(leftHigh), |
||||
|
Vector.AsVectorInt16(aboveHigh), |
||||
|
Vector.AsVectorInt16(upperHigh)); |
||||
|
|
||||
|
return Vector.AsVectorByte(Vector.Narrow(lower, upper)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the portable widened Paeth predictor.
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector<short> PaethPredictor( |
||||
|
Vector<short> left, |
||||
|
Vector<short> above, |
||||
|
Vector<short> upperLeft) |
||||
|
{ |
||||
|
Vector<short> p = left + above - upperLeft; |
||||
|
Vector<short> distanceLeft = Vector.Abs(p - left); |
||||
|
Vector<short> distanceAbove = Vector.Abs(p - above); |
||||
|
Vector<short> distanceUpper = Vector.Abs(p - upperLeft); |
||||
|
|
||||
|
Vector<short> chooseLeft = Vector.BitwiseAnd( |
||||
|
Vector.LessThanOrEqual(distanceLeft, distanceAbove), |
||||
|
Vector.LessThanOrEqual(distanceLeft, distanceUpper)); |
||||
|
|
||||
|
return Vector.ConditionalSelect( |
||||
|
chooseLeft, |
||||
|
left, |
||||
|
Vector.ConditionalSelect( |
||||
|
Vector.LessThanOrEqual(distanceAbove, distanceUpper), |
||||
|
above, |
||||
|
upperLeft)); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue