Index: Source/core/html/canvas/CanvasRenderingContext2D.cpp |
diff --git a/Source/core/html/canvas/CanvasRenderingContext2D.cpp b/Source/core/html/canvas/CanvasRenderingContext2D.cpp |
index 9f3346a862e6e8964aa072a165c91ade2dd36a4f..fa07aac47d75d67f7cc2d7f3f4f0aef62642b512 100644 |
--- a/Source/core/html/canvas/CanvasRenderingContext2D.cpp |
+++ b/Source/core/html/canvas/CanvasRenderingContext2D.cpp |
@@ -90,6 +90,32 @@ static bool contextLostRestoredEventsEnabled() |
return RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); |
} |
+// Drawing methods need to use this instead of SkAutoCanvasRestore in case overdraw |
+// detection substitutes the recording canvas (to discard overdrawn draw calls). |
+class CanvasRenderingContext2DAutoRestoreSkCanvas { |
+public: |
+ CanvasRenderingContext2DAutoRestoreSkCanvas(CanvasRenderingContext2D* context) |
+ : m_context(context) |
+ { |
+ ASSERT(m_context); |
+ SkCanvas* c = m_context->drawingCanvas(); |
+ if (c) { |
+ m_saveCount = c->getSaveCount(); |
+ } |
+ } |
+ |
+ ~CanvasRenderingContext2DAutoRestoreSkCanvas() |
+ { |
+ SkCanvas* c = m_context->drawingCanvas(); |
+ if (c) |
+ c->restoreToCount(m_saveCount); |
+ m_context->validateStateStack(); |
+ } |
+private: |
+ CanvasRenderingContext2D* m_context; |
+ int m_saveCount; |
+}; |
+ |
CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, const CanvasContextCreationAttributes& attrs, Document& document) |
: CanvasRenderingContext(canvas) |
, m_usesCSSCompatibilityParseMode(document.inQuirksMode()) |
@@ -283,6 +309,7 @@ CanvasRenderingContext2D::State::State() |
, m_unparsedFont(defaultFont) |
, m_realizedFont(false) |
, m_hasClip(false) |
+ , m_hasComplexClip(false) |
{ |
} |
@@ -313,6 +340,7 @@ CanvasRenderingContext2D::State::State(const State& other, ClipListCopyMode mode |
, m_font(other.m_font) |
, m_realizedFont(other.m_realizedFont) |
, m_hasClip(other.m_hasClip) |
+ , m_hasComplexClip(other.m_hasComplexClip) |
{ |
if (mode == CopyClipList) { |
m_clipList = other.m_clipList; |
@@ -355,6 +383,7 @@ CanvasRenderingContext2D::State& CanvasRenderingContext2D::State::operator=(cons |
m_font = other.m_font; |
m_realizedFont = other.m_realizedFont; |
m_hasClip = other.m_hasClip; |
+ m_hasComplexClip = other.m_hasComplexClip; |
m_clipList = other.m_clipList; |
if (m_realizedFont) |
@@ -542,14 +571,14 @@ void CanvasRenderingContext2D::setFillStyle(const StringOrCanvasGradientOrCanvas |
} |
ASSERT(canvasStyle); |
- |
SkCanvas* c = drawingCanvas(); |
- realizeSaves(c); |
- modifiableState().m_fillStyle = canvasStyle.release(); |
if (!c) |
return; |
- state().m_fillStyle->applyFillColor(drawingContext()); |
+ realizeSaves(c); |
+ |
+ modifiableState().m_fillStyle = canvasStyle.release(); |
modifiableState().m_unparsedFillColor = colorString; |
+ state().m_fillStyle->applyFillColor(drawingContext()); |
} |
float CanvasRenderingContext2D::lineWidth() const |
@@ -1122,7 +1151,7 @@ void CanvasRenderingContext2D::clipInternal(const Path& path, const String& wind |
c->clipPath(skPath, SkRegion::kIntersect_Op, m_clipAntialiasing == AntiAliased); |
if (!skPath.isRect(0)) |
- drawingContext()->setHasComplexClip(); |
+ modifiableState().m_hasComplexClip = true; |
modifiableState().m_hasClip = true; |
} |
@@ -1236,31 +1265,28 @@ void CanvasRenderingContext2D::clearRect(float x, float y, float width, float he |
{ |
if (!validateRectForCanvas(x, y, width, height)) |
return; |
- GraphicsContext* context = drawingContext(); |
- if (!context) |
+ |
+ GraphicsContext* c = drawingContext(); |
+ if (!c) |
return; |
if (!state().m_invertibleCTM) |
return; |
- FloatRect rect(x, y, width, height); |
- FloatRect dirtyRect; |
- if (!computeDirtyRect(rect, &dirtyRect)) |
- return; |
+ c->clearShadow(); |
+ c->setAlphaAsFloat(1); |
+ c->setCompositeOperation(SkXfermode::kClear_Mode); |
- context->clearShadow(); |
- context->setAlphaAsFloat(1); |
- context->setCompositeOperation(SkXfermode::kSrcOver_Mode); |
- |
- context->clearRect(rect); |
- if (m_hitRegionManager) |
- m_hitRegionManager->removeHitRegionsInRect(rect, state().m_transform); |
+ // call to didDraw is taken care of in fillRect |
+ fillRect(x, y, width, height); |
applyShadow(DrawShadowAndForeground); |
- context->setAlphaAsFloat(state().m_globalAlpha); |
- context->setCompositeOperation(state().m_globalComposite); |
+ c->setAlphaAsFloat(state().m_globalAlpha); |
+ c->setCompositeOperation(state().m_globalComposite); |
- validateStateStack(); |
- didDraw(dirtyRect); |
+ if (m_hitRegionManager) { |
+ FloatRect rect(x, y, width, height); |
+ m_hitRegionManager->removeHitRegionsInRect(rect, state().m_transform); |
+ } |
} |
// FIXME(crbug.com/425531): Funtional.h cannot handle override function signature. |
@@ -1297,6 +1323,7 @@ void CanvasRenderingContext2D::fillRect(float x, float y, float width, float hei |
FloatRect rect(x, y, width, height); |
if (rectContainsTransformedRect(rect, clipBounds)) { |
+ checkOverdraw(rect, &c->fillPaint(), NoImage, ClipFill); |
c->fillRect(rect); |
didDraw(clipBounds); |
} else if (isFullCanvasCompositeMode(state().m_globalComposite)) { |
@@ -1304,7 +1331,7 @@ void CanvasRenderingContext2D::fillRect(float x, float y, float width, float hei |
didDraw(clipBounds); |
} else if (state().m_globalComposite == SkXfermode::kSrc_Mode) { |
clearCanvas(); |
- c->clearShadow(); |
+ c->clearShadow(); // Takes care of signaling the overdraw |
c->fillRect(rect); |
applyShadow(DrawShadowAndForeground); |
didDraw(clipBounds); |
@@ -1425,7 +1452,7 @@ void CanvasRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSour |
CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
FloatSize sourceRectSize = imageSourceInternal->sourceSize(); |
FloatSize destRectSize = imageSourceInternal->defaultDestinationSize(); |
- drawImageInternal(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, destRectSize.width(), destRectSize.height(), exceptionState); |
+ drawImage(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, destRectSize.width(), destRectSize.height(), exceptionState); |
} |
void CanvasRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource, |
@@ -1433,7 +1460,7 @@ void CanvasRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSour |
{ |
CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
FloatSize sourceRectSize = imageSourceInternal->sourceSize(); |
- drawImageInternal(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, width, height, exceptionState); |
+ drawImage(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, width, height, exceptionState); |
} |
void CanvasRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource, |
@@ -1441,7 +1468,7 @@ void CanvasRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSour |
float dx, float dy, float dw, float dh, ExceptionState& exceptionState) |
{ |
CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
- drawImageInternal(imageSourceInternal, sx, sy, sw, sh, dx, dy, dw, dh, exceptionState); |
+ drawImage(imageSourceInternal, sx, sy, sw, sh, dx, dy, dw, dh, exceptionState); |
} |
static void drawVideo(SkCanvas* c, GraphicsContext* gc, CanvasImageSource* imageSource, FloatRect srcRect, FloatRect dstRect) |
@@ -1465,7 +1492,7 @@ static void drawImageOnContext(SkCanvas* c, GraphicsContext* gc, CanvasImageSour |
} |
} |
-void CanvasRenderingContext2D::drawImageInternal(CanvasImageSource* imageSource, |
+void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, |
float sx, float sy, float sw, float sh, |
float dx, float dy, float dw, float dh, ExceptionState& exceptionState) |
{ |
@@ -1518,13 +1545,14 @@ void CanvasRenderingContext2D::drawImageInternal(CanvasImageSource* imageSource, |
canvas()->buffer()->willAccessPixels(); |
if (rectContainsTransformedRect(dstRect, clipBounds)) { |
+ checkOverdraw(dstRect, &c->fillPaint(), imageSource->isOpaque() ? OpaqueImage : NonOpaqueImage, ClipFill); |
drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), srcRect, dstRect); |
didDraw(clipBounds); |
} else if (isFullCanvasCompositeMode(state().m_globalComposite)) { |
fullCanvasCompositedDraw(bind(&drawImageOnContext, drawingCanvas(), c, imageSource, image.get(), srcRect, dstRect)); |
didDraw(clipBounds); |
} else if (state().m_globalComposite == SkXfermode::kSrc_Mode) { |
- clearCanvas(); |
+ clearCanvas(); // takes care of signaling an overdraw |
drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), srcRect, dstRect); |
didDraw(clipBounds); |
} else { |
@@ -1551,7 +1579,10 @@ void CanvasRenderingContext2D::clearCanvas() |
if (!c) |
return; |
- c->clear(m_hasAlpha ? SK_ColorTRANSPARENT : SK_ColorBLACK); |
+ checkOverdraw(canvasRect, 0, NoImage, ClipFill); |
+ // Must not use 'c' beyond this point in case checkOverdraw substitutes the recording |
+ // canvas in order to clear a draw command backlog. |
+ drawingCanvas()->clear(m_hasAlpha ? SK_ColorTRANSPARENT : SK_ColorBLACK); |
} |
bool CanvasRenderingContext2D::rectContainsTransformedRect(const FloatRect& rect, const FloatRect& transformedRect) const |
@@ -1791,6 +1822,8 @@ void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, |
IntRect sourceRect(destRect); |
sourceRect.move(-destOffset); |
+ checkOverdraw(destRect, 0, NoImage, UntransformedUnclippedFill); |
+ |
buffer->putByteArray(Unmultiplied, data->data()->data(), IntSize(data->width(), data->height()), sourceRect, IntPoint(destOffset)); |
didDraw(destRect); |
@@ -2137,11 +2170,13 @@ void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, flo |
c->setTextDrawingMode(fill ? TextModeFill : TextModeStroke); |
- GraphicsContextStateSaver stateSaver(*c); |
+ CanvasRenderingContext2DAutoRestoreSkCanvas stateRestorer(this); |
if (useMaxWidth) { |
- c->translate(location.x(), location.y()); |
+ SkCanvas* canvas = drawingCanvas(); |
+ canvas->save(); |
+ canvas->translate(location.x(), location.y()); |
// We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work. |
- c->scale((fontWidth > 0 ? (width / fontWidth) : 0), 1); |
+ canvas->scale((fontWidth > 0 ? (width / fontWidth) : 0), 1); |
location = FloatPoint(); |
} |
@@ -2382,4 +2417,71 @@ unsigned CanvasRenderingContext2D::hitRegionsCount() const |
return 0; |
} |
+void CanvasRenderingContext2D::checkOverdraw(const SkRect& rect, const SkPaint* paint, ImageType imageType, DrawType drawType) |
+{ |
+ SkCanvas* c = drawingCanvas(); |
+ if (!c || !canvas()->buffer()->isRecording()) |
+ return; |
+ |
+ SkRect deviceRect; |
+ if (drawType == UntransformedUnclippedFill) { |
+ deviceRect = rect; |
+ } else { |
+ ASSERT(drawType == ClipFill); |
+ if (state().m_hasComplexClip) |
+ return; |
+ |
+ SkIRect skIBounds; |
+ if (!c->getClipDeviceBounds(&skIBounds)) |
+ return; |
+ deviceRect = SkRect::Make(skIBounds); |
+ } |
+ |
+ const SkImageInfo& imageInfo = c->imageInfo(); |
+ if (!deviceRect.contains(SkRect::MakeWH(imageInfo.width(), imageInfo.height()))) |
+ return; |
+ |
+ bool isSourceOver = true; |
+ unsigned alpha = 0xFF; |
+ if (paint) { |
+ if (paint->getLooper() || paint->getImageFilter() || paint->getMaskFilter()) |
+ return; |
+ |
+ SkXfermode* xfermode = paint->getXfermode(); |
+ if (xfermode) { |
+ SkXfermode::Mode mode; |
+ if (xfermode->asMode(&mode)) { |
+ isSourceOver = mode == SkXfermode::kSrcOver_Mode; |
+ if (!isSourceOver && mode != SkXfermode::kSrc_Mode && mode != SkXfermode::kClear_Mode) |
+ return; // The code below only knows how to handle Src, SrcOver, and Clear |
+ } else { |
+ // unknown xfermode |
+ ASSERT_NOT_REACHED(); |
+ return; |
+ } |
+ } |
+ |
+ alpha = paint->getAlpha(); |
+ |
+ if (isSourceOver && imageType == NoImage) { |
+ SkShader* shader = paint->getShader(); |
+ if (shader) { |
+ if (shader->isOpaque() && alpha == 0xFF) |
+ canvas()->buffer()->willOverwriteCanvas(); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ if (isSourceOver) { |
+ // With source over, we need to certify that alpha == 0xFF for all pixels |
+ if (imageType == NonOpaqueImage) |
+ return; |
+ if (alpha < 0xFF) |
+ return; |
+ } |
+ |
+ canvas()->buffer()->willOverwriteCanvas(); |
+} |
+ |
} // namespace blink |