Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Vp8Residual costs calculation #2432

Merged
merged 16 commits into from
Apr 16, 2023
Merged

Conversation

brianpopow
Copy link
Collaborator

@brianpopow brianpopow commented Apr 7, 2023

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This PR fixes an issue with the AVX2 version of the residual costs calculation. In some rare cases it does not match the scalar version. I have switch back to the SSE2 variant, which matches the original libwebp implementation.

AVX version was

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]

SSE version:

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]

The problem was that Avx2.PackSignedSaturate(e0, e0); is not the same as Sse2.PackSignedSaturate(e0, e1);

Related to #2414

/// Initializes a new instance of the <see cref="Vp8BandProbas"/> class.
/// Only used for unit tests.
/// </summary>
[JsonConstructor]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use a custom JSON converter in the tests rather than adding attributes to the source.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @brianpopow I've given you the runaround here. I didn't realize you'd introduced the constructors.

Let's go with your original plan but wrap the namespace imports and constructor code in the following conditional.

#if SIXLABORS_TESTING || SIXLABORS_TESTING_PREVIEW

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with your original plan but wrap the namespace imports and constructor code in the following conditional.
#if SIXLABORS_TESTING || SIXLABORS_TESTING_PREVIEW

I dont think that's a good idea. If someone does a fresh clone of the repo and tries to run the tests, those webp tests will fail without any obvious reason. I dont think test should behave different when a environment is set. Unfortunately I dont have a better idea so far.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid the json and populate the test instance with equivalent data generated in a class using string builder or similar?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid the json and populate the test instance with equivalent data generated in a class using string builder or similar?

Usually I would create a test instance in the unittest and populate it with data, but for Vp8Residual that does not seem feasible. There are so many nested arrays, that I dont know how this can be populated manually. Thats why I have chosen to go for json serialization.

Also I dont understand your suggestion with the string builder.

I now tried to use the binary serialization and this was looking promising. We would not need the additional constructors.
But now I noticed its marked as obsolete 😢

See: https://learn.microsoft.com/de-de/dotnet/standard/serialization/binaryformatter-security-guide

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed serialization now to use BinaryWriter/BinaryReader, according to https://learn.microsoft.com/de-de/dotnet/standard/serialization/binaryformatter-security-guide this should be fine.

No additional constructors required now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that’s fantastic! We’ll done finding a solution!

@JimBobSquarePants JimBobSquarePants merged commit 6ab4b10 into main Apr 16, 2023
@JimBobSquarePants JimBobSquarePants deleted the bp/fixCalcResidualCosts branch April 16, 2023 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants