-
Notifications
You must be signed in to change notification settings - Fork 6k
Framework wide color linear gradients #54748
Framework wide color linear gradients #54748
Conversation
cad3ee7
to
bfaefe5
Compare
bfaefe5
to
5c0887f
Compare
@jonahwilliams @flar this PR relies on the integration test for verification. Let me know if there is a more direct way in the engine you'd like to see it tested. |
lib/ui/painting/gradient.cc
Outdated
for (int i = 0; i < colors.num_elements(); ++i) { | ||
/// TODO(gaaclarke): Make this preserve wide gamut colors. | ||
dl_colors.emplace_back(DlColor(colors[i])); | ||
dl_colors.reserve(color_stops.num_elements()); |
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.
Can't this go back to a cast now? We're copying the data into the display list again so we shouldn't need to allocate more storage.
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.
Oh, we can't cast it anymore since the color space field.
In that case, can we update this API so we reserve and write the color bytes directly into the DL storage instead of allocating an intermediary?
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.
In order to have a cast the memory layouts need to be identical. Here we have an array of floats, we need to insert the colorspace field into the memory layout if we wanted to use casts. The casts are super frail. I don't recommend it unless we get some data that supports it's worth the risk.
If we did find it was slowing us down, I would recommend doing the conversion on the stack when the number of colors is less than a certain size to catch most use cases before resorting to an unsafe cast.
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.
Something like this:
if (num_colors <= 3) {
std::array<DlColor, 3> dl_colors;
//...
} else {
std::vector<DlColor> dl_colors;
dl_colors.reserve(num_colors);
//...
}
I don't think it's worth the extra complexity, but I can do it if we want. I know we were bit in the past with too many heap allocations.
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.
All this code is doing is passing the data into the DLBuilder so it can be copied into the display list. Can we copy into the final destination instead of allocating an intermediary?
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 think I'm gradually absorbing the concern here. Talk of "DL" has me thinking of the display list object itself with recorded operations, but I think the concern here is that DlFooGradientColorSource embeds the data into a contiguous chunk and we are converting from one array to a temp vector of DlColor objects and then into the data co-located with the eventual DlColorSource object.
So, yes, it would be nice if we could copy directly from the constructor/factory argument into the pod allocation without first creating a vector of DlColor objects. I'd be happy to see that added as a future optimization and just stick with making some sort of list of DlColor objects for now to get this capability implemented in its simplest fashion.
We could also consider having the storage for these gradient objects be just an array of floats and a separate common CS. The gradient objects could still get you a single color in a getter by marrying the right 4 floats and the CS into a DlColor object, but there would also be accessors to the common CS value and the array of component float data which the backend might use more efficiently. It would also save on storage size.
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.
Also, I will be coming through these classes to de-skia-fy them shortly, so I can implement such optimizations as I do that work. That work should all fall under "should have no impact at all on output" changes and so would hopefully pass up the chain without regressions.
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'd prefer we use a named factory so that it is clear what order the scalars are supposed to be in the array.
That's what I'm doing I'm refactoring DlColorSource to use the Builder pattern. That way we can pass in DlColor arrays or float arrays.
The logic that I'm asking you to add does not need an intergration test to cover it (in fact it may already be tested)
This PR is adding new functionality that is only tested in an integration test that has to live in another repo. This PR implements the feature while working within the bounds of the current API. I agree with your feedback and it will be addressed. That's why we went through the whole process of brainstorming a solution. However this PR does not add any new heap allocations, they were already there, removing them is going to be a bit of a refactor, one who would be served by having a test as a backstop to make sure we don't break the thing we are actually trying to implement.
We can withhold the approval of this PR until you see the refactor PR if you want. I don't understand why I'm not being trusted here.
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.
Removing an unnecessary heap allocation does not require an integration test
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.
Moving to the functions to DlScalar* is not going to work because there are 2 clients of DlLinearGradientColorSource::MakeLinear
. In one case we have DlScalar*, in another we have DlColor*. Moving to DlScalar would require us to do heap allocations when converting in the other route. The other client is DlColorSource::shared()
, I tried removing it and that runs deep coming from DlAttribute.
The only viable solution is to use the iterator pattern if we want to eliminate heap allocations in both cases. That would require moving more logic to templates or a dynamic dispatch, which would possibly eliminate any benefit of removing the tiny heap allocation.
I think the best bet is to eliminate the heap in most cases but have an overflow for gradients with large amounts of colors.
for (int i = 0; i < colors.num_elements(); ++i) { | ||
/// TODO(gaaclarke): Make this preserve wide gamut colors. | ||
dl_colors.emplace_back(DlColor(colors[i])); | ||
dl_colors.reserve(num_colors); |
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.
Can we have a helper method, or would that involve unnecessary copying? I thought the compiler optimized the case of returning a vector...??
Another way is to create the vector on the local stack and then hand it into the converter method by reference.
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.
Can we have a helper method, or would that involve unnecessary copying? I thought the compiler optimized the case of returning a vector...??
Another way is to create the vector on the local stack and then hand it into the converter method by reference.
You can't have a vector on a stack. It will always allocate memory on the heap. The size is needed to be known at compilation time in order to put something on the stack (without trickery). I have a proposition in an above thread for using an std::array
on the stack.
I think avoiding the copying will require DlColorSource some mechanism to read from the tonic collection directly.
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.
The data has to be heap allocated because it is growable, that will happen whether a helper function is used or not. I'm referring to the object itself, but I suppose that the object isn't very big whether it is stack allocated or not. It's a vtable + data pointer + allocated size + used size.
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.
Oh, I see, sorry I thought this was still related to the heap discussion. Yes we could have a helper function and yes C++ would perform the return value optimization.
@@ -4452,10 +4452,25 @@ enum TileMode { | |||
decal, | |||
} | |||
|
|||
Float32List _encodeWideColorList(List<Color> colors) { |
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 remember talking about preserving the CS, but I'm guessing that gradients are going to want to normalize to a similar CS for lerping - and extendedSRGB is probably the best for fractional combinations?
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.
We do preserve the colorspace from the users perspective. The conversion to extended srgb only happens when communicating the values to the engine.
Are there tests that test |
The test edit: link - engine/testing/dart/canvas_test.dart Line 165 in 8d7d5ff
|
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.
LGTM
@gaaclarke and I looked at this offline. I agree that the refactor can probably wait on this landing in the framework + other testing work. @flar would you be available to pair with @gaaclarke and come up with a way to remove the intermediate allocation?
…154653) flutter/engine@4a01a3a...652a43b 2024-09-04 [email protected] Manual roll Dart SDK from 139867e57ba9 to 1a6246225b75 (18 revisions) (flutter/engine#54962) 2024-09-04 [email protected] Framework wide color linear gradients (flutter/engine#54748) 2024-09-04 [email protected] Add more `package:test` (removing usages of `package:litetest`) (flutter/engine#54882) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
issue: #127855 depends on flutter/engine#54748 being rolled into the framework
Reverts: #153976 Initiated by: jonahwilliams Reason for reverting: failing on postsubmit Original PR Author: gaaclarke Reviewed By: {jonahwilliams} This change reverts the following previous change: issue: #127855 depends on flutter/engine#54748 being rolled into the framework
This implements wide gamut color support for gradients in the engine.
issue: flutter/flutter#127855
integration test: flutter/flutter#153976
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.