Skip to content

Commit 6ab4b10

Browse files
Merge pull request #2432 from SixLabors/bp/fixCalcResidualCosts
Fix Vp8Residual costs calculation
2 parents 9e4e7a8 + 81d47f4 commit 6ab4b10

File tree

5 files changed

+247
-16
lines changed

5 files changed

+247
-16
lines changed

src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public int GetResidualCost(int ctx0)
156156
return LossyUtils.Vp8BitCost(0, (byte)p0);
157157
}
158158

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

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

175178
ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs);
176179
ref byte levelsRef = ref MemoryMarshal.GetReference(levels);
177180
ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels);
178-
Unsafe.As<byte, Vector128<byte>>(ref ctxsRef) = g.GetLower();
179-
Unsafe.As<byte, Vector128<byte>>(ref levelsRef) = h.GetLower();
180-
Unsafe.As<ushort, Vector256<ushort>>(ref absLevelsRef) = e0.AsUInt16();
181+
Unsafe.As<byte, Vector128<byte>>(ref ctxsRef) = g;
182+
Unsafe.As<byte, Vector128<byte>>(ref levelsRef) = h;
183+
Unsafe.As<ushort, Vector128<ushort>>(ref absLevelsRef) = e0.AsUInt16();
184+
Unsafe.As<ushort, Vector128<ushort>>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16();
181185

182186
int level;
183187
int flevel;
Lines changed: 229 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Text;
5+
using SixLabors.ImageSharp.Formats.Webp;
46
using SixLabors.ImageSharp.Formats.Webp.Lossy;
57
using SixLabors.ImageSharp.Tests.TestUtilities;
68

@@ -9,10 +11,234 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp;
911
[Trait("Format", "Webp")]
1012
public class Vp8ResidualTests
1113
{
14+
private static void WriteVp8Residual(string filename, Vp8Residual residual)
15+
{
16+
using FileStream stream = File.Open(filename, FileMode.Create);
17+
using BinaryWriter writer = new(stream, Encoding.UTF8, false);
18+
19+
writer.Write(residual.First);
20+
writer.Write(residual.Last);
21+
writer.Write(residual.CoeffType);
22+
23+
for (int i = 0; i < residual.Coeffs.Length; i++)
24+
{
25+
writer.Write(residual.Coeffs[i]);
26+
}
27+
28+
for (int i = 0; i < residual.Prob.Length; i++)
29+
{
30+
for (int j = 0; j < residual.Prob[i].Probabilities.Length; j++)
31+
{
32+
writer.Write(residual.Prob[i].Probabilities[j].Probabilities);
33+
}
34+
}
35+
36+
for (int i = 0; i < residual.Costs.Length; i++)
37+
{
38+
Vp8Costs costs = residual.Costs[i];
39+
Vp8CostArray[] costsArray = costs.Costs;
40+
for (int j = 0; j < costsArray.Length; j++)
41+
{
42+
for (int k = 0; k < costsArray[j].Costs.Length; k++)
43+
{
44+
writer.Write(costsArray[j].Costs[k]);
45+
}
46+
}
47+
}
48+
49+
for (int i = 0; i < residual.Stats.Length; i++)
50+
{
51+
for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
52+
{
53+
for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
54+
{
55+
writer.Write(residual.Stats[i].Stats[j].Stats[k]);
56+
}
57+
}
58+
}
59+
60+
writer.Flush();
61+
}
62+
63+
private static Vp8Residual ReadVp8Residual(string fileName)
64+
{
65+
using FileStream stream = File.Open(fileName, FileMode.Open);
66+
using BinaryReader reader = new(stream, Encoding.UTF8, false);
67+
68+
Vp8Residual residual = new()
69+
{
70+
First = reader.ReadInt32(),
71+
Last = reader.ReadInt32(),
72+
CoeffType = reader.ReadInt32()
73+
};
74+
75+
for (int i = 0; i < residual.Coeffs.Length; i++)
76+
{
77+
residual.Coeffs[i] = reader.ReadInt16();
78+
}
79+
80+
Vp8BandProbas[] bandProbas = new Vp8BandProbas[8];
81+
for (int i = 0; i < bandProbas.Length; i++)
82+
{
83+
bandProbas[i] = new Vp8BandProbas();
84+
for (int j = 0; j < bandProbas[i].Probabilities.Length; j++)
85+
{
86+
for (int k = 0; k < 11; k++)
87+
{
88+
bandProbas[i].Probabilities[j].Probabilities[k] = reader.ReadByte();
89+
}
90+
}
91+
}
92+
93+
residual.Prob = bandProbas;
94+
95+
residual.Costs = new Vp8Costs[16];
96+
for (int i = 0; i < residual.Costs.Length; i++)
97+
{
98+
residual.Costs[i] = new Vp8Costs();
99+
Vp8CostArray[] costsArray = residual.Costs[i].Costs;
100+
for (int j = 0; j < costsArray.Length; j++)
101+
{
102+
for (int k = 0; k < costsArray[j].Costs.Length; k++)
103+
{
104+
costsArray[j].Costs[k] = reader.ReadUInt16();
105+
}
106+
}
107+
}
108+
109+
residual.Stats = new Vp8Stats[8];
110+
for (int i = 0; i < residual.Stats.Length; i++)
111+
{
112+
residual.Stats[i] = new Vp8Stats();
113+
for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
114+
{
115+
for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
116+
{
117+
residual.Stats[i].Stats[j].Stats[k] = reader.ReadUInt32();
118+
}
119+
}
120+
}
121+
122+
return residual;
123+
}
124+
125+
[Fact]
126+
public void Vp8Residual_Serialization_Works()
127+
{
128+
// arrange
129+
Vp8Residual expected = new();
130+
Vp8EncProba encProb = new();
131+
Random rand = new(439);
132+
CreateRandomProbas(encProb, rand);
133+
CreateCosts(encProb, rand);
134+
expected.Init(1, 0, encProb);
135+
for (int i = 0; i < expected.Coeffs.Length; i++)
136+
{
137+
expected.Coeffs[i] = (byte)rand.Next(255);
138+
}
139+
140+
// act
141+
string fileName = "Vp8SerializationTest.bin";
142+
WriteVp8Residual(fileName, expected);
143+
Vp8Residual actual = ReadVp8Residual(fileName);
144+
File.Delete(fileName);
145+
146+
// assert
147+
Assert.Equal(expected.CoeffType, actual.CoeffType);
148+
Assert.Equal(expected.Coeffs, actual.Coeffs);
149+
Assert.Equal(expected.Costs.Length, actual.Costs.Length);
150+
Assert.Equal(expected.First, actual.First);
151+
Assert.Equal(expected.Last, actual.Last);
152+
Assert.Equal(expected.Stats.Length, actual.Stats.Length);
153+
for (int i = 0; i < expected.Stats.Length; i++)
154+
{
155+
Vp8StatsArray[] expectedStats = expected.Stats[i].Stats;
156+
Vp8StatsArray[] actualStats = actual.Stats[i].Stats;
157+
Assert.Equal(expectedStats.Length, actualStats.Length);
158+
for (int j = 0; j < expectedStats.Length; j++)
159+
{
160+
Assert.Equal(expectedStats[j].Stats, actualStats[j].Stats);
161+
}
162+
}
163+
164+
Assert.Equal(expected.Prob.Length, actual.Prob.Length);
165+
for (int i = 0; i < expected.Prob.Length; i++)
166+
{
167+
Vp8ProbaArray[] expectedProbabilities = expected.Prob[i].Probabilities;
168+
Vp8ProbaArray[] actualProbabilities = actual.Prob[i].Probabilities;
169+
Assert.Equal(expectedProbabilities.Length, actualProbabilities.Length);
170+
for (int j = 0; j < expectedProbabilities.Length; j++)
171+
{
172+
Assert.Equal(expectedProbabilities[j].Probabilities, actualProbabilities[j].Probabilities);
173+
}
174+
}
175+
176+
for (int i = 0; i < expected.Costs.Length; i++)
177+
{
178+
Vp8CostArray[] expectedCosts = expected.Costs[i].Costs;
179+
Vp8CostArray[] actualCosts = actual.Costs[i].Costs;
180+
Assert.Equal(expectedCosts.Length, actualCosts.Length);
181+
for (int j = 0; j < expectedCosts.Length; j++)
182+
{
183+
Assert.Equal(expectedCosts[j].Costs, actualCosts[j].Costs);
184+
}
185+
}
186+
}
187+
188+
[Fact]
189+
public void GetResidualCost_Works()
190+
{
191+
// arrange
192+
int ctx0 = 0;
193+
int expected = 20911;
194+
Vp8Residual residual = ReadVp8Residual(Path.Combine("TestDataWebp", "Vp8Residual.bin"));
195+
196+
// act
197+
int actual = residual.GetResidualCost(ctx0);
198+
199+
// assert
200+
Assert.Equal(expected, actual);
201+
}
202+
203+
private static void CreateRandomProbas(Vp8EncProba probas, Random rand)
204+
{
205+
for (int t = 0; t < WebpConstants.NumTypes; ++t)
206+
{
207+
for (int b = 0; b < WebpConstants.NumBands; ++b)
208+
{
209+
for (int c = 0; c < WebpConstants.NumCtx; ++c)
210+
{
211+
for (int p = 0; p < WebpConstants.NumProbas; ++p)
212+
{
213+
probas.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)rand.Next(255);
214+
}
215+
}
216+
}
217+
}
218+
}
219+
220+
private static void CreateCosts(Vp8EncProba probas, Random rand)
221+
{
222+
for (int i = 0; i < probas.RemappedCosts.Length; i++)
223+
{
224+
for (int j = 0; j < probas.RemappedCosts[i].Length; j++)
225+
{
226+
for (int k = 0; k < probas.RemappedCosts[i][j].Costs.Length; k++)
227+
{
228+
ushort[] costs = probas.RemappedCosts[i][j].Costs[k].Costs;
229+
for (int m = 0; m < costs.Length; m++)
230+
{
231+
costs[m] = (byte)rand.Next(255);
232+
}
233+
}
234+
}
235+
}
236+
}
237+
12238
private static void RunSetCoeffsTest()
13239
{
14240
// arrange
15-
var residual = new Vp8Residual();
241+
Vp8Residual residual = new();
16242
short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 };
17243

18244
// act
@@ -23,11 +249,8 @@ private static void RunSetCoeffsTest()
23249
}
24250

25251
[Fact]
26-
public void RunSetCoeffsTest_Works() => RunSetCoeffsTest();
27-
28-
[Fact]
29-
public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
252+
public void SetCoeffsTest_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
30253

31254
[Fact]
32-
public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic);
255+
public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableSSE2);
33256
}

tests/ImageSharp.Tests/ImageSharp.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
<None Update="TestFonts\SixLaborsSampleAB.woff">
5757
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
5858
</None>
59+
<None Update="TestDataWebp\Vp8Residual.bin">
60+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
61+
</None>
5962
<None Update="xunit.runner.json">
6063
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
6164
</None>
Binary file not shown.

0 commit comments

Comments
 (0)