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

Conversation

@yjbanov
Copy link
Contributor

@yjbanov yjbanov commented Oct 29, 2021

  • Feature-detect that the browser supports ImageDecoder.
  • If it does, use it for image decoding instead of the WASM codecs shipped with CanvasKit.
  • Bonus: when running felt test --debug, auto-open Chrome DevTools and maximize the window (it is annoying to have to do this manually every time).

The benefit of ImageDecoder is that it decodes asynchronously on a separate thread, so it doesn't block the frames while decoding. Once this API is supported in all browsers, we can remove the WASM codecs and reduce the size of CanvasKit by a few 100s of KB.

Fixes flutter/flutter#63397

@flutter-dashboard flutter-dashboard bot added the platform-web Code specifically for the web engine label Oct 29, 2021
@google-cla google-cla bot added the cla: yes label Oct 29, 2021
);
}

if (!await _isImageTypeSupported(contentType)) {

Choose a reason for hiding this comment

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

If you don't already have an async step to hide this under, you can just create the image decoder and get the exception there instead of adding any async delay here.

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.

// color space, typically SRGB, which is what we want.
colorSpaceConversion: 'default',

// Flutter doesn't give the developer to customize this, so if this is an

Choose a reason for hiding this comment

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

"the developer a way..."

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.

// wait until the image is fully decoded.
// package:js bindings don't work with getters that return a Promise, which
// is why js_util is used instead.
await js_util.promiseToFuture<void>(js_util.getProperty(webDecoder, 'completed'));

Choose a reason for hiding this comment

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

completed is for loading, not decoding. No decoding starts until decode() is called. This will ensure the frame count is accurate though.

preferAnimation: true,
));

await js_util.promiseToFuture<void>(webDecoder.tracks.ready);

Choose a reason for hiding this comment

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

This is all that's needed unless you want to ensure frame count is fully known.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason, until this morning (when I was forced to update my Chrome), if I didn't also await ImageDecoder.completed I would get different values for VideoFrame.format. It would flip between BGRX and I420. I assumed there was some race between some internal work that ImageDecoder does and the call to decode() (e.g. progressive loading of images).

With the latest version of Chrome I cannot reproduce this any more, but I'm not sure what behavior to expect. Since I changed the code to load the VideoFrame straight to texImage2D, it probably doesn't matter. We should be able to handle both. The only thing that might be surprising to the user (and perhaps to our goldens test infrastructure) is if the image is sometimes decoded at full resolution and sometimes partially.

Choose a reason for hiding this comment

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

Ah, Chrome will opportunistically use YUV decoding, but it depends on if all the frame data is available. So waiting for completed will ensure it's YUV if the image format allows it and Chrome thinks it can decode to it.

In cases where a YUV capable image is decoded to BGRX it's still full resolution, it shouldn't muddle any pixel tests, but I'm not sure off the top of my head.

// surface, if necessary, or reuse the existing one.
final CkSurface ckSurface = SurfaceFactory.instance.baseSurface.createOrUpdateSurface(ui.window.physicalSize);
final SkSurface skSurface = ckSurface.surface;
final SkImage? skImage = skSurface.makeImageFromTextureSource(

Choose a reason for hiding this comment

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

You can also call copyTo to get the raw pixel bytes. But if your destination is the gpu drawImage/texImage straight from the VideoFrame is definitely better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, preferably it should go straight to the GPU. However, in a follow-up PR I'll add support for target size, and since not all codecs support resizing, we'll need the pixels in the CPU memory, which is when copyTo will come handy.

/// type. It should therefore contain the most popular file formats at the
/// top, and less popular towards the bottom.
static const List<ImageFileFormat> values = <ImageFileFormat>[
// PNG

Choose a reason for hiding this comment

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

AVIF is also supported by Chrome if you want.

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 do, but I don't know how to detect it :)

Copy link

@dalecurtis dalecurtis Nov 1, 2021

Choose a reason for hiding this comment

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

It's slightly complicated if you want to do precise detection, but 'ftyp' in the first 16 bytes is good enough to detect an ISO-BMFF image type -- you'll fail later during configure if it's not avif and instead heic or a video.

Complex way: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc;l=504;drc=fd8802b593110ea18a97ef044f8a40dd24a622ec

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fantastic! Thanks for the link. I'll add it.

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.

@yjbanov yjbanov force-pushed the web-codecs branch 3 times, most recently from bdb0a5f to 1c6be9a Compare November 2, 2021 20:03
@yjbanov yjbanov marked this pull request as ready for review November 2, 2021 22:45
@yjbanov yjbanov force-pushed the web-codecs branch 2 times, most recently from b5d373a to 2ebde7c Compare November 3, 2021 21:38
Copy link
Contributor

@harryterkelsen harryterkelsen left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link

@dalecurtis dalecurtis left a comment

Choose a reason for hiding this comment

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

lgtm % a couple of nits

@@ -0,0 +1,436 @@
// Copyright 2013 The Flutter Authors. All rights reserved.

Choose a reason for hiding this comment

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

2021?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Our project uses 2013 everywhere. Using a future date is problematic, but using an older date is fine.

);
}

final _ImageDecoder webDecoder = _ImageDecoder(_ImageDecoderOptions(

Choose a reason for hiding this comment

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

I think you want this in the try/catch 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.

Done.

frame.displayWidth,
frame.displayHeight,
);

Choose a reason for hiding this comment

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

If you're done with frame at this point it's good practice to call frame.close() to free up memory.

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.

Copy link
Contributor

@mdebbar mdebbar left a comment

Choose a reason for hiding this comment

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

LGTM.

The Linux Fuchsia failure looks like an infra failure, so I triggered a re-run.

EDIT: The re-run I triggered never happened. I tried multiple times but the re-run never runs for some reason. Maybe because I'm not the author of the PR?

@yjbanov
Copy link
Contributor Author

yjbanov commented Nov 11, 2021

Update: found a bug where the image is lost when moved across GL contexts. Waiting for a CanvasKit-side fix before merging this.

@CaseyHillers CaseyHillers changed the base branch from master to main November 15, 2021 18:13
@yjbanov yjbanov force-pushed the web-codecs branch 4 times, most recently from 6987dd1 to 0875758 Compare November 18, 2021 00:15
@yjbanov yjbanov changed the base branch from master to main November 18, 2021 00:18
@yjbanov
Copy link
Contributor Author

yjbanov commented Nov 18, 2021

CanvasKit fix landed, so I will merge this as soon as it goes green.

@skia-gold
Copy link

Gold has detected about 1 new digest(s) on patchset 1.
View them at https://flutter-engine-gold.skia.org/cl/github/29419

@skia-gold
Copy link

Gold has detected about 1 new digest(s) on patchset 1.
View them at https://flutter-engine-gold.skia.org/cl/github/29419

@yjbanov
Copy link
Contributor Author

yjbanov commented Nov 18, 2021

The luci-engine status seems to be incorrect again.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cla: yes platform-web Code specifically for the web engine

5 participants