Skip to content

Commit

Permalink
Cherry-pick 83d967c. rdar://118497211
Browse files Browse the repository at this point in the history
    2D Context get/putImageData cache copies cached image data twice
    https://bugs.webkit.org/show_bug.cgi?id=264927
    rdar://118497211

    Reviewed by Cameron McCormack.

    Store the PixelBuffer that was used for putImageData,
    avoid copying the the ImageData redundantly.

    Instead of copying during caching, copy with premultiply.
    This way we omit one memcpy for caching and one premultiply for the real
    putImageData.

    To simplify the implementation, removes the conservative approach
    where the caching would kick in at 3rd putImageData. Just cache
    the first.

    * Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:
    (WebCore::CanvasRenderingContext2DBase::cacheImageDataIfPossible):
    (WebCore::CanvasRenderingContext2DBase::takeCachedImageDataIfPossible const):
    (WebCore::CanvasRenderingContext2DBase::putImageData):
    * Source/WebCore/html/canvas/CanvasRenderingContext2DBase.h:

    Canonical link: https://commits.webkit.org/270975@main

Identifier: 267815.628@safari-7617-branch
  • Loading branch information
Dan Robson committed Dec 11, 2023
1 parent 8e02aee commit c52d8a9
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 76 deletions.
145 changes: 73 additions & 72 deletions Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1051,16 +1051,16 @@ void CanvasRenderingContext2DBase::clip(Path2D& path, CanvasFillRule windingRule
clipInternal(path.path(), windingRule);
}

static inline IntRect computeImageDataRect(const ImageBuffer& buffer, int width, int height, IntRect& destRect, const IntSize& destOffset)
static inline IntRect computeImageDataRect(const ImageBuffer& buffer, IntSize sourceSize, IntRect& destRect, IntPoint destOffset)
{
destRect.intersect(IntRect { 0, 0, width, height });
destRect.move(destOffset);
destRect.intersect(IntRect { { }, sourceSize });
destRect.moveBy(destOffset);
destRect.intersect(IntRect { { }, buffer.truncatedLogicalSize() });
if (destRect.isEmpty())
return destRect;
IntRect sourceRect { destRect };
sourceRect.move(-destOffset);
sourceRect.intersect(IntRect { 0, 0, width, height });
sourceRect.moveBy(-destOffset);
sourceRect.intersect(IntRect { { }, sourceSize });
return sourceRect;
}

Expand Down Expand Up @@ -2342,72 +2342,65 @@ ExceptionOr<Ref<ImageData>> CanvasRenderingContext2DBase::createImageData(int sw
return imageData;
}

static void roundTripThroughPremultipliedRepresentationForQuantization(unsigned bytesPerRow, uint8_t* data, const IntSize& size)
{
ConstPixelBufferConversionView source {
.format = {
.alphaFormat = AlphaPremultiplication::Unpremultiplied,
.pixelFormat = PixelFormat::RGBA8,
.colorSpace = DestinationColorSpace::SRGB(),
},
.bytesPerRow = bytesPerRow,
.rows = data,
};
PixelBufferConversionView destination {
.format = {
.alphaFormat = AlphaPremultiplication::Premultiplied,
.pixelFormat = PixelFormat::RGBA8,
.colorSpace = DestinationColorSpace::SRGB(),
},
.bytesPerRow = bytesPerRow,
.rows = data,
};
convertImagePixels(source, destination, size);

source.format.alphaFormat = AlphaPremultiplication::Premultiplied;
destination.format.alphaFormat = AlphaPremultiplication::Unpremultiplied;
convertImagePixels(source, destination, size);
}

void CanvasRenderingContext2DBase::evictCachedImageData()
{
if (m_cachedImageData)
m_cachedImageData->imageData = nullptr;
m_cachedImageData = std::nullopt;
}

CanvasRenderingContext2DBase::CachedImageData::CachedImageData(CanvasRenderingContext2DBase& context)
: evictionTimer(context, &CanvasRenderingContext2DBase::evictCachedImageData, 5_s)
CanvasRenderingContext2DBase::CachedImageData::CachedImageData(CanvasRenderingContext2DBase& context, Ref<ByteArrayPixelBuffer> imageData)
: imageData(WTFMove(imageData))
, evictionTimer(context, &CanvasRenderingContext2DBase::evictCachedImageData, 5_s)
{
}

static constexpr unsigned minimumConsecutiveCachedImageDataRequestsBeforeCaching = 2;
static constexpr unsigned imageDataSizeThresholdForCaching = 60 * 60;

bool CanvasRenderingContext2DBase::cacheImageDataIfPossible(ImageData& data, const IntPoint& destinationPosition, const IntRect& sourceRect)
RefPtr<ByteArrayPixelBuffer> CanvasRenderingContext2DBase::cacheImageDataIfPossible(const ImageData& imageData, const IntRect& sourceRect, const IntPoint& destinationPosition)
{
if (!destinationPosition.isZero() || !sourceRect.location().isZero() || sourceRect.size() != data.size() || sourceRect.size() != canvasBase().size())
return false;
if (!destinationPosition.isZero() || !sourceRect.location().isZero() || sourceRect.size() != imageData.size() || sourceRect.size() != canvasBase().size())
return nullptr;

if (data.colorSpace() != m_settings.colorSpace)
return false;
auto size = imageData.size();
if (size.area() > imageDataSizeThresholdForCaching)
return nullptr;

if (data.size().area() > imageDataSizeThresholdForCaching)
return false;
if (imageData.colorSpace() != m_settings.colorSpace)
return nullptr;

if (m_cachedImageData) {
if (++m_cachedImageData->requestCount >= minimumConsecutiveCachedImageDataRequestsBeforeCaching) {
m_cachedImageData->imageData = data.clone();
m_cachedImageData->evictionTimer.restart();
}
} else
m_cachedImageData.emplace(*this);
// Consider:
// * Real putImageData needs premultiply step.
// * Retrieve from cache needs to ensure premultiply + unpremultiply was made to simulate the real putImageData.
// * Caching needs at least a memcpy here.
// Instead of doing the plain memcpy, copy by doing premultiply here.
// This computation can be used for cache retrieval as well as the real putImageData.
// We're not doing RGBA -> BGRA swizzle here, as that is not needed for cache retrieval and
// the swizzle copy can be made at the putImageData copy site.
auto colorSpace = toDestinationColorSpace(imageData.colorSpace());
unsigned bytesPerRow = static_cast<unsigned>(size.width()) * 4u;
PixelBufferFormat cachedFormat { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, colorSpace };
auto cachedBuffer = ByteArrayPixelBuffer::tryCreate(cachedFormat, size);
if (!cachedBuffer)
return nullptr;
ConstPixelBufferConversionView source {
.format = { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, colorSpace },
.bytesPerRow = bytesPerRow,
.rows = imageData.data().data(),
};
PixelBufferConversionView destination {
.format = cachedFormat,
.bytesPerRow = bytesPerRow,
.rows = cachedBuffer->data().data(),
};
convertImagePixels(source, destination, size);

return true;
m_cachedImageData.emplace(*this, *cachedBuffer);
return cachedBuffer;
}

RefPtr<ImageData> CanvasRenderingContext2DBase::takeCachedImageDataIfPossible(const IntRect& sourceRect, PredefinedColorSpace colorSpace) const
{
if (!m_cachedImageData || !m_cachedImageData->imageData)
if (!m_cachedImageData)
return nullptr;

if (sourceRect != IntRect { { }, canvasBase().size() })
Expand All @@ -2419,14 +2412,23 @@ RefPtr<ImageData> CanvasRenderingContext2DBase::takeCachedImageDataIfPossible(co
if (colorSpace != m_settings.colorSpace)
return nullptr;

++m_cachedImageData->requestCount;

RefPtr result = m_cachedImageData->imageData.releaseNonNull();

if (result)
roundTripThroughPremultipliedRepresentationForQuantization(static_cast<unsigned>(result->size().width() * 4), result->data().data(), result->size());

return result;
Ref pixelBuffer = WTFMove(m_cachedImageData->imageData);
m_cachedImageData = std::nullopt;
auto size = pixelBuffer->size();
auto data = pixelBuffer->takeData();
unsigned bytesPerRow = static_cast<unsigned>(size.width()) * 4u;
ConstPixelBufferConversionView source {
.format = pixelBuffer->format(),
.bytesPerRow = bytesPerRow,
.rows = data->data(),
};
PixelBufferConversionView destination {
.format = { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, pixelBuffer->format().colorSpace },
.bytesPerRow = bytesPerRow,
.rows = data->data(),
};
convertImagePixels(source, destination, size);
return ImageData::create(size, WTFMove(data), m_settings.colorSpace);
}

ExceptionOr<Ref<ImageData>> CanvasRenderingContext2DBase::getImageData(int sx, int sy, int sw, int sh, std::optional<ImageDataSettings> settings) const
Expand Down Expand Up @@ -2456,7 +2458,7 @@ ExceptionOr<Ref<ImageData>> CanvasRenderingContext2DBase::getImageData(int sx, i
if (auto imageData = takeCachedImageDataIfPossible({ sx, sy, sw, sh }, computedColorSpace))
return imageData.releaseNonNull();
if (m_cachedImageData)
m_cachedImageData->imageData = nullptr;
m_cachedImageData = std::nullopt;

canvasBase().makeRenderingResultsAvailable();
ImageBuffer* buffer = canvasBase().buffer();
Expand Down Expand Up @@ -2504,20 +2506,19 @@ void CanvasRenderingContext2DBase::putImageData(ImageData& data, int dx, int dy,
dirtyHeight = -dirtyHeight;
}

IntSize destOffset { dx, dy };
IntPoint destOffset { dx, dy };
IntRect destRect { dirtyX, dirtyY, dirtyWidth, dirtyHeight };
// FIXME: computeImageDataRect also updates destRect. Maybe return a tuple? Or move
// the calculation of the real destRect to here.
IntRect sourceRect = computeImageDataRect(*buffer, data.width(), data.height(), destRect, destOffset);

if (!sourceRect.isEmpty())
buffer->putPixelBuffer(data.pixelBuffer(), sourceRect, IntPoint { destOffset });
IntRect sourceRect = computeImageDataRect(*buffer, data.size(), destRect, destOffset);

OptionSet<DidDrawOption> options; // ignore transform, shadow, clip, and post-processing

if (cacheImageDataIfPossible(data, { dx, dy }, sourceRect))
options.add(DidDrawOption::PreserveCachedImageData);

if (!sourceRect.isEmpty()) {
auto pixelBuffer = cacheImageDataIfPossible(data, sourceRect, destOffset);
if (pixelBuffer)
options.add(DidDrawOption::PreserveCachedImageData);
else
pixelBuffer = data.pixelBuffer();
buffer->putPixelBuffer(*pixelBuffer, sourceRect, destOffset);
}
didDraw(FloatRect { destRect }, options);
}

Expand Down
8 changes: 4 additions & 4 deletions Source/WebCore/html/canvas/CanvasRenderingContext2DBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

namespace WebCore {

class ByteArrayPixelBuffer;
class CachedImage;
class CanvasGradient;
class DOMMatrix;
Expand Down Expand Up @@ -327,11 +328,10 @@ class CanvasRenderingContext2DBase : public CanvasRenderingContext, public Canva

private:
struct CachedImageData {
CachedImageData(CanvasRenderingContext2DBase&);
CachedImageData(CanvasRenderingContext2DBase&, Ref<ByteArrayPixelBuffer>);

RefPtr<ImageData> imageData;
Ref<ByteArrayPixelBuffer> imageData;
DeferrableOneShotTimer evictionTimer;
unsigned requestCount = 0;
};

void applyLineDash() const;
Expand Down Expand Up @@ -448,7 +448,7 @@ class CanvasRenderingContext2DBase : public CanvasRenderingContext, public Canva

FloatPoint textOffset(float width, TextDirection);

bool cacheImageDataIfPossible(ImageData&, const IntPoint& destinationPosition, const IntRect& sourceRect);
RefPtr<ByteArrayPixelBuffer> cacheImageDataIfPossible(const ImageData&, const IntRect& sourceRect, const IntPoint& destinationPosition);
RefPtr<ImageData> takeCachedImageDataIfPossible(const IntRect& sourceRect, PredefinedColorSpace) const;
void evictCachedImageData();

Expand Down

0 comments on commit c52d8a9

Please sign in to comment.