Bug 168814 - Asynchronous image decoding should consider the drawing size if it is smaller than the size of the image
Summary: Asynchronous image decoding should consider the drawing size if it is smaller...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Images (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Said Abou-Hallawa
URL:
Keywords: InRadar
Depends on:
Blocks: 155322 165039
  Show dependency treegraph
 
Reported: 2017-02-23 18:19 PST by Said Abou-Hallawa
Modified: 2017-03-07 19:54 PST (History)
11 users (show)

See Also:


Attachments
Patch (41.41 KB, patch)
2017-02-23 18:31 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (48.77 KB, patch)
2017-02-24 09:56 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (50.51 KB, patch)
2017-02-24 16:47 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews113 for mac-elcapitan (1.81 MB, application/zip)
2017-02-24 19:21 PST, Build Bot
no flags Details
Patch (53.35 KB, patch)
2017-02-27 18:25 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (52.16 KB, patch)
2017-02-28 10:30 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (54.45 KB, patch)
2017-02-28 22:20 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (54.74 KB, patch)
2017-03-01 09:44 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (54.62 KB, patch)
2017-03-01 11:41 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (61.46 KB, patch)
2017-03-05 17:08 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews100 for mac-elcapitan (901.56 KB, application/zip)
2017-03-05 17:49 PST, Build Bot
no flags Details
Patch (65.02 KB, patch)
2017-03-06 17:51 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (65.13 KB, patch)
2017-03-07 11:32 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (65.34 KB, patch)
2017-03-07 18:19 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews114 for mac-elcapitan (1.67 MB, application/zip)
2017-03-07 19:33 PST, Build Bot
no flags Details
Patch (65.34 KB, patch)
2017-03-07 19:38 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Said Abou-Hallawa 2017-02-23 18:19:45 PST
If the size of the destinationRect is smaller than the image source size (e.g. 3000x3000 pixels), CGImageSourceCreateThumbnailAtIndex() is slower than CGImageSourceCreateImageAtIndex() in decoding this image. To overcome this problem, the entry (kCGImageSourceThumbnailMaxPixelSize, max(destinationRect.width, destinationRect.height)) is added to the options dictionary when calling CGImageSourceCreateThumbnailAtIndex(). This will avoid copying a large block of memory for the unscaled bitmap image.
Comment 1 Said Abou-Hallawa 2017-02-23 18:31:57 PST
Created attachment 302625 [details]
Patch
Comment 2 Said Abou-Hallawa 2017-02-24 09:56:48 PST
Created attachment 302672 [details]
Patch
Comment 3 Said Abou-Hallawa 2017-02-24 10:43:03 PST
Comment on attachment 302672 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=302672&action=review

> Source/WebCore/loader/cache/CachedImage.h:129
> +        URL sourceUrl() const override { return m_cachedImages[0]->url(); }

Is m_cachedImages guaranteed to have at least one entry?

Yes. Before <http://trac.webkit.org/changeset/209914>, CachedImage was inheriting from ImageObserver and all the virtual functions of ImageObserver were implemented inside CachedImage. After this change, CachedImage holds a RefPtr<CachedImageObserver> named m_imageObserver. This pointer is initialized in two places. (1) CachedImage::createImage(): this function creates a new m_image and the m_imageObserver is also created. And this (the CachedImage) is added to m_imageObserver. (2) CachedImage::setBodyDataFrom(): in this function m_image is cloned from the other CachedImage to this CachedImage. The m_imageObserver also references the m_imageObserver of the other CachedImage. Then this (the CachedImage) is added to m_imageObserver.
Comment 4 Simon Fraser (smfr) 2017-02-24 11:54:22 PST
Comment on attachment 302672 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=302672&action=review

> Source/WebCore/ChangeLog:26
> +        -- if (sizeForDrawing && sizeForDrawing.value().isZero()): async image decoding
> +           is needed but do not set kCGImageSourceThumbnailMaxPixelSize. This is the
> +           case where destinationRect.size() is larger than the image sourceSize.
> +        -- if (sizeForDrawing && !sizeForDrawing.value().isZero()): async image decoding
> +           is needed and kCGImageSourceThumbnailMaxPixelSize has to be set. The is the
> +           case where destinationRect.size() is smaller than the image sourceSize.

Do you really need to differentiate between these two states? Why not just always pass down sizeForDrawing, and make the determination based on that vs. the native size of the image?

That would avoid this confusing 3-state system.
Comment 5 Said Abou-Hallawa 2017-02-24 16:47:12 PST
Created attachment 302709 [details]
Patch
Comment 6 Said Abou-Hallawa 2017-02-24 16:57:47 PST
Comment on attachment 302709 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=302709&action=review

> Source/WebCore/platform/graphics/BitmapImage.cpp:240
> +bool BitmapImage::isAsyncDecodingRequired()

Can this be const?

It can but it will be very involving because BitmapImage::canAnimate() is not const. It is not const because it calls BitmapImage::frameCount() which is not const. BitmapImage::frameCount() is not const because ImageFrameCache::frameCount() has to cache the ImageFrameCache::m_frameCount if it is not set.

This a general problem with most of the query functions in BitmapImage, ImageSource and ImageFrameCache. Most of the properties are lazily calculated in these query functions. So either, we keep these query functions non-const. Or we make all the image properties mutable.
Comment 7 Build Bot 2017-02-24 19:20:42 PST
Comment on attachment 302709 [details]
Patch

Attachment 302709 [details] did not pass mac-debug-ews (mac):
Output: http://webkit-queues.webkit.org/results/3188614

New failing tests:
media/modern-media-controls/macos-fullscreen-media-controls/macos-fullscreen-media-controls-drag.html
media/modern-media-controls/macos-fullscreen-media-controls/macos-fullscreen-media-controls-drag-is-prevented-over-button.html
Comment 8 Build Bot 2017-02-24 19:21:03 PST
Created attachment 302727 [details]
Archive of layout-test-results from ews113 for mac-elcapitan

The attached test failures were seen while running run-webkit-tests on the mac-debug-ews.
Bot: ews113  Port: mac-elcapitan  Platform: Mac OS X 10.11.6
Comment 9 Radar WebKit Bug Importer 2017-02-27 14:40:29 PST
<rdar://problem/30742918>
Comment 10 Said Abou-Hallawa 2017-02-27 18:25:06 PST
Created attachment 302908 [details]
Patch
Comment 11 Said Abou-Hallawa 2017-02-28 10:30:04 PST
Created attachment 302947 [details]
Patch
Comment 12 Tim Horton 2017-02-28 16:32:47 PST
Comment on attachment 302947 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=302947&action=review

> Source/WebCore/platform/graphics/ImageFrameCache.h:144
> +        IntSize sizeForDraw;

I would vote for "sizeForDrawing" or...  something else, this sounds weird.

> Source/WebCore/platform/graphics/cg/ImageDecoderCG.cpp:88
> -    static NeverDestroyed<RetainPtr<CFDictionaryRef>> optionsOnDemand = createImageSourceOptions(SubsamplingLevel::First, DecodingMode::OnDemand);
> -    static NeverDestroyed<RetainPtr<CFDictionaryRef>> optionsImmediate = createImageSourceOptions(SubsamplingLevel::First, DecodingMode::Immediate);
> +static RetainPtr<CFMutableDictionaryRef> imageSourceAsyncOptions(SubsamplingLevel subsamplingLevel)

So now we're building CFDictionaries all over the place, where they used to be static? That seems not great.

> Source/WebCore/platform/graphics/win/ImageDecoderDirect2D.cpp:183
> -NativeImagePtr ImageDecoder::createFrameImageAtIndex(size_t index, SubsamplingLevel subsamplingLevel, DecodingMode) const
> +NativeImagePtr ImageDecoder::createFrameImageAtIndex(size_t index, SubsamplingLevel subsamplingLevel, , const std::optional<IntSize>&) const

This doesn't look right (, ,)
Comment 13 Said Abou-Hallawa 2017-02-28 22:20:09 PST
Created attachment 303040 [details]
Patch
Comment 14 Said Abou-Hallawa 2017-02-28 22:21:52 PST
Comment on attachment 302947 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=302947&action=review

>> Source/WebCore/platform/graphics/ImageFrameCache.h:144
>> +        IntSize sizeForDraw;
> 
> I would vote for "sizeForDrawing" or...  something else, this sounds weird.

Done.

>> Source/WebCore/platform/graphics/cg/ImageDecoderCG.cpp:88
>> +static RetainPtr<CFMutableDictionaryRef> imageSourceAsyncOptions(SubsamplingLevel subsamplingLevel)
> 
> So now we're building CFDictionaries all over the place, where they used to be static? That seems not great.

I think I fixed this issue in the new patch.

>> Source/WebCore/platform/graphics/win/ImageDecoderDirect2D.cpp:183
>> +NativeImagePtr ImageDecoder::createFrameImageAtIndex(size_t index, SubsamplingLevel subsamplingLevel, , const std::optional<IntSize>&) const
> 
> This doesn't look right (, ,)

Fixed. There is not a port to build Windows D2D so EWS did not catch it.
Comment 15 Said Abou-Hallawa 2017-03-01 09:44:56 PST
Created attachment 303073 [details]
Patch
Comment 16 Said Abou-Hallawa 2017-03-01 11:12:23 PST
Comment on attachment 303073 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=303073&action=review

> Source/WebCore/platform/graphics/cg/ImageDecoderCG.cpp:389
> +    RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_nativeDecoder.get(), index, options.get()));

This is wrong. I deleted the call to CGImageSourceCreateThumbnailAtIndex by mistake. This will disable the async image decoding completely. There should be a call to CGImageSourceCreateImageAtIndex() inside the if (!sizeForDrawing) above. And another call to CGImageSourceCreateThumbnailAtIndex() should be inside the else block.
Comment 17 Simon Fraser (smfr) 2017-03-01 11:25:10 PST
(In reply to comment #16)
 This will disable the async image decoding completely.

Why didn't tests fail then?
Comment 18 Said Abou-Hallawa 2017-03-01 11:41:06 PST
Created attachment 303090 [details]
Patch
Comment 19 Said Abou-Hallawa 2017-03-01 11:43:04 PST
Comment on attachment 303090 [details]
Patch

I made sure the animated images are decoded asynchronously and the frame rate is high as without this patch.
Comment 20 Said Abou-Hallawa 2017-03-01 13:02:59 PST
(In reply to comment #17)
> (In reply to comment #16)
>  This will disable the async image decoding completely.
> 
> Why didn't tests fail then?

Because the current tests for animated image can only verify the correctness of the async image decoding path. These tests force async image decoding since the images they use are small and simple ones. They also set the m_desiredFrameDecodeTimeForTesting to fake a lengthy decoding time. The tests expect to get the frame drawn correctly but they do not test the frame is drawn because it went through a specific code path.

They do not test if we used CGImageSourceCreateThumbnailAtIndex() instead of CGImageSourceCreateImageAtIndex() in the async image decoding path. What makes it difficult to do such test is the difference between decoding a simple small image using one of these two functions and then drawing it can hardly be noticeable. Using CGImageSourceCreateImageAtIndex() to decode an image asynchronously will still generate a valid CGImageRef. The only problem here is the real deducing will happen later at the drawing time.
Comment 21 Said Abou-Hallawa 2017-03-05 17:08:36 PST
Created attachment 303487 [details]
Patch
Comment 22 Build Bot 2017-03-05 17:49:30 PST
Comment on attachment 303487 [details]
Patch

Attachment 303487 [details] did not pass mac-ews (mac):
Output: http://webkit-queues.webkit.org/results/3249414

New failing tests:
media/modern-media-controls/tracks-support/tracks-support-show-panel-after-dragging-controls.html
Comment 23 Build Bot 2017-03-05 17:49:34 PST
Created attachment 303490 [details]
Archive of layout-test-results from ews100 for mac-elcapitan

The attached test failures were seen while running run-webkit-tests on the mac-ews.
Bot: ews100  Port: mac-elcapitan  Platform: Mac OS X 10.11.6
Comment 24 Simon Fraser (smfr) 2017-03-06 14:41:49 PST
Comment on attachment 303487 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=303487&action=review

> Source/WebCore/platform/graphics/BitmapImage.cpp:245
> +    if (!((canAnimate() && allowAnimatedImageAsyncDecoding()) || (!canAnimate() && allowLargeImageAsyncDecoding())))

I feel this could be simplified.

> Source/WebCore/platform/graphics/ImageFrame.h:151
> +    std::optional<IntSize> m_sizeForDrawing;

Thinking about it some more, I think this is a fundamentally wrong change. I think we need to preserve the existing behavior that BitmapImage is agnostic to the size at which it's going to get pained.

Tim points out that CachedImage has something relevant: CachedImage::setContainerSizeForRenderer().
Comment 25 Said Abou-Hallawa 2017-03-06 17:51:17 PST
Created attachment 303593 [details]
Patch
Comment 26 Said Abou-Hallawa 2017-03-07 11:32:31 PST
Created attachment 303686 [details]
Patch
Comment 27 Said Abou-Hallawa 2017-03-07 12:11:55 PST
Comment on attachment 303487 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=303487&action=review

>> Source/WebCore/platform/graphics/BitmapImage.cpp:245
>> +    if (!((canAnimate() && allowAnimatedImageAsyncDecoding()) || (!canAnimate() && allowLargeImageAsyncDecoding())))
> 
> I feel this could be simplified.

I replaced this function by the two functions to removed the complication.

bool BitmapImage::isLargeImageAsyncDecodingRequired()
{
    return !canAnimate() && allowLargeImageAsyncDecoding() && (isAsyncDecodingForcedForTesting() || m_source.isAsyncDecodingRequired());
}

bool BitmapImage::isAnimatedImageAsyncDecodingRequired()
{
    return canAnimate() && allowAnimatedImageAsyncDecoding() && (isAsyncDecodingForcedForTesting() || m_source.isAsyncDecodingRequired());
}

>> Source/WebCore/platform/graphics/ImageFrame.h:151
>> +    std::optional<IntSize> m_sizeForDrawing;
> 
> Thinking about it some more, I think this is a fundamentally wrong change. I think we need to preserve the existing behavior that BitmapImage is agnostic to the size at which it's going to get pained.
> 
> Tim points out that CachedImage has something relevant: CachedImage::setContainerSizeForRenderer().

setContainerSizeForRenderer() is mainly used by the SVG to resize the FrameView.

For the BitmapImage, I think the sizeForDrawing is hidden by the ImageFrame. The BitmapImage still returns the native size of the image when it is requested. The ImageFrame is just transient thing. It can be deleted at any point if we go under memory stress for example. It knows when and how to rebuild itself. When drawing for a specific size, it tries to do some optimization but it gives the highest quality of the image. If BitmapImage tries to draw for a size whose MaxPixelSize is larger the MaxPixelSize of the ImageFrame sizeForDrawing, ImageFrame rebuilds a new NativeImage with the larger MaxPixelSize.
Comment 28 Simon Fraser (smfr) 2017-03-07 17:00:07 PST
Comment on attachment 303686 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=303686&action=review

> Source/WebCore/ChangeLog:9
> +        (e.g. 3000x3000 pixels), CGImageSourceCreateThumbnailAtIndex() is slower

Because the thumbnail is decoding at the native size of the image? Doesn't that also imply higher memory use?

> Source/WebCore/ChangeLog:17
> +        all the way from BitmapImage till ImageDecoder. If bool(sizeForDrawing) equals

till -> to

> Source/WebCore/ChangeLog:19
> +        asynchronously.

synchronously?

> Source/WebCore/platform/graphics/BitmapImage.cpp:319
> +        if (m_source.requestFrameAsyncDecodingAtIndex(nextFrame, m_currentSubsamplingLevel, *m_sizeForDrawing))
> +            LOG(Images, "BitmapImage::%s - %p - url: %s [requesting async decoding for nextFrame = %ld]", __FUNCTION__, this, sourceURL().characters8(), nextFrame);
> +        else
> +            LOG(Images, "BitmapImage::%s - %p - url: %s [cachedFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().characters8(), ++m_cachedFrameCount, nextFrame);

It's a bit odd to have the main decode happen in an if () statement whose result is only used for logging. This would be better as:

bool isAsyncDecode  = m_source.requestFrameAsyncDecodingAtIndex(nextFrame, m_currentSubsamplingLevel, *m_sizeForDrawing);
LOG(.... , isAsyncDecode, ...)

with the appropriate UNUSED_PARAM if LOG is not defined.

> Source/WebCore/platform/graphics/ImageFrame.cpp:128
> +static int maxPixelSize(const IntSize& size)

I would call this maxDimension or largerOfWidthAndHeight

> Source/WebCore/platform/graphics/ImageFrame.cpp:141
> +    return maxPixelSize(m_sizeForDecoding.last()) >= maxPixelSize(*sizeForDrawing);

Seems odd to compare a tall skinny image with a short, wide one. We may have to enhance this in future.
Comment 29 Said Abou-Hallawa 2017-03-07 18:19:26 PST
Created attachment 303754 [details]
Patch
Comment 30 Build Bot 2017-03-07 19:33:04 PST
Comment on attachment 303754 [details]
Patch

Attachment 303754 [details] did not pass mac-debug-ews (mac):
Output: http://webkit-queues.webkit.org/results/3262854

New failing tests:
media/track/track-in-band-style.html
Comment 31 Build Bot 2017-03-07 19:33:10 PST
Created attachment 303761 [details]
Archive of layout-test-results from ews114 for mac-elcapitan

The attached test failures were seen while running run-webkit-tests on the mac-debug-ews.
Bot: ews114  Port: mac-elcapitan  Platform: Mac OS X 10.11.6
Comment 32 Said Abou-Hallawa 2017-03-07 19:38:15 PST
Created attachment 303762 [details]
Patch
Comment 33 WebKit Commit Bot 2017-03-07 19:54:32 PST
Comment on attachment 303762 [details]
Patch

Clearing flags on attachment: 303762

Committed r213563: <http://trac.webkit.org/changeset/213563>
Comment 34 WebKit Commit Bot 2017-03-07 19:54:41 PST
All reviewed patches have been landed.  Closing bug.