-
Notifications
You must be signed in to change notification settings - Fork 6k
[WIP] Display list picture prototype #25234
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
It's awesome to have demonstrated that the current API can be decomposed down to canvas ops. What isn't clear to me right now the framework to be used to determine how to rearrange these ops based on optimizations we want to perform. Perhaps you are going to attempt that in a later patch. My guess is that once this framework is in place, we will need to rethink these ops. Right now, they are just based on the current Skia API. BTW, I am not super concerned that the remaining ops cannot be handled with just as much ease. Would Dart FFI help at all here to represent these native objects? |
|
This prototype is looking great so far! I'm wondering if display list size & growth could have a net negative effect on highly dynamic pictures, like Rive animations. Of course it would be great to profile before acting on that, as the benefits from building display lists entirely in Dart, instead of jumping back and forth between Dart and C++ through the native API, probably outweighs any benefits from chaining new op + data buffers instead of growing them. |
The most recent commit switched from growable lists to caching the longest so far. It needs more work to detect when the cached list has a legacy length that isn't needed any more (a statistics set associated with the buffer perhaps), but before the change I was seeing multi-millisecond spikes in the UI thread while running an app that produced long display lists and afterwards it was very smooth with a very fast average time. The current prototype is quite runnable - feel free to download it for testing and report back (which commit hash was used and) the results you might see on some complicated examples. The "contributing" guideline in the main Flutter repo has information on building a local engine and testing with it. |
|
What is the next step for this? |
|
I've been working on getting our engine's "picture cache" to add support for these display lists for at least some parity on performance. |
b914126 to
bb77c80
Compare
…during shader warmup, pass down object list
flow/display_list_interpreter.cc
Outdated
| FML_LOG(ERROR) << "Starting ops: " << ops_vector_->size() | ||
| << ", data: " << data_vector_->size() | ||
| << ", refs: " << ref_vector_->size(); | ||
| FML_LOG(ERROR) << "Remaining ops: " << (it.ops_end - it.ops) | ||
| << ", data: " << (it.data_end - it.data) | ||
| << ", refs: " << (it.refs_end - it.refs) | ||
| << ", save count delta: " | ||
| << (canvas->getSaveCount() - entrySaveCount); |
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.
These will probably get changed to FML_DCHECKs right?
flow/display_list_interpreter.cc
Outdated
| int entrySaveCount = canvas->getSaveCount(); | ||
| Iterator it(this); | ||
| while (it.HasOp()) { | ||
| FML_LOG(INFO) << DescribeNextOp(it); |
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.
Remove log
| std::make_shared<std::vector<DisplayListRefHolder>>(); | ||
| int obj_index = 0; | ||
| SkSamplingOptions sampling = DisplayListInterpreter::NearestSampling; | ||
| for (uint8_t op : *ops_vector) { |
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.
This seems like logic we should be testing. Could we move it to a dedicated class or method(s) that get tested? It would also make this method easier to read.
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.
Which logic?
It appears to me that I may not have adequate assertions/protections that the vectors are all of the appropriate length for iterating.
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 parsing logic in here - the whole switch statement etc. We should have tests that say something like given this input, we get this data structure back.
|
Very cool, I'm excited for this. Would you consider breaking this up into some smaller PRs to ease review? |
yjbanov
left a comment
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.
This PR needs a substantial amount of tests for the amount of functionality it's adding.
My other thought is that there's a big overlap with the existing web code. We also have a plain Dart implementation of a "display list", including bounds estimation, including a plain Dart path bounds estimator. Can we somehow share this code?
| _updatePaintData(paint, _drawMask); | ||
| _addOp(_CanvasOp.drawPath, 0); | ||
| _addPathData(path); | ||
| _addBounds(path.getBounds()); |
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.
Path.getBounds overestimates its size, which is fine for the purposes of computing the bounds of the picture (I'm assuming this is to be used for optimization, such as pruning of paint ops). This is also the case on the web. Example:
https://github.com/flutter/goldens/blob/master/engine/web/paint_bounds_for_line_rotated.png
|
BTW, the web version of this also has a pretty decent test suite, including golden tests: https://github.com/flutter/goldens/blob/master/engine/web/paint_bounds_for_line_rotated.png. So I think sharing code would be beneficial both for web and mobile. |
On the web we'll be soon migrating from |
|
@yjbanov Is there a good way to share this with web? I remember having a conversation with you that suggested that there was a reason a bit of code was duplicated between web and dart:ui based on the capabilities of how to create a dart package. |
That would be something for @jason-simmons to consider and comment on. |
| const SkImage* GetImage() { return (refs++)->image.get(); } | ||
| const SkVertices* GetVertices() { return (refs++)->vertices.get(); } | ||
| const SkPicture* GetSkPicture() { return (refs++)->picture.get(); } |
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 would find it helpful if we documented why we're returning a raw pointer here and what the ownership expectations are. It seems like we're saying that at this pointer is only good until the iterator is advanced again?
I'm a little worried that this will be brittle to refactoring and testing. It would be easier to reason about this API and review future refactoring of it if it just had a clear ownership model - but at least if it's documented we won't have to re-read it to figure out what's going on.
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'm waffling on whether to return Refs or pointers here. For most cases, we don't need to take ownership of these values because they are used in a single op that goes out of scope.
Note that I still return Refs for attributes because SkPaint takes refs (for obvious reasons).
| // << draw_rect_.top() << ", " | ||
| // << draw_rect_.right() << ", " | ||
| // << draw_rect_.bottom() << "]"; | ||
| // TODO(flar): implement DisplayList raster caching |
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.
nit: please link an issue here explaining what needs to be done, perhaps with a link to the commit(s) containing the start of the implementation.
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 will want to file issues eventually, but I feel odd filing an issue for a piece of code that isn't merged yet and this code may have a few more weeks before it can be merged.
| // << paint_bounds().right() << ", " | ||
| // << paint_bounds().bottom() << "] " | ||
| // << ops_vector_.size() << " ops"; | ||
| // TODO(flar): implement DisplayList raster caching |
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.
ditto
| static Uint32List _emptyInts = _emptyData.buffer.asUint32List(); | ||
| static Float32List _emptyFloats = _emptyData.buffer.asFloat32List(); | ||
| static List<Object> _deadObjects = List<Object>.empty(growable: false); | ||
| static List<NativeFieldWrapperClass2> _deadObjects = List<NativeFieldWrapperClass2>.empty(growable: false); |
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 this could just be static const List<NativeFieldWrapperClass2> _deadObjects = <NativeFieldWrapperClass2>[];.
For that matter, you could just inline that into the dispose method below.
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.
Originally I was going to use these as a flag for the StateError below to detect when a developer was trying to reuse a Canvas after endRecording, but I instead test for the Recorder link so I don't really need most of these values to be static any more.
I'm not sure of the cost of creating the empty byte data and then creating an int/float buffer from it, though.
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.
If it's const it basically is baked into the snapshot and is o(1) to construct at runtime
dnfield
left a comment
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.
This is looking better! Tests?
|
This prototype will likely be replaced by #26928 |
|
I'll close this PR under the assumption that it will be superseded by #26928. |
This PR is now ready for review.
This PR represents an alternative storage format for our rendering that removes a large dependency on the SkPicture format and improves our ability to inspect rendering operations for future optimization work.
An SkPicture will still be involved as the current way we handle text with SkParagraph objects requires an SkPicture to store the output of the paragraph rendering, but that limitation can be overcome with some more work.
This is just a first pass prototype for what it might look like to use our own recording format in lieu of SkPicture. The design is basic and skips a lot of advanced rendering for now:images, paragraphs, paths(all are implemented, although paragraphs are turned into SkPictures for now)filters and shaders(now implemented)Most of that can be implemented as soon as I add code to digest the "native Skia objects" used to represent those attributes and operations.This PR is being run through pre-submit testing with the new mechanism enabled to check for incompatibilities and fix them, but it will eventually be pushed with the mechanism turned off.(testing done, default set back to Skia versions of the objects - all unit tests pass using the DisplayList versions save for a test that makes assumptions that a generated Picture object will be using an SkPicture object natively - the test will have to be disabled if we switch over to the new display list code permanently).