-
Notifications
You must be signed in to change notification settings - Fork 608
Allow for subpixel deviations + improved unoptimized loop execution in image comparison #571
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
Conversation
This is an great fix for a weird problem, and I just want to voice my support in the hope that this PR gets merged. We have the same issue with differences in produced colors on M1 and Intel and this will solve our issue until the whole team (and possibly CircleCI) is on M1. Lowering the precision threshold is not an option for us, since we primarily use this library to test placement and size of elements. Minor differences in colors, however, is something we can excuse since we now need to have a hybrid M1/Intel solution in our team. I've opened PRs in our component libraries that will use @pimms fork until this (hopefully) gets merged. |
@dmitri-zganiaiko Nice find on the threshhold calculation I think @pimms has addressed it and this is ready for review again. |
This is still blocked I think we need an approval from someone with write access. |
Just to emphasize the important of this PR, we have a team that begins to use M1 as the main development platform, but our CI will be on Intel for the foreseeable future (At least until Github provides M1-Based Github Runners). |
/// - size: A view size override. | ||
/// - traits: A trait collection override. | ||
public static func image( | ||
drawHierarchyInKeyWindow: Bool = false, | ||
precision: Float = 1, | ||
subpixelThreshold: UInt8 = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it maybe make sense the default the threshold to a value which will avoid the M1 vs Intel issue out of the box (ex - 5)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't seen any "one" value that would make sense. Sometimes, with corner-rounding for instance, the difference is <= 2
, while with images it seems to typically be <= 1
. With shadows and rounded corners, it may be as high as <= 5
. I think all of these values are anecdotal though, and that the user should have a fully explicit relationship with this value. After all, this is a hacky workaround, and it's probably a good idea to not enable it by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
YeH, makes sense, I was just hoping there was a magic value haha.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pimms Thanks for sharing this I’m using it now and it works great.
I’ve a few nits that don't really matter but on the off-chance this delays the eventual merge I'm happy to do myself if you're ok with it as they appear to be pretty trivial.
- Update the
.image
entires within Documentation/Available-Snapshot-Strategies.md to include the sub-pixel property. - SceneKit snapshot strategy support
- SpriteKit snapshot strategy support
I wasn't able to check that the internal snapshot tests for this project work, there are a fair few failing but I suspect this is due to me not knowing what simulator to use or the tests already failing.
@Deco354 Thanks, good points. I believe that a handful of tests are failing on I don't think I'll have time to look at this this week at least, so if you or someone else wants to have a look at it it'd be great — otherwise I'll get to it at some point. |
One more thing, what exactly does 1 byte of the |
while offset < pixelCount * 4 { | ||
if p1[offset].diff(between: p2[offset]) > subpixelThreshold { | ||
differentPixelCount += 1 | ||
if differentPixelCount > threshold { | ||
return false | ||
} | ||
} | ||
if Float(differentPixelCount) > threshold { return false } | ||
offset += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason this was changed from a for-loop to a while-loop? My interpretation is that in both implementations the loop should either increment offset
or return false
.
Using a while loop and manually incrementing the offset
makes this less maintainable since it's easy to introduce changes in the loop body that skip incrementing offset
and would lead to an infinite loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, just saw your PR description with the explanation for when compiled without optimization.
while byte < byteCount { | ||
if oldBytes[byte].diff(between: newerBytes[byte]) > subpixelThreshold { | ||
differentPixelCount += 1 | ||
if differentPixelCount >= threshold { | ||
return false | ||
} | ||
} | ||
byte += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here about replacing the for loop with a while loop.
I believe it's using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pimms Thanks for taking this on! We've been backed up on some of these PRs, but are hoping to merge them slowly.
I think we'll likely merge this as is unless you want to tie up some of the loose ends mentioned above, first. Once we merge we may also make a few small changes on main
as far as API naming goes.
Let us know what you think!
Great news, @stephencelis! 🎉 I've not had as much free time to look into the follow-ups as I thought I'd have, and I know I don't have much time coming up. If you're fine with merging this as-is, that sounds great 💯 |
@pimms Can you tell us, what flags are used?
|
@chosa91 We were using CocoaPods at the time, and added the following at the end of our Podfile. I can't fully recall what Xcode-values this would translate to.
|
Will this be merged soon, would be good to be able to start testing this? |
We started using this fork in our project and it's been working great so far. Setting |
Is there a schedule when this will be merged? |
@stephencelis any thoughts on merging this? |
@stephencelis any plans to merge it? |
@mbrandonw Trying to ping someone else from this org to take a look and merge it 🙏🏼 Edit: Seems like |
Subpixel difference
Workaround for the issue described in #313 and #424.
The readme clearly states that the reference-device should be identical to the recorder-device, but as M1 Macs are becoming popular with developers at a different rate to CI & cloud providers, that is becoming increasingly difficult.
Allow each subpixel (R, G, B) to deviate by up to a certain degree. Currently, a
precision: 0.9
means that "90% of the pixels must match 100%". The issues described in particularly #313, where some experience that the pixels deviate by only 1, can be resolved by comparing withsubpixelThreshold: 1
. Comparing withprecision: 0.9, subpixelThreshold: 1
then means "90% of the subpixels' value must be ±1 of the reference".Optimization
When an image comparison
memcmp
fails and we need to compare each byte of the failing image, the current loop iteration is slow when compiled without optimization on Intel machines.My team defaulted to adding custom compiler flags to
SnapshotTesting
, but the problem can also be diminished significantly by using a more basic loop implementation.The difference between looping over a range (current implementation) and looping with a condition can be seen on Godbolt.
Example in Godbolt
LBB1_4
-LBB1_10
withLBB3_1