Commit c8018d82 authored by junov@chromium.org's avatar junov@chromium.org

Implementation of 2D canvas context lost/restored events

This change adds the APIs specified in the feature proposal found here:
http://wiki.whatwg.org/wiki/Canvas_Context_Loss_and_Restoration
The API changes are hidden behind the experimental canvas features flag.

This change does not implement any elective canvas evictions. Those will
be added in a future change. Only pre-existing context loss use cases
are handled, such as failure to allocate a backing store, and gpu
failures.

The strategy for recovering from a GPU context lost was modified
substantially in order to accomodate synchronization issues with the
context restored event. Context restoration is now attempted proactively
through a scheduled event. Prior to this change, restoration happened
lazily when trying to use the canvas.

BUG=322335
R=senorblanco@chromium.org

Review URL: https://codereview.chromium.org/211503006

git-svn-id: svn://svn.chromium.org/blink/trunk@170572 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 712e40bd
Series of tests to ensure correct behaviour when canvas size is extremely large.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
check for crash on extremely large canvas size.
PASS imgdata[4] is 0
PASS imgdata[5] is 0
PASS imgdata[6] is 0
check for crash after resetting to the same size.
PASS imgdata[4] is 0
PASS imgdata[5] is 0
PASS imgdata[6] is 0
check for crash after resizing to googol.
PASS imgdata[4] is 0
PASS imgdata[5] is 0
PASS imgdata[6] is 0
check for crash after resetting to the same size.
PASS imgdata[4] is 0
PASS imgdata[5] is 0
PASS imgdata[6] is 0
check again for crash on extremely large canvas size.
PASS imgdata[4] is 0
PASS imgdata[5] is 0
PASS imgdata[6] is 0
after resizing to normal size, the canvas must be in a valid state.
PASS imgdata[4] is 0
PASS imgdata[5] is 0
PASS imgdata[6] is 255
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../../resources/js-test.js"></script>
</head>
<body>
<script src="script-tests/canvas-extremely-large-dimensions.js"></script>
</body>
</html>
Tests to ensure correct behaviour of canvas loss and restoration when size is extremely large then, restored to a reasonable value.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS contextLostTest is false
PASS ctx.isContextLost() is false
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS successfullyParsed is true
TEST COMPLETE
PASS Graphics context lost event dispatched.
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS Context restored event dispatched after context lost.
PASS contextLostTest is false
PASS ctx.isContextLost() is false
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../../resources/js-test.js"></script>
</head>
<body>
<script>
description("Tests to ensure correct behaviour of canvas loss and restoration when size is extremely large then, restored to a reasonable value.");
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
var canvas = document.createElement('canvas')
canvas.addEventListener('contextlost', contextLost);
canvas.addEventListener('contextrestored', contextRestored);
var ctx = canvas.getContext('2d');
var lostEventHasFired = false;
verifyContextLost(false);
var googol = Math.pow(10,100);
canvas.width = googol;
canvas.height = googol;
verifyContextLost(true);
canvas.width = googol;
verifyContextLost(true);
canvas.width = 100;
canvas.height = 100;
verifyContextLost(true); // Restoration is async.
// Restore a sane dimension
function verifyContextLost(shouldBeLost) {
// Verify context loss experimentally as well as isContextLost()
ctx.fillStyle = '#0f0';
ctx.fillRect(0, 0, 1, 1);
contextLostTest = ctx.getImageData(0, 0, 1, 1).data[1] == 0;
if (shouldBeLost) {
shouldBeTrue('contextLostTest');
shouldBeTrue('ctx.isContextLost()');
} else {
shouldBeFalse('contextLostTest');
shouldBeFalse('ctx.isContextLost()');
}
}
function contextLost() {
if (lostEventHasFired) {
testFailed('Context lost event was dispatched more than once.');
} else {
testPassed('Graphics context lost event dispatched.');
}
lostEventHasFired = true;
verifyContextLost(true);
}
function contextRestored() {
if (lostEventHasFired) {
testPassed('Context restored event dispatched after context lost.');
} else {
testFailed('Context restored event was dispatched before a context lost event.');
}
verifyContextLost(false);
if (window.testRunner) {
testRunner.notifyDone();
}
}
</script>
</body>
</html>
Tests to ensure correct behaviour of canvas loss and restoration when size is extremely large then, restored to a reasonable value.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS contextLostTest is false
PASS ctx.isContextLost() is false
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS successfullyParsed is true
TEST COMPLETE
PASS Graphics context lost event dispatched.
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS Context restored event dispatched after context lost.
PASS contextLostTest is false
PASS ctx.isContextLost() is false
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../../resources/js-test.js"></script>
</head>
<body>
<script>
description("Tests to ensure correct behaviour of canvas loss and restoration when size is extremely large then, restored to a reasonable value.");
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
var canvas = document.createElement('canvas')
canvas.addEventListener('contextlost', contextLost);
canvas.addEventListener('contextrestored', contextRestored);
var ctx = canvas.getContext('2d');
var lostEventHasFired = false;
verifyContextLost(false);
// WebIDL defines width and height as int. 2147483647 is int max.
var extremelyLargeNumber = 2147483647;
canvas.width = extremelyLargeNumber;
canvas.height = extremelyLargeNumber;
verifyContextLost(true);
canvas.width = extremelyLargeNumber;
verifyContextLost(true);
canvas.width = 100;
canvas.height = 100;
verifyContextLost(true); // Restoration is async.
// Restore a sane dimension
function verifyContextLost(shouldBeLost) {
// Verify context loss experimentally as well as isContextLost()
ctx.fillStyle = '#0f0';
ctx.fillRect(0, 0, 1, 1);
contextLostTest = ctx.getImageData(0, 0, 1, 1).data[1] == 0;
if (shouldBeLost) {
shouldBeTrue('contextLostTest');
shouldBeTrue('ctx.isContextLost()');
} else {
shouldBeFalse('contextLostTest');
shouldBeFalse('ctx.isContextLost()');
}
}
function contextLost() {
if (lostEventHasFired) {
testFailed('Context lost event was dispatched more than once.');
} else {
testPassed('Graphics context lost event dispatched.');
}
lostEventHasFired = true;
verifyContextLost(true);
}
function contextRestored() {
if (lostEventHasFired) {
testPassed('Context restored event dispatched after context lost.');
} else {
testFailed('Context restored event was dispatched before a context lost event.');
}
verifyContextLost(false);
if (window.testRunner) {
testRunner.notifyDone();
}
}
</script>
</body>
</html>
description("Series of tests to ensure correct behaviour when canvas size is extremely large.");
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d');
// WebIDL defines width and height as int. 2147483647 is int max.
var extremelyLargeNumber = 2147483647;
canvas.width = extremelyLargeNumber;
canvas.height = extremelyLargeNumber;
debug("check for crash on extremely large canvas size.");
useCanvasContext(ctx);
var imageData = ctx.getImageData(1, 1, 98, 98);
var imgdata = imageData.data;
// Blink returns zero color if the image buffer does not exist.
shouldBe("imgdata[4]", "0");
shouldBe("imgdata[5]", "0");
shouldBe("imgdata[6]", "0");
debug("check for crash after resetting to the same size.");
canvas.width = extremelyLargeNumber;
useCanvasContext(ctx);
imageData = ctx.getImageData(1, 1, 98, 98);
imgdata = imageData.data;
shouldBe("imgdata[4]", "0");
shouldBe("imgdata[5]", "0");
shouldBe("imgdata[6]", "0");
// googol is parsed to 0.
var googol = Math.pow(10, 100);
debug("check for crash after resizing to googol.");
canvas.width = googol;
canvas.height = googol;
useCanvasContext(ctx);
imageData = ctx.getImageData(1, 1, 98, 98);
imgdata = imageData.data;
shouldBe("imgdata[4]", "0");
shouldBe("imgdata[5]", "0");
shouldBe("imgdata[6]", "0");
debug("check for crash after resetting to the same size.");
canvas.width = googol;
useCanvasContext(ctx);
imageData = ctx.getImageData(1, 1, 98, 98);
imgdata = imageData.data;
shouldBe("imgdata[4]", "0");
shouldBe("imgdata[5]", "0");
shouldBe("imgdata[6]", "0");
debug("check again for crash on extremely large canvas size.");
canvas.width = extremelyLargeNumber;
canvas.height = extremelyLargeNumber;
useCanvasContext(ctx);
imageData = ctx.getImageData(1, 1, 98, 98);
imgdata = imageData.data;
shouldBe("imgdata[4]", "0");
shouldBe("imgdata[5]", "0");
shouldBe("imgdata[6]", "0");
function useCanvasContext(ctx) {
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 100, 100);
for(var i = 0; i < 100; i++) {
// This API tries to create an image buffer if the image buffer is not created.
ctx.getImageData(1, 1, 1, 1);
}
ctx.beginPath();
ctx.rect(0,0,100,100);
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);
ctx.restore();
ctx.fillStyle = 'green';
ctx.fill();
}
debug("after resizing to normal size, the canvas must be in a valid state.");
canvas.width = 100;
canvas.height = 100;
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);
imageData = ctx.getImageData(1, 1, 98, 98);
imgdata = imageData.data;
shouldBe("imgdata[4]", "0");
shouldBe("imgdata[5]", "0");
shouldBe("imgdata[6]", "255");
description("Test the behavior of canvas recovery after a gpu context loss");
var recoveryLoopPeriod = 5;
var ctx;
var imageData;
var imgdata;
var lostEventHasFired = false;
var contextLostTest;
if (window.internals && window.testRunner) {
testRunner.dumpAsText();
ctx = document.createElement('canvas').getContext('2d');
var canvas = document.createElement('canvas');
canvas.addEventListener('contextlost', contextLost);
canvas.addEventListener('contextrestored', contextRestored);
ctx = canvas.getContext('2d');
document.body.appendChild(ctx.canvas);
ctx.fillStyle = '#f00';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
imageData = ctx.getImageData(0, 0, 1, 1);
imgdata = imageData.data;
shouldBe("imgdata[0]", "255");
shouldBe("imgdata[1]", "0");
shouldBe("imgdata[2]", "0");
shouldBe("imgdata[3]", "255");
verifyContextLost(false);
window.internals.loseSharedGraphicsContext3D();
// Verify whether canvas contents are lost with the graphics context.
imageData = ctx.getImageData(0, 0, 1, 1);
if (imageData.data[0] == 255) {
// for the canvas to realize it Graphics context was lost we must try to use the canvas
ctx.fillRect(0, 0, 1, 1);
if (!ctx.isContextLost()) {
debug('<span>Aborting test: Graphics context loss did not destroy canvas contents. This is expected if canvas is not accelerated.</span>');
} else {
// Redrawing immediately will fail because we are working with an
// unrecovered context here. The context recovery is asynchronous
// because it requires the context loss notification task to be
// processed on the renderer main thread, which triggers the
// re-creation of the SharedGC3D.
ctx.fillStyle = '#0f0';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
imageData = ctx.getImageData(0, 0, 1, 1);
imgdata = imageData.data;
shouldBe("imgdata[0]", "0");
shouldBe("imgdata[1]", "0");
shouldBe("imgdata[2]", "0");
shouldBe("imgdata[3]", "0");
verifyContextLost(true);
testRunner.waitUntilDone();
setTimeout(recoveryLoop, recoveryLoopPeriod);
}
} else {
testFailed('This test requires window.internals and window.testRunner.');
}
// Graphics context recovery happens asynchronously. To test for recovery, we keep
// retrying to use the canvas until it succeeds, which should hapen long before the test
// times-out.
function recoveryLoop() {
ctx.fillStyle = '#00f';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
imageData = ctx.getImageData(0, 0, 1, 1);
if (imageData.data[2] == 255) {
testPassed('Graphics context recovered.');
testRunner.notifyDone();
function verifyContextLost(shouldBeLost) {
// Verify context loss experimentally as well as isContextLost()
ctx.fillStyle = '#0f0';
ctx.fillRect(0, 0, 1, 1);
contextLostTest = ctx.getImageData(0, 0, 1, 1).data[1] == 0;
if (shouldBeLost) {
shouldBeTrue('contextLostTest');
shouldBeTrue('ctx.isContextLost()');
} else {
shouldBeFalse('contextLostTest');
shouldBeFalse('ctx.isContextLost()');
}
}
function contextLost() {
if (lostEventHasFired) {
testFailed('Context lost event was dispatched more than once.');
} else {
testPassed('Graphics context lost event dispatched.');
}
lostEventHasFired = true;
verifyContextLost(true);
}
function contextRestored() {
if (lostEventHasFired) {
testPassed('Context restored event dispatched after context lost.');
} else {
// Context not yet recovered. Try again.
setTimeout(recoveryLoop, recoveryLoopPeriod);
testFailed('Context restored event was dispatched before a context lost event.');
}
verifyContextLost(false);
testRunner.notifyDone();
}
......@@ -5,6 +5,7 @@ currentTransform
drawCustomFocusRing
drawFocusIfNeeded
ellipse
isContextLost
resetTransform
New properties and functions of CanvasGradient object that should be manually examined (should be empty to pass the test):
New properties and functions of CanvasPattern object that should be manually examined (should be empty to pass the test):
......
......@@ -3,16 +3,17 @@ Test the behavior of canvas recovery after a gpu context loss
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS imgdata[0] is 255
PASS imgdata[1] is 0
PASS imgdata[2] is 0
PASS imgdata[3] is 255
PASS imgdata[0] is 0
PASS imgdata[1] is 0
PASS imgdata[2] is 0
PASS imgdata[3] is 0
PASS contextLostTest is false
PASS ctx.isContextLost() is false
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS successfullyParsed is true
TEST COMPLETE
PASS Graphics context recovered.
PASS Graphics context lost event dispatched.
PASS contextLostTest is true
PASS ctx.isContextLost() is true
PASS Context restored event dispatched after context lost.
PASS contextLostTest is false
PASS ctx.isContextLost() is false
......@@ -50,7 +50,9 @@ compositionstart
compositionupdate
connect
connecting
contextlost
contextmenu
contextrestored
copy
cuechange
cut
......
......@@ -358,6 +358,12 @@ void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
m_didFailToCreateImageBuffer = false;
discardImageBuffer();
clearCopiedImage();
if (m_context && m_context->is2d()) {
CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get());
if (context2d->isContextLost()) {
context2d->restoreContext();
}
}
}
String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
......@@ -449,6 +455,13 @@ PassOwnPtr<ImageBufferSurface> HTMLCanvasElement::createImageBufferSurface(const
}
void HTMLCanvasElement::createImageBuffer()
{
createImageBufferInternal();
if (m_didFailToCreateImageBuffer && m_context && m_context->is2d())
toCanvasRenderingContext2D(m_context.get())->loseContext();
}
void HTMLCanvasElement::createImageBufferInternal()
{
ASSERT(!m_imageBuffer);
ASSERT(!m_contextStateSaver);
......@@ -470,7 +483,9 @@ void HTMLCanvasElement::createImageBuffer()
OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount);
if (!surface->isValid())
return;
m_imageBuffer = ImageBuffer::create(surface.release());
m_imageBuffer->setClient(this);
m_didFailToCreateImageBuffer = false;
......@@ -481,6 +496,7 @@ void HTMLCanvasElement::createImageBuffer()
return;
}
m_imageBuffer->setClient(this);
m_imageBuffer->context()->setShouldClampToSourceRect(false);
m_imageBuffer->context()->setImageInterpolationQuality(CanvasDefaultInterpolationQuality);
// Enabling MSAA overrides a request to disable antialiasing. This is true regardless of whether the
......@@ -497,6 +513,15 @@ void HTMLCanvasElement::createImageBuffer()
// Recalculate compositing requirements if acceleration state changed.
if (m_context)
scheduleLayerUpdate();
return;
}
void HTMLCanvasElement::notifySurfaceInvalid()
{
if (m_context && m_context->is2d()) {
CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get());
context2d->loseContext();
}
}
void HTMLCanvasElement::updateExternallyAllocatedMemory() const
......@@ -588,6 +613,11 @@ void HTMLCanvasElement::discardImageBuffer()
updateExternallyAllocatedMemory();
}
bool HTMLCanvasElement::hasValidImageBuffer() const
{
return m_imageBuffer && m_imageBuffer->isSurfaceValid();
}
void HTMLCanvasElement::clearCopiedImage()
{
m_copiedImage.clear();
......
......@@ -35,6 +35,7 @@
#include "platform/geometry/IntSize.h"
#include "platform/graphics/Canvas2DLayerBridge.h"
#include "platform/graphics/GraphicsTypes.h"
#include "platform/graphics/ImageBufferClient.h"
#include "wtf/Forward.h"
#define CanvasDefaultInterpolationQuality InterpolationLow
......@@ -62,7 +63,7 @@ public:
virtual void canvasDestroyed(HTMLCanvasElement*) = 0;
};
class HTMLCanvasElement FINAL : public HTMLElement, public DocumentVisibilityObserver, public CanvasImageSource {
class HTMLCanvasElement FINAL : public HTMLElement, public DocumentVisibilityObserver, public CanvasImageSource, public ImageBufferClient {
public:
static PassRefPtr<HTMLCanvasElement> create(Document&);
virtual ~HTMLCanvasElement();
......@@ -125,7 +126,9 @@ public:
bool is3D() const;
bool hasImageBuffer() const { return m_imageBuffer.get(); }
bool hasImageBuffer() const { return m_imageBuffer; }
bool hasValidImageBuffer() const;
void discardImageBuffer();
bool shouldAccelerate(const IntSize&) const;
......@@ -139,6 +142,9 @@ public:
virtual bool wouldTaintOrigin(SecurityOrigin*) const OVERRIDE;
virtual FloatSize sourceSize() const OVERRIDE;
// ImageBufferClient implementation
virtual void notifySurfaceInvalid() OVERRIDE;
protected:
virtual void didMoveToNewDocument(Document& oldDocument) OVERRIDE;
......@@ -153,8 +159,8 @@ private:
PassOwnPtr<ImageBufferSurface> createImageBufferSurface(const IntSize& deviceSize, int* msaaSampleCount);
void createImageBuffer();
void createImageBufferInternal();
void clearImageBuffer();
void discardImageBuffer();
void setSurfaceSize(const IntSize&);
......
......@@ -28,10 +28,13 @@
#include "Canvas2DContextAttributes.h"
#include "wtf/text/WTFString.h"
namespace WebCore {
Canvas2DContextAttributes::Canvas2DContextAttributes()
: m_alpha(true)
, m_storage(PersistentStorage)
{
ScriptWrappable::init(this);
}
......@@ -55,4 +58,22 @@ void Canvas2DContextAttributes::setAlpha(bool alpha)
m_alpha = alpha;
}
String Canvas2DContextAttributes::storage() const
{
return m_storage == PersistentStorage ? "persistent" : "discardable";
}
void Canvas2DContextAttributes::setStorage(const String& storage)
{
if (storage == "persistent")
m_storage = PersistentStorage;
else if (storage == "discardable")
m_storage = DiscardableStorage;
}
Canvas2DContextStorage Canvas2DContextAttributes::parsedStorage() const
{
return m_storage;
}
} // namespace WebCore
......@@ -33,6 +33,11 @@
namespace WebCore {
enum Canvas2DContextStorage {
PersistentStorage,
DiscardableStorage
};
class Canvas2DContextAttributes : public CanvasContextAttributes, public ScriptWrappable {
public:
virtual ~Canvas2DContextAttributes();
......@@ -44,10 +49,15 @@ public:
bool alpha() const;
void setAlpha(bool);
String storage() const;
void setStorage(const String&);
Canvas2DContextStorage parsedStorage() const;
protected:
Canvas2DContextAttributes();
bool m_alpha;
Canvas2DContextStorage m_storage;
};
} // namespace WebCore
......
......@@ -28,4 +28,5 @@
NoInterfaceObject
] interface Canvas2DContextAttributes {
attribute boolean alpha;
[RuntimeEnabled=ExperimentalCanvasFeatures] attribute DOMString storage;
};
......@@ -76,11 +76,25 @@ namespace WebCore {
static const int defaultFontSize = 10;
static const char defaultFontFamily[] = "sans-serif";
static const char defaultFont[] = "10px sans-serif";
static const double TryRestoreContextInterval = 0.5;
static const unsigned MaxTryRestoreContextAttempts = 4;
static bool contextLostRestoredEventsEnabled()
{
return RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled();
}
CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, const Canvas2DContextAttributes* attrs, bool usesCSSCompatibilityParseMode)
: CanvasRenderingContext(canvas)
, m_usesCSSCompatibilityParseMode(usesCSSCompatibilityParseMode)
, m_hasAlpha(!attrs || attrs->alpha())
, m_isContextLost(false)
, m_contextRestorable(true)
, m_storageMode(!attrs ? PersistentStorage : attrs->parsedStorage())
, m_tryRestoreContextAttemptCount(0)
, m_dispatchContextLostEventTimer(this, &CanvasRenderingContext2D::dispatchContextLostEvent)
, m_dispatchContextRestoredEventTimer(this, &CanvasRenderingContext2D::dispatchContextRestoredEvent)
, m_tryRestoreContextEventTimer(this, &CanvasRenderingContext2D::tryRestoreContextEvent)
{
m_stateStack.append(adoptPtrWillBeNoop(new State()));
ScriptWrappable::init(this);
......@@ -114,6 +128,91 @@ bool CanvasRenderingContext2D::isAccelerated() const
return context && context->isAccelerated();
}
bool CanvasRenderingContext2D::isContextLost() const
{
return m_isContextLost;
}
void CanvasRenderingContext2D::loseContext()
{
if (m_isContextLost)
return;
m_isContextLost = true;
m_dispatchContextLostEventTimer.startOneShot(0, FROM_HERE);
}
void CanvasRenderingContext2D::restoreContext()
{
if (!m_contextRestorable)
return;
// This code path is for restoring from an eviction
// Restoring from surface failure is handled internally
ASSERT(m_isContextLost && !canvas()->hasImageBuffer());
if (canvas()->buffer()) {
if (contextLostRestoredEventsEnabled()) {
m_dispatchContextRestoredEventTimer.startOneShot(0, FROM_HERE);
} else {
// legacy synchronous context restoration.
reset();
m_isContextLost = false;
}
}
}
void CanvasRenderingContext2D::dispatchContextLostEvent(Timer<CanvasRenderingContext2D>*)
{
if (contextLostRestoredEventsEnabled()) {
RefPtr<Event> event(Event::createCancelable(EventTypeNames::contextlost));
canvas()->dispatchEvent(event);
if (event->defaultPrevented()) {
m_contextRestorable = false;
}
}
// If an image buffer is present, it means the context was not lost due to
// an eviction, but rather due to a surface failure (gpu context lost?)
if (m_contextRestorable && canvas()->hasImageBuffer()) {
m_tryRestoreContextAttemptCount = 0;
m_tryRestoreContextEventTimer.startRepeating(TryRestoreContextInterval, FROM_HERE);
}
}
void CanvasRenderingContext2D::tryRestoreContextEvent(Timer<CanvasRenderingContext2D>* timer)
{
if (!m_isContextLost) {
// Canvas was already restored (possibly thanks to a resize), so stop trying.
m_tryRestoreContextEventTimer.stop();
return;
}
if (canvas()->hasImageBuffer() && canvas()->buffer()->restoreSurface()) {
m_tryRestoreContextEventTimer.stop();
dispatchContextRestoredEvent(0);
}
if (++m_tryRestoreContextAttemptCount > MaxTryRestoreContextAttempts)
canvas()->discardImageBuffer();
if (!canvas()->hasImageBuffer()) {
// final attempt: allocate a brand new image buffer instead of restoring
timer->stop();
if (canvas()->buffer())
dispatchContextRestoredEvent(0);
}
}
void CanvasRenderingContext2D::dispatchContextRestoredEvent(Timer<CanvasRenderingContext2D>*)
{
if (!m_isContextLost)
return;
reset();
m_isContextLost = false;
if (contextLostRestoredEventsEnabled()) {
RefPtr<Event> event(Event::create(EventTypeNames::contextrestored));
canvas()->dispatchEvent(event);
}
}
void CanvasRenderingContext2D::reset()
{
unwindStateStack();
......@@ -1708,6 +1807,8 @@ void CanvasRenderingContext2D::didDraw(const FloatRect& dirtyRect)
GraphicsContext* CanvasRenderingContext2D::drawingContext() const
{
if (isContextLost())
return 0;
return canvas()->drawingContext();
}
......@@ -1793,7 +1894,7 @@ PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::getImageData(float s
IntRect imageDataRect = enclosingIntRect(logicalRect);
ImageBuffer* buffer = canvas()->buffer();
if (!buffer)
if (!buffer || isContextLost())
return createEmptyImageData(imageDataRect.size());
RefPtr<Uint8ClampedArray> byteArray = buffer->getUnmultipliedImageData(imageDataRect);
......
......@@ -112,6 +112,8 @@ public:
float globalAlpha() const;
void setGlobalAlpha(float);
bool isContextLost() const;
String globalCompositeOperation() const;
void setGlobalCompositeOperation(const String&);
......@@ -230,6 +232,9 @@ public:
void drawFocusIfNeeded(Element*);
bool drawCustomFocusRing(Element*);
void loseContext();
void restoreContext();
private:
class State FINAL : public CSSFontSelectorClient {
public:
......@@ -285,6 +290,10 @@ private:
void applyShadow();
bool shouldDrawShadows() const;
void dispatchContextLostEvent(Timer<CanvasRenderingContext2D>*);
void dispatchContextRestoredEvent(Timer<CanvasRenderingContext2D>*);
void tryRestoreContextEvent(Timer<CanvasRenderingContext2D>*);
bool computeDirtyRect(const FloatRect& localBounds, FloatRect*);
bool computeDirtyRect(const FloatRect& localBounds, const FloatRect& transformedClipBounds, FloatRect*);
void didDraw(const FloatRect&);
......@@ -339,7 +348,14 @@ private:
WillBePersistentHeapVector<OwnPtrWillBeMember<State> > m_stateStack;
bool m_usesCSSCompatibilityParseMode;
bool m_hasAlpha;
bool m_isContextLost;
bool m_contextRestorable;
Canvas2DContextStorage m_storageMode;
MutableStylePropertyMap m_fetchedFonts;
unsigned m_tryRestoreContextAttemptCount;
Timer<CanvasRenderingContext2D> m_dispatchContextLostEventTimer;
Timer<CanvasRenderingContext2D> m_dispatchContextRestoredEventTimer;
Timer<CanvasRenderingContext2D> m_tryRestoreContextEventTimer;
};
DEFINE_TYPE_CASTS(CanvasRenderingContext2D, CanvasRenderingContext, context, context->is2d(), context.is2d());
......
......@@ -100,6 +100,10 @@ interface CanvasRenderingContext2D {
TextMetrics measureText(DOMString text);
// Context state
// Should be merged with WebGL counterpart in CanvasRenderingContext, once no-longer experimental
[RuntimeEnabled=ExperimentalCanvasFeatures] boolean isContextLost();
// other
void setAlpha(float alpha);
......
......@@ -591,12 +591,13 @@
'graphics/Image.h',
'graphics/ImageBuffer.cpp',
'graphics/ImageBuffer.h',
'graphics/ImageBufferClient.h',
'graphics/ImageBufferSurface.cpp',
'graphics/ImageBufferSurface.h',
'graphics/ImageDecodingStore.cpp',
'graphics/ImageDecodingStore.h',
'graphics/ImageFrameGenerator.cpp',
'graphics/ImageFrameGenerator.h',
'graphics/ImageBufferSurface.cpp',
'graphics/ImageBufferSurface.h',
'graphics/ImageObserver.cpp',
'graphics/ImageObserver.h',
'graphics/ImageOrientation.cpp',
......
......@@ -55,11 +55,13 @@ public:
// ImageBufferSurface implementation
virtual void willUse() OVERRIDE { m_layerBridge->willUse(); }
virtual SkCanvas* canvas() const OVERRIDE { return m_layerBridge->canvas(); }
virtual bool isValid() const OVERRIDE { return m_layerBridge && (m_layerBridge->surfaceIsValid() || m_layerBridge->recoverSurface()); }
virtual bool isValid() const OVERRIDE { return m_layerBridge && m_layerBridge->checkSurfaceValid(); }
virtual bool restore() OVERRIDE { return m_layerBridge->restoreSurface(); }
virtual blink::WebLayer* layer() const OVERRIDE { return m_layerBridge->layer(); }
virtual Platform3DObject getBackingTexture() const OVERRIDE { return m_layerBridge->getBackingTexture(); }
virtual bool isAccelerated() const OVERRIDE { return m_layerBridge->isAccelerated(); }
virtual void setIsHidden(bool hidden) OVERRIDE { m_layerBridge->setIsHidden(hidden); }
virtual void setImageBuffer(ImageBuffer* imageBuffer) OVERRIDE { m_layerBridge->setImageBuffer(imageBuffer); }
private:
RefPtr<Canvas2DLayerBridge> m_layerBridge;
......
......@@ -83,10 +83,11 @@ PassRefPtr<Canvas2DLayerBridge> Canvas2DLayerBridge::create(const IntSize& size,
Canvas2DLayerBridge::Canvas2DLayerBridge(PassOwnPtr<blink::WebGraphicsContext3DProvider> contextProvider, PassOwnPtr<SkDeferredCanvas> canvas, int msaaSampleCount, OpacityMode opacityMode)
: m_canvas(canvas)
, m_contextProvider(contextProvider)
, m_imageBuffer(0)
, m_msaaSampleCount(msaaSampleCount)
, m_bytesAllocated(0)
, m_didRecordDrawCommand(false)
, m_surfaceIsValid(true)
, m_isSurfaceValid(true)
, m_framesPending(0)
, m_framesSinceMailboxRelease(0)
, m_destructionInProgress(false)
......@@ -135,6 +136,7 @@ void Canvas2DLayerBridge::beginDestruction()
ASSERT(!m_destructionInProgress);
setRateLimitingEnabled(false);
m_canvas->silentFlush();
m_imageBuffer = 0;
freeTransientResources();
setIsHidden(true);
m_destructionInProgress = true;
......@@ -207,7 +209,7 @@ void Canvas2DLayerBridge::prepareForDraw()
{
ASSERT(!m_destructionInProgress);
ASSERT(m_layer);
if (!surfaceIsValid() && !recoverSurface()) {
if (!checkSurfaceValid()) {
if (m_canvas) {
// drop pending commands because there is no surface to draw to
m_canvas->silentFlush();
......@@ -295,7 +297,7 @@ bool Canvas2DLayerBridge::hasReleasedMailbox() const
void Canvas2DLayerBridge::freeReleasedMailbox()
{
if (m_contextProvider->context3d()->isContextLost() || !m_surfaceIsValid)
if (m_contextProvider->context3d()->isContextLost() || !m_isSurfaceValid)
return;
MailboxInfo* mailboxInfo = releasedMailboxInfo();
if (!mailboxInfo)
......@@ -322,24 +324,31 @@ blink::WebGraphicsContext3D* Canvas2DLayerBridge::context()
{
// Check on m_layer is necessary because context() may be called during
// the destruction of m_layer
if (m_layer && !m_destructionInProgress && !surfaceIsValid()) {
recoverSurface(); // To ensure rate limiter is disabled if context is lost.
}
if (m_layer && !m_destructionInProgress)
checkSurfaceValid(); // To ensure rate limiter is disabled if context is lost.
return m_contextProvider->context3d();
}
bool Canvas2DLayerBridge::surfaceIsValid()
bool Canvas2DLayerBridge::checkSurfaceValid()
{
ASSERT(!m_destructionInProgress);
return !m_destructionInProgress && !m_contextProvider->context3d()->isContextLost() && m_surfaceIsValid;
if (m_destructionInProgress || !m_isSurfaceValid)
return false;
if (m_contextProvider->context3d()->isContextLost()) {
m_isSurfaceValid = false;
if (m_imageBuffer)
m_imageBuffer->notifySurfaceInvalid();
setRateLimitingEnabled(false);
}
return m_isSurfaceValid;
}
bool Canvas2DLayerBridge::recoverSurface()
bool Canvas2DLayerBridge::restoreSurface()
{
ASSERT(!m_destructionInProgress);
ASSERT(m_layer && !surfaceIsValid());
if (m_destructionInProgress)
return false;
ASSERT(m_layer && !m_isSurfaceValid);
blink::WebGraphicsContext3D* sharedContext = 0;
// We must clear the mailboxes before calling m_layer->clearTexture() to prevent
......@@ -351,25 +360,17 @@ bool Canvas2DLayerBridge::recoverSurface()
if (m_contextProvider)
sharedContext = m_contextProvider->context3d();
if (!sharedContext || sharedContext->isContextLost()) {
m_surfaceIsValid = false;
} else {
if (sharedContext && !sharedContext->isContextLost()) {
IntSize size(m_canvas->getTopDevice()->width(), m_canvas->getTopDevice()->height());
RefPtr<SkSurface> surface(createSkSurface(m_contextProvider->grContext(), size, m_msaaSampleCount));
if (surface.get()) {
m_canvas->setSurface(surface.get());
m_surfaceIsValid = true;
m_isSurfaceValid = true;
// FIXME: draw sad canvas picture into new buffer crbug.com/243842
} else {
// Surface allocation failed. Set m_surfaceIsValid to false to
// trigger subsequent retry.
m_surfaceIsValid = false;
}
}
if (!m_surfaceIsValid)
setRateLimitingEnabled(false);
return m_surfaceIsValid;
return m_isSurfaceValid;
}
bool Canvas2DLayerBridge::prepareMailbox(blink::WebExternalTextureMailbox* outMailbox, blink::WebExternalBitmap* bitmap)
......@@ -384,7 +385,7 @@ bool Canvas2DLayerBridge::prepareMailbox(blink::WebExternalTextureMailbox* outMa
m_lastImageId = 0;
return false;
}
if (!surfaceIsValid() && !recoverSurface())
if (!checkSurfaceValid())
return false;
blink::WebGraphicsContext3D* webContext = context();
......@@ -510,7 +511,7 @@ void Canvas2DLayerBridge::willUse()
Platform3DObject Canvas2DLayerBridge::getBackingTexture()
{
ASSERT(!m_destructionInProgress);
if (!surfaceIsValid() && !recoverSurface())
if (!checkSurfaceValid())
return 0;
willUse();
m_canvas->flush();
......
......@@ -49,6 +49,8 @@ class Canvas2DLayerBridgeTest;
namespace WebCore {
class ImageBuffer;
class PLATFORM_EXPORT Canvas2DLayerBridge : public blink::WebExternalTextureLayerClient, public SkDeferredCanvas::NotificationClient, public DoublyLinkedListNode<Canvas2DLayerBridge>, public RefCounted<Canvas2DLayerBridge> {
WTF_MAKE_NONCOPYABLE(Canvas2DLayerBridge);
public:
......@@ -70,12 +72,13 @@ public:
// ImageBufferSurface implementation
void willUse();
SkCanvas* canvas() const { return m_canvas.get(); }
bool surfaceIsValid();
bool recoverSurface();
bool checkSurfaceValid();
bool restoreSurface();
blink::WebLayer* layer() const;
Platform3DObject getBackingTexture();
bool isAccelerated() const { return true; }
void setIsHidden(bool);
void setImageBuffer(ImageBuffer* imageBuffer) { m_imageBuffer = imageBuffer; }
// Methods used by Canvas2DLayerManager
virtual size_t freeMemoryIfPossible(size_t); // virtual for mocking
......@@ -99,10 +102,11 @@ protected:
OwnPtr<SkDeferredCanvas> m_canvas;
OwnPtr<blink::WebExternalTextureLayer> m_layer;
OwnPtr<blink::WebGraphicsContext3DProvider> m_contextProvider;
ImageBuffer* m_imageBuffer;
int m_msaaSampleCount;
size_t m_bytesAllocated;
bool m_didRecordDrawCommand;
bool m_surfaceIsValid;
bool m_isSurfaceValid;
int m_framesPending;
int m_framesSinceMailboxRelease;
bool m_destructionInProgress;
......
......@@ -38,6 +38,7 @@
#include "platform/graphics/BitmapImage.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/GraphicsTypes3D.h"
#include "platform/graphics/ImageBufferClient.h"
#include "platform/graphics/UnacceleratedImageBufferSurface.h"
#include "platform/graphics/gpu/DrawingBuffer.h"
#include "platform/graphics/gpu/Extensions3DUtil.h"
......@@ -76,7 +77,9 @@ PassOwnPtr<ImageBuffer> ImageBuffer::create(const IntSize& size, OpacityMode opa
ImageBuffer::ImageBuffer(PassOwnPtr<ImageBufferSurface> surface)
: m_surface(surface)
, m_client(0)
{
m_surface->setImageBuffer(this);
if (m_surface->canvas()) {
m_context = adoptPtr(new GraphicsContext(m_surface->canvas()));
m_context->setCertainlyOpaque(m_surface->opacityMode() == Opaque);
......@@ -90,6 +93,8 @@ ImageBuffer::~ImageBuffer()
GraphicsContext* ImageBuffer::context() const
{
if (!isSurfaceValid())
return 0;
m_surface->willUse();
ASSERT(m_context.get());
return m_context.get();
......@@ -101,11 +106,22 @@ const SkBitmap& ImageBuffer::bitmap() const
return m_surface->bitmap();
}
bool ImageBuffer::isValid() const
bool ImageBuffer::isSurfaceValid() const
{
return m_surface->isValid();
}
bool ImageBuffer::restoreSurface() const
{
return m_surface->isValid() || m_surface->restore();
}
void ImageBuffer::notifySurfaceInvalid()
{
if (m_client)
m_client->notifySurfaceInvalid();
}
static SkBitmap deepSkBitmapCopy(const SkBitmap& bitmap)
{
SkBitmap tmp;
......@@ -117,7 +133,7 @@ static SkBitmap deepSkBitmapCopy(const SkBitmap& bitmap)
PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior) const
{
if (!isValid())
if (!isSurfaceValid())
return BitmapImage::create(NativeImageSkia::create());
const SkBitmap& bitmap = m_surface->bitmap();
......@@ -136,7 +152,7 @@ blink::WebLayer* ImageBuffer::platformLayer() const
bool ImageBuffer::copyToPlatformTexture(blink::WebGraphicsContext3D* context, Platform3DObject texture, GLenum internalFormat, GLenum destType, GLint level, bool premultiplyAlpha, bool flipY)
{
if (!m_surface->isAccelerated() || !platformLayer() || !isValid())
if (!m_surface->isAccelerated() || !platformLayer() || !isSurfaceValid())
return false;
if (!Extensions3DUtil::canUseCopyTextureCHROMIUM(internalFormat, destType, level))
......@@ -217,13 +233,13 @@ bool ImageBuffer::copyRenderingResultsFromDrawingBuffer(DrawingBuffer* drawingBu
void ImageBuffer::draw(GraphicsContext* context, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, blink::WebBlendMode blendMode)
{
if (!isValid())
if (!isSurfaceValid())
return;
SkBitmap bitmap = m_surface->bitmap();
// For ImageBufferSurface that enables cachedBitmap, Use the cached Bitmap for CPU side usage
// if it is available, otherwise generate and use it.
if (!context->isAccelerated() && m_surface->isAccelerated() && m_surface->cachedBitmapEnabled() && m_surface->isValid()) {
if (!context->isAccelerated() && m_surface->isAccelerated() && m_surface->cachedBitmapEnabled() && isSurfaceValid()) {
m_surface->updateCachedBitmapIfNeeded();
bitmap = m_surface->cachedBitmap();
}
......@@ -243,7 +259,7 @@ void ImageBuffer::flush()
void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const FloatSize& scale,
const FloatPoint& phase, CompositeOperator op, const FloatRect& destRect, blink::WebBlendMode blendMode, const IntSize& repeatSpacing)
{
if (!isValid())
if (!isSurfaceValid())
return;
const SkBitmap& bitmap = m_surface->bitmap();
......@@ -258,7 +274,7 @@ void ImageBuffer::transformColorSpace(ColorSpace srcColorSpace, ColorSpace dstCo
return;
// FIXME: Disable color space conversions on accelerated canvases (for now).
if (context()->isAccelerated() || !isValid())
if (context()->isAccelerated() || !isSurfaceValid())
return;
const SkBitmap& bitmap = m_surface->bitmap();
......@@ -315,21 +331,21 @@ PassRefPtr<Uint8ClampedArray> getImageData(const IntRect& rect, GraphicsContext*
PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
{
if (!isValid())
if (!isSurfaceValid())
return Uint8ClampedArray::create(rect.width() * rect.height() * 4);
return getImageData<Unmultiplied>(rect, context(), m_surface->size());
}
PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
{
if (!isValid())
if (!isSurfaceValid())
return Uint8ClampedArray::create(rect.width() * rect.height() * 4);
return getImageData<Premultiplied>(rect, context(), m_surface->size());
}
void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
{
if (!isValid())
if (!isSurfaceValid())
return;
ASSERT(sourceRect.width() > 0);
......@@ -388,7 +404,7 @@ String ImageBuffer::toDataURL(const String& mimeType, const double* quality) con
ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
Vector<char> encodedImage;
if (!isValid() || !encodeImage(m_surface->bitmap(), mimeType, quality, &encodedImage))
if (!isSurfaceValid() || !encodeImage(m_surface->bitmap(), mimeType, quality, &encodedImage))
return "data:,";
Vector<char> base64Data;
base64Encode(encodedImage, base64Data);
......
......@@ -55,6 +55,7 @@ namespace WebCore {
class DrawingBuffer;
class Image;
class ImageBufferClient;
class IntPoint;
class IntRect;
......@@ -81,8 +82,12 @@ public:
~ImageBuffer();
void setClient(ImageBufferClient* client) { m_client = client; }
const IntSize& size() const { return m_surface->size(); }
bool isAccelerated() const { return m_surface->isAccelerated(); }
bool isSurfaceValid() const;
bool restoreSurface() const;
void setIsHidden(bool hidden) { m_surface->setIsHidden(hidden); }
......@@ -117,9 +122,10 @@ public:
void flush();
void notifySurfaceInvalid();
private:
ImageBuffer(PassOwnPtr<ImageBufferSurface>);
bool isValid() const;
void draw(GraphicsContext*, const FloatRect&, const FloatRect& = FloatRect(0, 0, -1, -1), CompositeOperator = CompositeSourceOver, blink::WebBlendMode = blink::WebBlendModeNormal);
void drawPattern(GraphicsContext*, const FloatRect&, const FloatSize&, const FloatPoint&, CompositeOperator, const FloatRect&, blink::WebBlendMode, const IntSize& repeatSpacing = IntSize());
......@@ -133,6 +139,7 @@ private:
OwnPtr<ImageBufferSurface> m_surface;
OwnPtr<GraphicsContext> m_context;
ImageBufferClient* m_client;
};
struct ImageDataBuffer {
......
/*
* Copyright (c) 2014, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ImageBufferClient_h
#define ImageBufferClient_h
namespace WebCore {
class ImageBufferClient {
public:
virtual ~ImageBufferClient() { }
virtual void notifySurfaceInvalid() = 0;
};
}
#endif
......@@ -32,11 +32,19 @@
#include "platform/graphics/ImageBufferSurface.h"
#include "platform/graphics/ImageBuffer.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkDevice.h"
namespace WebCore {
ImageBufferSurface::ImageBufferSurface(const IntSize& size, OpacityMode opacityMode)
: m_opacityMode(opacityMode)
, m_size(size)
{
setIsHidden(false);
}
void ImageBufferSurface::clear()
{
// Clear the background transparent or opaque, as required. It would be nice if this wasn't
......
......@@ -44,6 +44,8 @@ namespace blink { class WebLayer; }
namespace WebCore {
class ImageBuffer;
enum OpacityMode {
NonOpaque,
Opaque,
......@@ -58,6 +60,7 @@ public:
virtual const SkBitmap& bitmap() const;
virtual void willUse() { } // Called by ImageBuffer before reading or writing to the surface.
virtual bool isValid() const = 0;
virtual bool restore() { return false; };
virtual blink::WebLayer* layer() const { return 0; };
virtual bool isAccelerated() const { return false; }
virtual Platform3DObject getBackingTexture() const { return 0; }
......@@ -66,20 +69,16 @@ public:
virtual void invalidateCachedBitmap() { }
virtual void updateCachedBitmapIfNeeded() { }
virtual void setIsHidden(bool) { }
virtual void setImageBuffer(ImageBuffer*) { }
OpacityMode opacityMode() const { return m_opacityMode; }
const IntSize& size() const { return m_size; }
void notifyIsValidChanged(bool isValid) const;
protected:
ImageBufferSurface(const IntSize&, OpacityMode);
void clear();
ImageBufferSurface(const IntSize& size, OpacityMode opacityMode)
: m_opacityMode(opacityMode)
, m_size(size)
{
setIsHidden(false);
}
private:
OpacityMode m_opacityMode;
IntSize m_size;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment