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

Added feature to use rounded values #256

Closed
wants to merge 13 commits into from

Conversation

woehrl01
Copy link
Contributor

Added an experimental feature to allow to use only rounded values. See #184. It's not a perfect solution and definitely can be further improved. I'm looking forward to your ideas.

@emilsjolander
Copy link
Contributor

Thanks! Pretty hard to review whether this does what we want though. Could you add a test (CSSLayoutPixelRoundingTest.cpp) which tests this thoroughly? I'm specifically interested how this works together with flex-grow and flex-shrink.

@woehrl01
Copy link
Contributor Author

I just tried to add some test, but I can't get it to work to modify gentest.js to output the values as float. For a simple case like the following:

<div id="pixel_rounding_flex_basis_flex_grow_row" style="width: 100px; height: 100px; flex-direction: row;">
  <div style="flex-grow: 1;"></div>
  <div style="flex-grow: 1;"></div>
  <div style="flex-grow: 1;"></div>
</div>

it returns 33, 34, 33 as width. But it should return 33.3333, 33.3333, 33.3333.

image

Did I miss something?

@emilsjolander
Copy link
Contributor

I suggest for this that you right a test manually which tests this specific
feature and don't use gentest. Gentest is used for testing style features
and make sure they render the same as browsers. We have manually written
tests as well for example the CSSLayoutMeasureCacheTest.cpp as well as
others.
On Sat, 19 Nov 2016 at 21:47, Lukas Wöhrl [email protected] wrote:

I just tried to add some test, but I can't get it to work to modify
gentest.js to output the values as float. For a simple case like the
following:

it returns 33, 34, 33 as width. But it should return 33.3333, 33.3333,
33.3333.

[image: image]
https://cloud.githubusercontent.com/assets/2535856/20458607/f517696c-aea9-11e6-90b9-3a594da2fa0d.png

Did I miss something?


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#256 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABdIpC4JYUjnRoS30bDOp-L5fOmp4TZzks5q_26OgaJpZM4K3Qp3
.

@woehrl01
Copy link
Contributor Author

Jep, I think this is the way to go. I just wanted to use gentest.js as a pre-step of the manually written tests.
I just figured out, that if I run this test (took me a little to setup gtest first), they fail as expected: 👍

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from CSSLayoutTest
[ RUN      ] CSSLayoutTest.pixel_rounding_flex_basis_flex_grow_row
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(51): error: Value of: CSSNodeLayoutGetWidth(root_child0)
  Actual: 33.3333
Expected: 33
[  FAILED  ] CSSLayoutTest.pixel_rounding_flex_basis_flex_grow_row (1 ms)

I had the wrong assumption that there are tests which return numbers with decimal places already. As there where some in the older code base. Had some wtf moments while figuring that out 😅.

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@woehrl01
Copy link
Contributor Author

I added some tests and changed the distribution logic of the remaining pixels. You now can even use the output from gentest.js directly. You only have to replace ASSERT_EQ with ASSERT_FLOAT_EQ and reference the "feature" test suite with TEST_F.

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@emilsjolander
Copy link
Contributor

Because browsers do rounding of pixel values automatically I would argue that this perhaps should not be an experimental feature but instead just part of the core library. Also the tests you committed don't seem to be generated via gentest.rb from a fixture provided in /gentext/fixtures. Also generated tests should not be hand modified in any way as they are not able to be re-generated.

@woehrl01
Copy link
Contributor Author

I haven't added the .html file for the generated test as it would be overriden on the next gen. I would suggest to change the generated tests to use ASSERT_FLOAT_EQ and be able to change the test fixture via data-testfixture=SomeClassName on the main div. So we can generate them more easily in future.

@emilsjolander
Copy link
Contributor

To date we haven't needed any test fixtures so it has not been a problem really, once we do I think that might be a good idea. I think using ASSERT_FLOAT_EQ would be fine, please submit that as a separate pull request. As I said I don't think the fixture is needed here though as I'm not sure this should be an experimental feature. What do you think?

@woehrl01
Copy link
Contributor Author

I'll provide a PR for the gentest change.

I'm not sure if this should be a core feature. There are a few pros/cons for this.

Pro:

  • Result behaves graphically like the browser
  • Won't clutter the code with lot's of IFs and different behaviour according to having the feature enabled or not

Con:

  • Could be worse for the performance because of lots of rounding calculation.
  • Some end users maybe want to have fractial numbers as an endresult, maybe if you want to scale the output.

I did some tests with nodes like top: 0.3px; height: 113.4px;. They aren't calculated as in chrome, I found it pretty hard to adapt to the expected result, especially correctly distribution of the "missing pixels" over the children. I think this needs a bigger change, which aren't that easy to opt-out.

@emilsjolander
Copy link
Contributor

Could be worse for the performance because of lots of rounding calculation

I'm not all too worried about this. We can test this and currently a lot of the higher level frameworks do their own rounding of values anyways. May be worth keeping this as experimental to be able to run these tests first though.

Some end users maybe want to have fractial numbers as an endresult, maybe if you want to scale the output.

Let's not speculate in what people may want. We have seen many people have the need for rounded output so let's only tackle this in the case we see a need for it.

I did some tests with nodes like top: 0.3px; height: 113.4px;. They aren't calculated as in chrome.

Could you elaborate?

@woehrl01
Copy link
Contributor Author

I agree that we shouldn't speculate.

Sure, I tried the following tests:

<div id="rounding_feature_fractial_1" style="height: 113.4px; width: 100px;">
  <div style="height: 20px; flex-grow:1; flex-basis:50px;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
</div>

<div id="rounding_feature_fractial_2" style="height: 113.6px; width: 100px;">
  <div style="height: 20px; flex-grow:1; flex-basis:50px;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
</div>

<div id="rounding_feature_fractial_3" style="top: 0.3px; height: 113.4px; width: 100px;">
  <div style="height: 20px; flex-grow:1; flex-basis:50px;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
</div>

<div id="rounding_feature_fractial_4" style="top: 0.7px; height: 113.4px; width: 100px;">
  <div style="height: 20px; flex-grow:1; flex-basis:50px;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
  <div style="height: 10px; flex-grow:1;"></div>
</div>

I got the code so that it almost works except for one test:

[ RUN      ] CSSLayoutFeatureRoundingTest.rounding_feature_fractial_2
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(444): error: Value of: CSSNodeLayoutGetHeight(root_child0)
  Actual: 64
Expected: 65
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(447): error: Value of: CSSNodeLayoutGetTop(root_child1)
  Actual: 64
Expected: 65
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(449): error: Value of: CSSNodeLayoutGetHeight(root_child1)
  Actual: 25
Expected: 24
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(454): error: Value of: CSSNodeLayoutGetHeight(root_child2)
  Actual: 24
Expected: 25
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(466): error: Value of: CSSNodeLayoutGetHeight(root_child0)
  Actual: 64
Expected: 65
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(469): error: Value of: CSSNodeLayoutGetTop(root_child1)
  Actual: 64
Expected: 65
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(471): error: Value of: CSSNodeLayoutGetHeight(root_child1)
  Actual: 25
Expected: 24
c:\users\lukas\dev\css-layout\tests\csslayoutpixelroundingtest.cpp(476): error: Value of: CSSNodeLayoutGetHeight(root_child2)
  Actual: 24
Expected: 25
[  FAILED  ] CSSLayoutFeatureRoundingTest.rounding_feature_fractial_2 (16 ms)

I'm not sure how "bad" those differences are, and if this is even relevant? (In my use case it isn't ;) )

But I only had an hour to look into this, yet. I just wanted to try out how it behaves with fractial input.

@emilsjolander
Copy link
Contributor

Yeah I was going to mention that. We should expand the tests to handle when a user explicitly sets a fractional input value. We could round the number in the setter perhaps?

@woehrl01
Copy link
Contributor Author

Rounding the numbers via setter could be a possibility to lower the calculation overhead. But you won't get your input back via the getter.

My current implementation is rounding inside computedEdgeValue and round availableWidth and availableHeight at the top of layoutImpl. If you want I'll publish them, but it isn't perfect yet.

Do we have the requirement to have the distribution of the values over the children to be pixel perfect with chrome?

@emilsjolander
Copy link
Contributor

Rounding the numbers via setter could be a possibility to lower the calculation overhead. But you won't get your input back via the getter.

This is fine as long as we document it.

Do we have the requirement to have the distribution of the values over the children to be pixel perfect with chrome?

We try to where possible. This makes it much easier to test our implementation.

@woehrl01
Copy link
Contributor Author

I reworked the algorithm, now its more opt-out/-in and the tests are passing as expected. It's also more clean imho.

@@ -2462,6 +2462,23 @@ bool layoutNodeInternal(const CSSNodeRef node,
return (needToVisitNode || cachedResults == NULL);
}


void roundToPixelGrid(const CSSNodeRef node){
Copy link
Contributor

Choose a reason for hiding this comment

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

static

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@emilsjolander
Copy link
Contributor

This is starting to look very good. Please also add tests for when input is a decimal value (width, height, flex-basis, left, top, right, bottom) as well as for when a measure function returns a decimal value.

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@woehrl01
Copy link
Contributor Author

woehrl01 commented Nov 21, 2016

made the method static and added some extended tests cases. Not sure how I should test the measure function returnment. Should I simply assume the correct result? As those can't be verified via chrome.

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@@ -2549,7 +2549,7 @@ void CSSLog(CSSLogLevel level, const char *format, ...) {
va_end(args);
}

static bool experimentalFeatures[CSSExperimentalFeatureCount + 1];
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep this in here as it is very little memory and changing this back and forth when adding/removing experiments is just annoying.

@emilsjolander
Copy link
Contributor

For measure function test just return something like {10.2, 10.2} and assert that the output width/height is what you expect {10, 10} I would assume.

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@woehrl01
Copy link
Contributor Author

Added two tests one for rounding down and one for rounding up.

@facebook-github-bot
Copy link
Contributor

@woehrl01 updated the pull request - view changes

@emilsjolander
Copy link
Contributor

Awesome! I'll import and test this. Once we have run this in production i'll probably remove the experimental flag. Sound good?

@woehrl01
Copy link
Contributor Author

Sounds pretty good to me! Looking forward to your results. 😄

@facebook-github-bot
Copy link
Contributor

@emilsjolander has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Nov 22, 2016
Summary:
Added an experimental feature to allow to use only rounded values. See #184. It's not a perfect solution and definitely  can be further improved. I'm looking forward to your ideas.
Closes facebook/yoga#256

Reviewed By: splhack

Differential Revision: D4214168

Pulled By: emilsjolander

fbshipit-source-id: 6293352d479b7b4dad258eb3f9e0afaa11cf7236
@vjeux
Copy link
Contributor

vjeux commented Nov 22, 2016

FYI, the code that does the same thing in React Native is: https://github.com/facebook/react-native/blob/master/React/Views/RCTShadowView.m#L107-L182

The way the algorithm works is to first get the global absolute position by summing up your position + the position of all your parents unrounded and then round it.

There are two benefits:

  • This ensures that 1px borders don't vanish. If you operate locally, your 1px border may be rounded down to 0 and disappear. But if you operate in a global space, you are guaranteed that they are all going to remain.

  • This ensures that you don't have rounding errors that propagate, this is especially important for weird aspect ratios like 1/3 which don't cleanly map to floating points.

It also not only rounds to the nearest integer but to the nearest pixel density. For example on a 2x retina you can have a value of 0.5 and on 3x you can have 0.33333.

@woehrl01
Copy link
Contributor Author

Thanks @vjeux . I'm not sure if RCTShadowView is doing what you think it does:

Frame.X and Frame.Y are Node.Left and Node.Right simply rounded.

Frame.Width is also simply rounding of Node.Width: (same for Frame.Height)

absoluteBottomRight.x - absoluteTopLeft.x 
= (absolutePosition.x + CSSNodeLayoutGetLeft(node) + CSSNodeLayoutGetWidth(node)) - (absolutePosition.x + CSSNodeLayoutGetLeft(node))
= absolutePosition.x + CSSNodeLayoutGetLeft(node) + CSSNodeLayoutGetWidth(node) - absolutePosition.x - CSSNodeLayoutGetLeft(node)
= CSSNodeLayoutGetWidth(node)

Please don't get me wrong but absolutePosition seems to have no relevance for me in that calculation.

I tried to match the calculation, as it is done in chrome, I took the absolute position into account and the result differed from the chrome output.

Thanks for the hint with the boarder, those values aren't rounded yet, this should definitely be done.

True that the pixel density is not taken into account, maybe density should be configurable?

DanielMSchmidt pushed a commit to DanielMSchmidt/react-native that referenced this pull request Jan 4, 2017
Summary:
Added an experimental feature to allow to use only rounded values. See facebook#184. It's not a perfect solution and definitely  can be further improved. I'm looking forward to your ideas.
Closes facebook/yoga#256

Reviewed By: splhack

Differential Revision: D4214168

Pulled By: emilsjolander

fbshipit-source-id: 6293352d479b7b4dad258eb3f9e0afaa11cf7236
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.

4 participants