Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@flar
Copy link
Contributor

@flar flar commented Mar 25, 2021

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)
  • transform(4x4) (we pass the appropriate parts of the transform to Skia as they support, but our bounds calculations in the Dart code may be inaccurate for anything beyond 2x3)

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).

@flar flar added the Work in progress (WIP) Not ready (yet) for review! label Mar 25, 2021
@flutter-dashboard
Copy link

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.

@chinmaygarde
Copy link
Member

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?

@ds84182
Copy link
Contributor

ds84182 commented Apr 3, 2021

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.

@flar
Copy link
Contributor Author

flar commented Apr 5, 2021

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.

@zanderso
Copy link
Member

zanderso commented May 6, 2021

What is the next step for this?

@flar
Copy link
Contributor Author

flar commented May 6, 2021

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.

@flar flar force-pushed the DisplayListPicture-implementation branch from b914126 to bb77c80 Compare May 7, 2021 02:42
@flar flar removed the Work in progress (WIP) Not ready (yet) for review! label May 10, 2021
Comment on lines 202 to 209
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);
Copy link
Contributor

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?

int entrySaveCount = canvas->getSaveCount();
Iterator it(this);
while (it.HasOp()) {
FML_LOG(INFO) << DescribeNextOp(it);
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@dnfield
Copy link
Contributor

dnfield commented May 11, 2021

Very cool, I'm excited for this.

Would you consider breaking this up into some smaller PRs to ease review?

Copy link
Contributor

@yjbanov yjbanov left a 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());
Copy link
Contributor

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

@yjbanov
Copy link
Contributor

yjbanov commented May 12, 2021

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.

@yjbanov
Copy link
Contributor

yjbanov commented May 12, 2021

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.

On the web we'll be soon migrating from drawParagraph to drawGlyphs (for memory reasons), so we won't need SkParagraph for pictures. Perhaps it will be useful here too.

@flar
Copy link
Contributor Author

flar commented May 13, 2021

@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.

@flar
Copy link
Contributor Author

flar commented May 13, 2021

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.

On the web we'll be soon migrating from drawParagraph to drawGlyphs (for memory reasons), so we won't need SkParagraph for pictures. Perhaps it will be useful here too.

That would be something for @jason-simmons to consider and comment on.

Comment on lines +318 to +320
const SkImage* GetImage() { return (refs++)->image.get(); }
const SkVertices* GetVertices() { return (refs++)->vertices.get(); }
const SkPicture* GetSkPicture() { return (refs++)->picture.get(); }
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor

@dnfield dnfield left a 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?

@flar
Copy link
Contributor Author

flar commented Jun 24, 2021

This prototype will likely be replaced by #26928

@flar flar changed the title Display list picture prototype [WIP] Display list picture prototype Jun 24, 2021
@zanderso
Copy link
Member

I'll close this PR under the assumption that it will be superseded by #26928.

@zanderso zanderso closed this Jun 24, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cla: yes Work in progress (WIP) Not ready (yet) for review!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants