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

Conversation

@dnfield
Copy link
Contributor

@dnfield dnfield commented Aug 2, 2022

Fixes flutter/flutter#108389

This is very similar to #34931, but does not require new Skia API. All of the differences from that patch are in the last commit (d7c89e0)

This patch assumes that the DisplayListCanvasDispatcher is only ever used to render things to Skia. AFAICT, we make the same assumption when using TextureLayers and other Skia objects - @flar fyi.

This patch changes the implementation of toByteData because SkImage::MakeFromTexture does not appear to support SkImage::makeRasterImage on the GL and Vulkan backends (but does support it on Metal .. hah).

@jonahwilliams fyi

@bsalomon @egdaniel fyi

@dnfield dnfield requested a review from jason-simmons August 2, 2022 00:33
@dnfield dnfield marked this pull request as ready for review August 2, 2022 00:33
@bsalomon
Copy link
Contributor

bsalomon commented Aug 2, 2022

Glad this approach worked! makeRasterImage should work on GL and Vulkan, though.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 2, 2022

Moving this to draft while I work out some of the CI issues. I fixed a clang-tidy issue that appears to have broken some tests, and I'm having issues with hte color types on Linux

@dnfield dnfield marked this pull request as draft August 2, 2022 16:57
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

std::vector<RasterCacheItem*>& cacheable_items() { return cacheable_items_; }

TextureRegistry& texture_regitry() { return texture_registry_; }
std::shared_ptr<TextureRegistry> texture_regitry() {
Copy link
Member

Choose a reason for hiding this comment

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

typo: "registry"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


// |DlImage|
// This method is only safe to call from the raster thread.
// The image created here must not be added to the unref queue.
Copy link
Member

Choose a reason for hiding this comment

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

I would make this warning more general - callers should not hold long term references to this image and should only use it for the immediate painting operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


void TextureRegistry::RegisterContextDestroyedListener(
std::weak_ptr<ContextDestroyedListener> image) {
images_.push_back(std::move(image));
Copy link
Member

Choose a reason for hiding this comment

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

The images_ list will grow unbounded until the context is destroyed.

DlDeferredImageGPU holds a reference to the TextureRegistry - could the DlDeferredImageGPU dtor's raster thread task remove the ImageWrapper from the TextureRegistry?

IIRC a previous version of this patch had an unregister mechanism like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh yeah. I was thinking of another place I used a pattern like this where it gets visited on every frame, but that won't happen here. I'll add back the unregister method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed this.


private:
GrBackendTexture texture_;
sk_sp<GrDirectContext> context_;
Copy link
Member

Choose a reason for hiding this comment

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

This is a potential risk, but I'm not sure what to do about it.

Holding a strong reference to the GrDirectContext here is going to make it harder to understand and control how the GrDirectContext is deleted during engine shutdown. Or conceivably someone could call GrDirectContext::abandonContext while one of these objects is still referencing the GrDirectContext.

On the Android embedder the engine group context sharing mechanism should ensure that the GrDirectContext is not abandoned until after the engine is shut down. So by that time the DlDeferredImageGpu instances should all be GCed and the TextureRegistry.images_ should be cleaned up.

But I don't know if there are other platforms where the GrDirectContext has a different lifecycle that this might conflict with (for example, the embedding library)

I'm wondering if these images should be treated specially instead of making them look like subclasses of DlImage?

Would it be feasible to create a display list object that explicitly wraps a GrBackendTexture?
Or an API where the caller must supply a GrDirectContext to get an SkImage, and the caller therefore knows that the resulting image is tied to the GrDirectContext and must not be held beyond the context's lifetime?

@chinmaygarde @flar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If they call abandonContext, they should notify the TextureRegistry::onGrContextDestroyed which will give this class a chance to delete the texture and then release its reference to GrDirectContext.

In fact, that will happen during rasterizer teardown and synchronously complete before the rasterizer lets go to its surface_ pointer, which holds the GrDirectContext.

I'm not really sure what benefit we'd get from having a special object that isn't a subclass of DlImage. It's probably possible but it would still hav eto go all the same places DlImage goes today (drawImage, drawImageRect, drawImageNine, drawAtlas, ImageShader).

Copy link
Member

Choose a reason for hiding this comment

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

TextureRegistry::onGrContextDestroyed is called during the Rasterizer::Teardown that happens when the engine loses its rendering surface (for example, when you pause and resume an app in the Android task switcher)

So if Dart code calls toImageSync and holds a reference to the resulting Image across a pause/resume cycle, then the Image will not render properly after the app resumes.

(onGrContextDestroyed is now a misnomer on Android - in the past the GrDirectContext was destroyed at that time, but with the engine group context sharing scheme the GrDirectContext is being kept alive beyond that call.)

I'm not sure when the right time would be to clean up any outstanding DlDeferredImageGPU::ImageWrapper textures. Doing it during or immediately before Rasterizer destruction might work, but I don't know if that risks keeping a GrDirectContext instance alive longer than intended on some platform.

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 think it's fine to require that these images get recreated on application resume - they will throw if you try to draw them after at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

An application would be left with two choices - either explicitly make a host copy with toByteData to re-upload on resume, or re-create the image on resume with toImageSync again using the same picture object.

Copy link
Member

Choose a reason for hiding this comment

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

I tried experimenting with this, and it looks like that is not how these images currently behave. Drawing an image after a pause/resume produces a black rectangle with no exception.

Does DlDeferredImageGPU need to track whether OnGrContextDestroyed was called in order to implement this?

It should also be made clear in the API documentation that these are not general purpose images. They should only be used temporarily and should not be stored or cached anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I'm missing setting the error string when the context is destroyed.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, throwing in this case might be too strict.

Even if the image is created, used, and then quickly discarded, there still might be a risk that the user pauses the app at the exact wrong moment and the OnGrContextDestroyed happens between creation and use. The error might need to be a warning logged when in debug mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Latest change re-creates the texture when the texture registry tells us the gr context has been restored. It does this by keeping the displaylist around.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 2, 2022

Linux is having issues with the GPU path, but that embedding is having issues in general right now. I've filed a bug to track enabling the GPU path on Linux and for now am having it do a slower software based path.

@dnfield dnfield marked this pull request as ready for review August 2, 2022 21:17
FML_DCHECK(raster_task_runner_->RunsTasksOnCurrentThread());

if (texture_.isValid()) {
unref_queue_->DeleteTexture(std::move(texture_));
Copy link
Member

Choose a reason for hiding this comment

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

Reset texture_ to an invalid GrBackendTexture
(AFAICT GrBackendTexture does not have a move constructor that will invalidate texture_)

Also drop the context_ reference to the GrDirectContext

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


for (const auto& [id, weak_image] : images_) {
if (auto image = weak_image.lock()) {
image->OnGrContextCreated();
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to do this lazily for each image instead of doing it for all images when the context becomes available?

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 think if we did it lazily it would be in the image itself rather than here - IOW the image object would have to track state and re-create the image on demand.

I'm inclined to think that it'd be better to just take time resuming than to potentially jank during an animation to re-create an image. I'm not really sure which will be better - if an application has a lot of these images it could significantly slow down resume, but there will be a number of problems with using that many images anyway e.g. memory constraints on the device.

if (!wrapper) {
return;
}
auto result = wrapper->snapshot_delegate_->MakeGpuImage(
Copy link
Member

Choose a reason for hiding this comment

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

Check the validity of the snapshot delegate fml::WeakPtr before dereferencing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

// |DlImage|
DlDeferredImageGPU::~DlDeferredImageGPU() = default;
DlDeferredImageGPU::~DlDeferredImageGPU() {
auto image_wrapper = std::atomic_load(&image_wrapper_);
Copy link
Member

Choose a reason for hiding this comment

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

The use of atomic_load with image_wrapper_ is no longer necessary.

image_wrapper_ is now const and is not mutated until the DlDeferredImageGPU is destructed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh yeah, done

@jason-simmons
Copy link
Member

LGTM for concurrency and object lifetime issues

@chinmaygarde Any feedback on the API and its interaction with the rasterizer subsystem?

@dnfield dnfield merged commit 4ac52f2 into flutter:main Aug 9, 2022
@dnfield dnfield deleted the gpu_img_safe_2 branch August 9, 2022 20:22
@chinmaygarde
Copy link
Member

The tree became red after this patch but the failure is a very brittle test. Still investigating if what happened.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 9, 2022

I had to update that value as part of these changes but maybe there was a merge conflict

dnfield added a commit that referenced this pull request Aug 9, 2022
dnfield added a commit that referenced this pull request Aug 9, 2022
dnfield added a commit to dnfield/engine that referenced this pull request Aug 9, 2022
@dnfield dnfield mentioned this pull request Aug 9, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 9, 2022
emilyabest pushed a commit to emilyabest/engine that referenced this pull request Aug 12, 2022
emilyabest pushed a commit to emilyabest/engine that referenced this pull request Aug 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove Picture/Scene toImageSync from 3.3 beta

4 participants