Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public int GetResidualCost(int ctx0)
return LossyUtils.Vp8BitCost(0, (byte)p0);
}

if (Avx2.IsSupported)
if (Sse2.IsSupported)
{
Span<byte> scratch = stackalloc byte[32];
Span<byte> ctxs = scratch.Slice(0, 16);
Expand All @@ -165,19 +165,23 @@ public int GetResidualCost(int ctx0)

// Precompute clamped levels and contexts, packed to 8b.
ref short outputRef = ref MemoryMarshal.GetReference<short>(this.Coeffs);
Vector256<short> c0 = Unsafe.As<short, Vector256<byte>>(ref outputRef).AsInt16();
Vector256<short> d0 = Avx2.Subtract(Vector256<short>.Zero, c0);
Vector256<short> e0 = Avx2.Max(c0, d0); // abs(v), 16b
Vector256<sbyte> f = Avx2.PackSignedSaturate(e0, e0);
Vector256<byte> g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2));
Vector256<byte> h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67]
Vector128<short> c0 = Unsafe.As<short, Vector128<byte>>(ref outputRef).AsInt16();
Vector128<short> c1 = Unsafe.As<short, Vector128<byte>>(ref Unsafe.Add(ref outputRef, 8)).AsInt16();
Vector128<short> d0 = Sse2.Subtract(Vector128<short>.Zero, c0);
Vector128<short> d1 = Sse2.Subtract(Vector128<short>.Zero, c1);
Vector128<short> e0 = Sse2.Max(c0, d0); // abs(v), 16b
Vector128<short> e1 = Sse2.Max(c1, d1);
Vector128<sbyte> f = Sse2.PackSignedSaturate(e0, e1);
Vector128<byte> g = Sse2.Min(f.AsByte(), Vector128.Create((byte)2)); // context = 0, 1, 2
Vector128<byte> h = Sse2.Min(f.AsByte(), Vector128.Create((byte)67)); // clampLevel in [0..67]

ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs);
ref byte levelsRef = ref MemoryMarshal.GetReference(levels);
ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels);
Unsafe.As<byte, Vector128<byte>>(ref ctxsRef) = g.GetLower();
Unsafe.As<byte, Vector128<byte>>(ref levelsRef) = h.GetLower();
Unsafe.As<ushort, Vector256<ushort>>(ref absLevelsRef) = e0.AsUInt16();
Unsafe.As<byte, Vector128<byte>>(ref ctxsRef) = g;
Unsafe.As<byte, Vector128<byte>>(ref levelsRef) = h;
Unsafe.As<ushort, Vector128<ushort>>(ref absLevelsRef) = e0.AsUInt16();
Unsafe.As<ushort, Vector128<ushort>>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16();

int level;
int flevel;
Expand Down
235 changes: 229 additions & 6 deletions tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Text;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities;

Expand All @@ -9,10 +11,234 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp;
[Trait("Format", "Webp")]
public class Vp8ResidualTests
{
private static void WriteVp8Residual(string filename, Vp8Residual residual)
{
using FileStream stream = File.Open(filename, FileMode.Create);
using BinaryWriter writer = new(stream, Encoding.UTF8, false);

writer.Write(residual.First);
writer.Write(residual.Last);
writer.Write(residual.CoeffType);

for (int i = 0; i < residual.Coeffs.Length; i++)
{
writer.Write(residual.Coeffs[i]);
}

for (int i = 0; i < residual.Prob.Length; i++)
{
for (int j = 0; j < residual.Prob[i].Probabilities.Length; j++)
{
writer.Write(residual.Prob[i].Probabilities[j].Probabilities);
}
}

for (int i = 0; i < residual.Costs.Length; i++)
{
Vp8Costs costs = residual.Costs[i];
Vp8CostArray[] costsArray = costs.Costs;
for (int j = 0; j < costsArray.Length; j++)
{
for (int k = 0; k < costsArray[j].Costs.Length; k++)
{
writer.Write(costsArray[j].Costs[k]);
}
}
}

for (int i = 0; i < residual.Stats.Length; i++)
{
for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
{
for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
{
writer.Write(residual.Stats[i].Stats[j].Stats[k]);
}
}
}

writer.Flush();
}

private static Vp8Residual ReadVp8Residual(string fileName)
{
using FileStream stream = File.Open(fileName, FileMode.Open);
using BinaryReader reader = new(stream, Encoding.UTF8, false);

Vp8Residual residual = new()
{
First = reader.ReadInt32(),
Last = reader.ReadInt32(),
CoeffType = reader.ReadInt32()
};

for (int i = 0; i < residual.Coeffs.Length; i++)
{
residual.Coeffs[i] = reader.ReadInt16();
}

Vp8BandProbas[] bandProbas = new Vp8BandProbas[8];
for (int i = 0; i < bandProbas.Length; i++)
{
bandProbas[i] = new Vp8BandProbas();
for (int j = 0; j < bandProbas[i].Probabilities.Length; j++)
{
for (int k = 0; k < 11; k++)
{
bandProbas[i].Probabilities[j].Probabilities[k] = reader.ReadByte();
}
}
}

residual.Prob = bandProbas;

residual.Costs = new Vp8Costs[16];
for (int i = 0; i < residual.Costs.Length; i++)
{
residual.Costs[i] = new Vp8Costs();
Vp8CostArray[] costsArray = residual.Costs[i].Costs;
for (int j = 0; j < costsArray.Length; j++)
{
for (int k = 0; k < costsArray[j].Costs.Length; k++)
{
costsArray[j].Costs[k] = reader.ReadUInt16();
}
}
}

residual.Stats = new Vp8Stats[8];
for (int i = 0; i < residual.Stats.Length; i++)
{
residual.Stats[i] = new Vp8Stats();
for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
{
for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
{
residual.Stats[i].Stats[j].Stats[k] = reader.ReadUInt32();
}
}
}

return residual;
}

[Fact]
public void Vp8Residual_Serialization_Works()
{
// arrange
Vp8Residual expected = new();
Vp8EncProba encProb = new();
Random rand = new(439);
CreateRandomProbas(encProb, rand);
CreateCosts(encProb, rand);
expected.Init(1, 0, encProb);
for (int i = 0; i < expected.Coeffs.Length; i++)
{
expected.Coeffs[i] = (byte)rand.Next(255);
}

// act
string fileName = "Vp8SerializationTest.bin";
WriteVp8Residual(fileName, expected);
Vp8Residual actual = ReadVp8Residual(fileName);
File.Delete(fileName);

// assert
Assert.Equal(expected.CoeffType, actual.CoeffType);
Assert.Equal(expected.Coeffs, actual.Coeffs);
Assert.Equal(expected.Costs.Length, actual.Costs.Length);
Assert.Equal(expected.First, actual.First);
Assert.Equal(expected.Last, actual.Last);
Assert.Equal(expected.Stats.Length, actual.Stats.Length);
for (int i = 0; i < expected.Stats.Length; i++)
{
Vp8StatsArray[] expectedStats = expected.Stats[i].Stats;
Vp8StatsArray[] actualStats = actual.Stats[i].Stats;
Assert.Equal(expectedStats.Length, actualStats.Length);
for (int j = 0; j < expectedStats.Length; j++)
{
Assert.Equal(expectedStats[j].Stats, actualStats[j].Stats);
}
}

Assert.Equal(expected.Prob.Length, actual.Prob.Length);
for (int i = 0; i < expected.Prob.Length; i++)
{
Vp8ProbaArray[] expectedProbabilities = expected.Prob[i].Probabilities;
Vp8ProbaArray[] actualProbabilities = actual.Prob[i].Probabilities;
Assert.Equal(expectedProbabilities.Length, actualProbabilities.Length);
for (int j = 0; j < expectedProbabilities.Length; j++)
{
Assert.Equal(expectedProbabilities[j].Probabilities, actualProbabilities[j].Probabilities);
}
}

for (int i = 0; i < expected.Costs.Length; i++)
{
Vp8CostArray[] expectedCosts = expected.Costs[i].Costs;
Vp8CostArray[] actualCosts = actual.Costs[i].Costs;
Assert.Equal(expectedCosts.Length, actualCosts.Length);
for (int j = 0; j < expectedCosts.Length; j++)
{
Assert.Equal(expectedCosts[j].Costs, actualCosts[j].Costs);
}
}
}

[Fact]
public void GetResidualCost_Works()
{
// arrange
int ctx0 = 0;
int expected = 20911;
Vp8Residual residual = ReadVp8Residual(Path.Combine("TestDataWebp", "Vp8Residual.bin"));

// act
int actual = residual.GetResidualCost(ctx0);

// assert
Assert.Equal(expected, actual);
}

private static void CreateRandomProbas(Vp8EncProba probas, Random rand)
{
for (int t = 0; t < WebpConstants.NumTypes; ++t)
{
for (int b = 0; b < WebpConstants.NumBands; ++b)
{
for (int c = 0; c < WebpConstants.NumCtx; ++c)
{
for (int p = 0; p < WebpConstants.NumProbas; ++p)
{
probas.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)rand.Next(255);
}
}
}
}
}

private static void CreateCosts(Vp8EncProba probas, Random rand)
{
for (int i = 0; i < probas.RemappedCosts.Length; i++)
{
for (int j = 0; j < probas.RemappedCosts[i].Length; j++)
{
for (int k = 0; k < probas.RemappedCosts[i][j].Costs.Length; k++)
{
ushort[] costs = probas.RemappedCosts[i][j].Costs[k].Costs;
for (int m = 0; m < costs.Length; m++)
{
costs[m] = (byte)rand.Next(255);
}
}
}
}
}

private static void RunSetCoeffsTest()
{
// arrange
var residual = new Vp8Residual();
Vp8Residual residual = new();
short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 };

// act
Expand All @@ -23,11 +249,8 @@ private static void RunSetCoeffsTest()
}

[Fact]
public void RunSetCoeffsTest_Works() => RunSetCoeffsTest();

[Fact]
public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
public void SetCoeffsTest_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);

[Fact]
public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic);
public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableSSE2);
}
3 changes: 3 additions & 0 deletions tests/ImageSharp.Tests/ImageSharp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
<None Update="TestFonts\SixLaborsSampleAB.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestDataWebp\Vp8Residual.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Binary file not shown.
Loading