Adds Texture.toImage() method#183046
Conversation
8fece2b to
0ad59f0
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces a new feature to create a dart:ui.Image from a backend texture, which is useful for integrating external data sources with fragment shaders. The implementation adds a GetTextureImage method to the Texture interface and provides implementations for various platforms (Android, Darwin, Embedder). A new DlImageTextureRegistry class is added to manage the creation of images from texture IDs. The public API is exposed through getImageFromTexture in dart:ui and a new getImage method on TextureBox. The changes are consistent and include relevant tests. My main feedback is to refactor some duplicated code in the new DlImageTextureRegistry class to improve maintainability.
Note: Security Review did not run due to the size of the PR.
mboetger
left a comment
There was a problem hiding this comment.
LGTM from the android side of things - did you mean to add ios reviewers?
hellohuanlin
left a comment
There was a problem hiding this comment.
The embedder part is quite minimum. This is probably better to be reviewed by the engine team.
|
Oh thanks guys, I was planning on adding reviewers manually once all the CI lights had turned green. I'll get someone on the engine team to take a look. |
I think with DlImageTextureRegistry, the texture is copied whenever skia_image() or impeller_texture() is called, right? So the DlImage's underlying SkImage/impeller::Texture that gets returned is not the same static image with each call. And that's intended, because the purpose of this is to allow a non-static DlImage (like from a camera or video playback) to be used with fragment shaders, right? I think that's different from the semantics as other DlImages, where their returned skia_image() or impeller_texture() is a static image. Another question I have after looking at this for a while: With DlImageTextureRegistry, skia_image() and impeller_texture() directly uses snapshot_delegate and calls texture->GetTextureImage(). I think this is only allowed on the raster thread. So skia_image() and impeller_texture() can only be called from the raster thread. Is this a problem? I think skia_image() and impeller_texture() do currently get called from the UI thread. The other DlImage subclasses seem to have this whole ImageWrapper pattern which contains their usage of snapshot_delegate to be within a raster thread task runner. The resulting SkImage/impeller::Texture gets cached so it can be returned in calls to skia_image() and impeller_texture() from the UI thread. But DlImageTextureRegistry doesn't do this, and uses snapshot_delegate and calls texture->GetTextureImage() directly from skia_image() and impeller_texture(). |
Yes, this is correct. It creates an image each time. I don't think that's an issue, but we could cache the result if we wanted to to make sure it always returns the same image.
I don't think those methods are called on the UI thread. The way it works is
It doesn't use the delegate until the image is read from the raster thread as noted above. The closest analog to this would be |
They're called for things like flutter/engine/src/flutter/display_list/image/dl_image.h Lines 127 to 136 in 24c19ed
flutter/engine/src/flutter/lib/ui/painting/image.cc Lines 45 to 48 in 24c19ed and other places like this too. I'm not completely confident on what threads may end up calling these, but I think these might be called from the UI thread. |
Hmm yea. That's also the case for There is an assert in DlDeferredImageGPUImpeller::ImageWrapper::texture() that makes sure it's run on the raster thread. |
|
Right. From my previous comment: "The other DlImage subclasses seem to have this whole ImageWrapper pattern which contains their usage of snapshot_delegate to be within a raster thread task runner". When a DlDeferredImageGPUImpeller is created, it creates a DlDeferredImageGPUImpeller::ImageWrapper, which starts a task on the raster thread: The raster thread task copies the pixels into |
It's not safe or correct though. There is a race where one thread is potentially reading the texture variable while another is writing it. There is also a window where you ask for the texture on the ui thread before the raster thread is created it. |
|
We can mimic that behavior though by firing off a task for the raster thread though. I can make it as safe as the deferred images. |
The design of The The value returned by There probably does need to be some additional synchronization around the reading/writing of the The Do you recall why you added that assert? I don't think it should be there - it will trigger if an app reads |
I don't recall. It may have been inferred from the lack of thread-safety between the task setting the variable and the function reading it. |
|
@b-luk I removed the skia implementation for now so we can avoid looking at what happens when we lose access to the GPU. Although, I think we could implement that easily and we can do that as a followup if we want. I've also matched the semantics of deferred dl image and I made it safe by using atomics with the texture pointer. |
…ements * Use atomic read/write operations on ImageWrapper::texture_ (which is accessed from both the raster and UI threads) * Remove an assert that restricted ImageWrapper::texture() to the raster thread * Avoid calling DlImage::skia_image() on the UI thread in CanvasImage::colorSpace(). The DlDeferredImageGPUSkia::skia_image() implementation is only usable on the raster thread. See discussion at flutter#183046
…ements (#183429) * Use atomic read/write operations on ImageWrapper::texture_ (which is accessed from both the raster and UI threads) * Remove an assert that restricted ImageWrapper::texture() to the raster thread * Avoid calling DlImage::skia_image() on the UI thread in CanvasImage::colorSpace(). The DlDeferredImageGPUSkia::skia_image() implementation is only usable on the raster thread. See discussion at #183046
…ements (flutter#183429) * Use atomic read/write operations on ImageWrapper::texture_ (which is accessed from both the raster and UI threads) * Remove an assert that restricted ImageWrapper::texture() to the raster thread * Avoid calling DlImage::skia_image() on the UI thread in CanvasImage::colorSpace(). The DlDeferredImageGPUSkia::skia_image() implementation is only usable on the raster thread. See discussion at flutter#183046
|
I'm going to cancel this PR. I have had some conversations about this feature offline and I think we could potentially come up with a technical solution, however if you look at the PR, it requires a non-trivial addition to the engine. The payoff isn't that big either for the effort or the maintenance cost. There is a clear workaround for this feature, which is to use backdrop filter's on Texture widgets. On opengles we can't even get a full optimization where we remove the copy because of limitations in external textures. This PR today implements this feature but it has no performance advantage for vulkan or metal, that would require more investment. This feature just started to creep deeper and deeper. Having a design doc beforehand would have been helpful, but knowing the time required to invest in this, it probably wouldn't have been worth it. |
|
Would it be worth it to automatically use the work around (can we detect
the situation)
…On Tue, Mar 10, 2026, 5:46 PM gaaclarke ***@***.***> wrote:
*gaaclarke* left a comment (flutter/flutter#183046)
<#183046 (comment)>
I'm going to cancel this PR. I have had some conversations about this
feature offline and I think we could potentially come up with a technical
solution, however if you look at the PR, it requires a non-trivial addition
to the engine.
The payoff isn't that big either for the effort or the maintenance cost.
There is a clear workaround for this feature, which is to use backdrop
filter's on Texture widgets. On opengles we can't even get a full
optimization where we remove the copy because of limitations in external
textures. This PR today implements this feature but it has no performance
advantage for vulkan or metal, that would require more investment.
This feature just started to creep deeper and deeper. Having a design doc
beforehand would have been helpful, but knowing the time required to invest
in this, it probably wouldn't have been worth it.
—
Reply to this email directly, view it on GitHub
<#183046 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAIDVLCGWG3OQSKBHBJVOJD4QCEKVAVCNFSM6AAAAACWCI2EB6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DAMZUGY3DSOBSGU>
.
You are receiving this because your review was requested.Message ID:
***@***.***>
|
We can't detect the situation because you are not able to express it with the current API. Maybe with a small tweak to the API we could implement it as the workaround under the covers. So, for example, if we added Benson's idea, Workaround today: ImageFiltered(
imageFilter: ImageFilter.shader(fragmentShader),
child: Texture(textureId: yourTextureId),
)With the hypothetical change: class MyTexturePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
shader.setTextureSampler(0, yourTextureId);
final paint = Paint()..shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}Maybe we can come back to this in the future. I'm ready to move on for now. Thanks for the suggestion. |
…ements (flutter#183429) * Use atomic read/write operations on ImageWrapper::texture_ (which is accessed from both the raster and UI threads) * Remove an assert that restricted ImageWrapper::texture() to the raster thread * Avoid calling DlImage::skia_image() on the UI thread in CanvasImage::colorSpace(). The DlDeferredImageGPUSkia::skia_image() implementation is only usable on the raster thread. See discussion at flutter#183046
…ements (flutter#183429) * Use atomic read/write operations on ImageWrapper::texture_ (which is accessed from both the raster and UI threads) * Remove an assert that restricted ImageWrapper::texture() to the raster thread * Avoid calling DlImage::skia_image() on the UI thread in CanvasImage::colorSpace(). The DlDeferredImageGPUSkia::skia_image() implementation is only usable on the raster thread. See discussion at flutter#183046
fixes #136025
This PR allows developers to create dart:ui Image's from external textures, just as one would create a Texture widget. It also allows them to get the dart:ui Image from a Texture instance. This allows developers to pipe in external data sources like the camera or video playback through to fragment shaders.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.