Bug 709490 - Part 7: If layer is not available, fallback to BasicCanvasLayer, r=roc

This commit is contained in:
Morris Tseng 2015-09-29 11:51:25 +01:00
parent 48d2385368
commit 0b17a6fcc9
22 changed files with 534 additions and 101 deletions

View File

@ -8,6 +8,7 @@
#include "mozilla/dom/CanvasRenderingContext2D.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/layers/AsyncCanvasRenderer.h"
#include "mozilla/RefPtr.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/unused.h"
@ -165,6 +166,7 @@ public:
mSize,
mImage,
nullptr,
nullptr,
getter_AddRefs(stream),
mEncoder);
@ -178,6 +180,7 @@ public:
mSize,
mImage,
nullptr,
nullptr,
getter_AddRefs(stream),
mEncoder);
}
@ -234,6 +237,7 @@ ImageEncoder::ExtractData(nsAString& aType,
const nsAString& aOptions,
const nsIntSize aSize,
nsICanvasRenderingContextInternal* aContext,
layers::AsyncCanvasRenderer* aRenderer,
nsIInputStream** aStream)
{
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
@ -242,10 +246,9 @@ ImageEncoder::ExtractData(nsAString& aType,
}
return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
aContext, aStream, encoder);
aContext, aRenderer, aStream, encoder);
}
/* static */
nsresult
ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
@ -341,6 +344,7 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
const nsIntSize aSize,
layers::Image* aImage,
nsICanvasRenderingContextInternal* aContext,
layers::AsyncCanvasRenderer* aRenderer,
nsIInputStream** aStream,
imgIEncoder* aEncoder)
{
@ -366,6 +370,11 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
rv = aContext->GetInputStream(encoderType.get(),
nsPromiseFlatString(aOptions).get(),
getter_AddRefs(imgStream));
} else if (aRenderer) {
NS_ConvertUTF16toUTF8 encoderType(aType);
rv = aRenderer->GetInputStream(encoderType.get(),
nsPromiseFlatString(aOptions).get(),
getter_AddRefs(imgStream));
} else if (aImage) {
// It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
// Other image formats could have problem to convert format off-main-thread.

View File

@ -19,6 +19,7 @@ class nsICanvasRenderingContextInternal;
namespace mozilla {
namespace layers {
class AsyncCanvasRenderer;
class Image;
} // namespace layers
@ -40,6 +41,7 @@ public:
const nsAString& aOptions,
const nsIntSize aSize,
nsICanvasRenderingContextInternal* aContext,
layers::AsyncCanvasRenderer* aRenderer,
nsIInputStream** aStream);
// Extracts data asynchronously. aType may change to "image/png" if we had to
@ -84,7 +86,7 @@ public:
nsIInputStream** aStream);
private:
// When called asynchronously, aContext is null.
// When called asynchronously, aContext and aRenderer are null.
static nsresult
ExtractDataInternal(const nsAString& aType,
const nsAString& aOptions,
@ -93,6 +95,7 @@ private:
const nsIntSize aSize,
layers::Image* aImage,
nsICanvasRenderingContextInternal* aContext,
layers::AsyncCanvasRenderer* aRenderer,
nsIInputStream** aStream,
imgIEncoder* aEncoder);

View File

@ -23,10 +23,12 @@ namespace dom {
OffscreenCanvasCloneData::OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
bool aNeutered)
: mRenderer(aRenderer)
, mWidth(aWidth)
, mHeight(aHeight)
, mCompositorBackendType(aCompositorBackend)
, mNeutered(aNeutered)
{
}
@ -37,26 +39,20 @@ OffscreenCanvasCloneData::~OffscreenCanvasCloneData()
OffscreenCanvas::OffscreenCanvas(uint32_t aWidth,
uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::AsyncCanvasRenderer* aRenderer)
: mAttrDirty(false)
, mNeutered(false)
, mWidth(aWidth)
, mHeight(aHeight)
, mCompositorBackendType(aCompositorBackend)
, mCanvasClient(nullptr)
, mCanvasRenderer(aRenderer)
{}
OffscreenCanvas::~OffscreenCanvas()
{
if (mCanvasRenderer) {
mCanvasRenderer->SetCanvasClient(nullptr);
mCanvasRenderer->mContext = nullptr;
mCanvasRenderer->mActiveThread = nullptr;
}
if (mCanvasClient) {
ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient);
}
ClearResources();
}
OffscreenCanvas*
@ -72,6 +68,26 @@ OffscreenCanvas::WrapObject(JSContext* aCx,
return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto);
}
void
OffscreenCanvas::ClearResources()
{
if (mCanvasClient) {
mCanvasClient->Clear();
ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient);
mCanvasClient = nullptr;
if (mCanvasRenderer) {
nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread();
MOZ_RELEASE_ASSERT(activeThread);
MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread());
mCanvasRenderer->SetCanvasClient(nullptr);
mCanvasRenderer->mContext = nullptr;
mCanvasRenderer->mGLContext = nullptr;
mCanvasRenderer->ResetActiveThread();
}
}
}
already_AddRefed<nsISupports>
OffscreenCanvas::GetContext(JSContext* aCx,
const nsAString& aContextId,
@ -103,26 +119,34 @@ OffscreenCanvas::GetContext(JSContext* aCx,
aContextOptions,
aRv);
if (mCanvasRenderer && mCurrentContext && ImageBridgeChild::IsCreated()) {
TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
if (!mCurrentContext) {
return nullptr;
}
mCanvasClient = ImageBridgeChild::GetSingleton()->
CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take();
mCanvasRenderer->SetCanvasClient(mCanvasClient);
gl::GLContext* gl = static_cast<WebGLContext*>(mCurrentContext.get())->GL();
if (mCanvasRenderer) {
WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
gl::GLContext* gl = webGL->GL();
mCanvasRenderer->mContext = mCurrentContext;
mCanvasRenderer->mActiveThread = NS_GetCurrentThread();
mCanvasRenderer->SetActiveThread();
mCanvasRenderer->mGLContext = gl;
mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha);
gl::GLScreenBuffer* screen = gl->Screen();
gl::SurfaceCaps caps = screen->mCaps;
auto forwarder = mCanvasClient->GetForwarder();
if (ImageBridgeChild::IsCreated()) {
TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
mCanvasClient = ImageBridgeChild::GetSingleton()->
CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take();
mCanvasRenderer->SetCanvasClient(mCanvasClient);
UniquePtr<gl::SurfaceFactory> factory =
gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags);
gl::GLScreenBuffer* screen = gl->Screen();
gl::SurfaceCaps caps = screen->mCaps;
auto forwarder = mCanvasClient->GetForwarder();
if (factory)
screen->Morph(Move(factory));
UniquePtr<gl::SurfaceFactory> factory =
gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags);
if (factory)
screen->Morph(Move(factory));
}
}
return result;
@ -157,6 +181,7 @@ OffscreenCanvas::CommitFrameToCompositor()
}
if (mCanvasRenderer && mCanvasRenderer->mGLContext) {
mCanvasRenderer->NotifyElementAboutInvalidation();
ImageBridgeChild::GetSingleton()->
UpdateAsyncCanvasRenderer(mCanvasRenderer);
}
@ -165,8 +190,8 @@ OffscreenCanvas::CommitFrameToCompositor()
OffscreenCanvasCloneData*
OffscreenCanvas::ToCloneData()
{
return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth,
mHeight, mNeutered);
return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight,
mCompositorBackendType, mNeutered);
}
/* static */ already_AddRefed<OffscreenCanvas>
@ -174,7 +199,8 @@ OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData)
{
MOZ_ASSERT(aData);
nsRefPtr<OffscreenCanvas> wc =
new OffscreenCanvas(aData->mWidth, aData->mHeight, aData->mRenderer);
new OffscreenCanvas(aData->mWidth, aData->mHeight,
aData->mCompositorBackendType, aData->mRenderer);
if (aData->mNeutered) {
wc->SetNeutered();
}

View File

@ -8,6 +8,7 @@
#define MOZILLA_DOM_OFFSCREENCANVAS_H_
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/RefPtr.h"
#include "CanvasRenderingContextHelper.h"
#include "nsCycleCollectionParticipant.h"
@ -33,12 +34,14 @@ struct OffscreenCanvasCloneData final
{
OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
bool aNeutered);
~OffscreenCanvasCloneData();
RefPtr<layers::AsyncCanvasRenderer> mRenderer;
uint32_t mWidth;
uint32_t mHeight;
layers::LayersBackend mCompositorBackendType;
bool mNeutered;
};
@ -51,6 +54,7 @@ public:
OffscreenCanvas(uint32_t aWidth,
uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::AsyncCanvasRenderer* aRenderer);
OffscreenCanvas* GetParentObject() const;
@ -58,6 +62,8 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
void ClearResources();
uint32_t Width() const
{
return mWidth;
@ -141,6 +147,11 @@ public:
return mNeutered;
}
layers::LayersBackend GetCompositorBackendType() const
{
return mCompositorBackendType;
}
private:
~OffscreenCanvas();
@ -156,6 +167,8 @@ private:
uint32_t mWidth;
uint32_t mHeight;
layers::LayersBackend mCompositorBackendType;
layers::CanvasClient* mCanvasClient;
RefPtr<layers::AsyncCanvasRenderer> mCanvasRenderer;
};

View File

@ -1066,32 +1066,8 @@ WebGLContext::GetImageBuffer(uint8_t** out_imageBuffer, int32_t* out_format)
RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
DataSourceSurface::MappedSurface map;
if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map))
return;
uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
if (!imageBuffer) {
dataSurface->Unmap();
return;
}
memcpy(imageBuffer, map.mData, mWidth * mHeight * 4);
dataSurface->Unmap();
int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
if (!mOptions.premultipliedAlpha) {
// We need to convert to INPUT_FORMAT_RGBA, otherwise
// we are automatically considered premult, and unpremult'd.
// Yes, it is THAT silly.
// Except for different lossy conversions by color,
// we could probably just change the label, and not change the data.
gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4);
format = imgIEncoder::INPUT_FORMAT_RGBA;
}
*out_imageBuffer = imageBuffer;
*out_format = format;
return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha,
out_imageBuffer, out_format);
}
NS_IMETHODIMP
@ -1103,20 +1079,18 @@ WebGLContext::GetInputStream(const char* mimeType,
if (!gl)
return NS_ERROR_FAILURE;
nsCString enccid("@mozilla.org/image/encoder;2?type=");
enccid += mimeType;
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
if (!encoder)
// Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
bool premult;
RefPtr<SourceSurface> snapshot =
GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
if (!snapshot)
return NS_ERROR_FAILURE;
nsAutoArrayPtr<uint8_t> imageBuffer;
int32_t format = 0;
GetImageBuffer(getter_Transfers(imageBuffer), &format);
if (!imageBuffer)
return NS_ERROR_FAILURE;
MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
encoder, encoderOptions, out_stream);
RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType,
encoderOptions, out_stream);
}
void
@ -1236,11 +1210,12 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
layers::LayersBackend
WebGLContext::GetCompositorBackendType() const
{
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
if (docWidget) {
layers::LayerManager* layerManager = docWidget->GetLayerManager();
return layerManager->GetCompositorBackendType();
if (mCanvasElement) {
return mCanvasElement->GetCompositorBackendType();
} else if (mOffscreenCanvas) {
return mOffscreenCanvas->GetCompositorBackendType();
}
return LayersBackend::LAYERS_NONE;
}

View File

@ -28,6 +28,7 @@ support-files =
imagebitmap_structuredclone.js
imagebitmap_structuredclone_iframe.html
offscreencanvas.js
offscreencanvas_mask.svg
offscreencanvas_neuter.js
offscreencanvas_serviceworker_inner.html
@ -266,6 +267,8 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
[test_filter.html]
[test_offscreencanvas_basic_webgl.html]
tags = offscreencanvas
[test_offscreencanvas_dynamic_fallback.html]
tags = offscreencanvas
[test_offscreencanvas_sharedworker.html]
tags = offscreencanvas
[test_offscreencanvas_serviceworker.html]

View File

@ -17,6 +17,14 @@ function finish() {
}
}
function drawCount(count) {
if (port) {
port.postMessage({type: "draw", count: count});
} else {
postMessage({type: "draw", count: count});
}
}
//--------------------------------------------------------------------
// WebGL Drawing Functions
//--------------------------------------------------------------------
@ -144,6 +152,7 @@ function createDrawFunc(canvas) {
preDraw(prefix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
postDraw(prefix);
gl.commit();
checkGLError(prefix);
};
}
@ -185,6 +194,22 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
}, 0);
}
//------------------------------------------------------------------------
// Test dynamic fallback
//------------------------------------------------------------------------
else if (test == "webgl_fallback") {
draw = createDrawFunc(canvas);
if (!draw) {
return;
}
var count = 0;
var iid = setInterval(function() {
++count;
draw("loop " + count);
drawCount(count);
}, 0);
}
//------------------------------------------------------------------------
// Canvas Size Change from Worker
//------------------------------------------------------------------------
else if (test == "webgl_changesize") {

View File

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<mask id="fade_mask_both" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
<linearGradient id="fade_gradient_both" gradientUnits="objectBoundingBox" x2="0" y2="1">
<stop stop-color="white" stop-opacity="0" offset="0"></stop>
<stop stop-color="white" stop-opacity="1" offset="0.2"></stop>
<stop stop-color="white" stop-opacity="1" offset="0.8"></stop>
<stop stop-color="white" stop-opacity="0" offset="1"></stop>
</linearGradient>
<rect x="0" y="0" width="1" height="1" fill="url(#fade_gradient_both)"></rect>
</mask>
</svg>

After

Width:  |  Height:  |  Size: 638 B

View File

@ -7,10 +7,23 @@
</head>
<body>
<canvas id="c" width="64" height="64"></canvas>
<canvas id="c-ref" width="64" height="64"></canvas>
<script>
SimpleTest.waitForExplicitFinish();
function testToDataURL() {
// testing toDataURL
// Fill c-ref with green color.
var c = document.getElementById("c-ref");
var ctx = c.getContext("2d");
ctx.rect(0, 0, 64, 64);
ctx.fillStyle = "#00FF00";
ctx.fill();
var htmlCanvas = document.getElementById("c");
ok(c.toDataURL() == htmlCanvas.toDataURL(), "toDataURL should return a 64x64 green square");
}
function runTest() {
var htmlCanvas = document.getElementById("c");
@ -25,6 +38,7 @@ function runTest() {
ok(msg.result, msg.name);
}
if (msg.type == "finish") {
testToDataURL();
worker.terminate();
SimpleTest.finish();
}

View File

@ -0,0 +1,80 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebGL in OffscreenCanvas</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<script>
SimpleTest.waitForExplicitFinish();
function createCanvas(initWithMask) {
var canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
document.body.appendChild(canvas);
if (initWithMask) {
canvas.style.mask = "url('offscreencanvas_mask.svg#fade_mask_both')";
}
return canvas;
}
function getRefSnapshot(initWithMask) {
var refCanvas = createCanvas(!initWithMask);
var ctx = refCanvas.getContext("2d");
ctx.rect(0, 0, 64, 64);
ctx.fillStyle = "#00FF00";
ctx.fill();
var result = snapshotWindow(window);
document.body.removeChild(refCanvas);
return result;
}
function runTest(initWithMask) {
var htmlCanvas = createCanvas(initWithMask);
var worker = new Worker("offscreencanvas.js");
worker.onmessage = function(evt) {
var msg = evt.data || {};
if (msg.type == "draw") {
if (msg.count === 10) {
// Change the fallback state dynamically when drawing count reaches 10.
if (initWithMask) {
htmlCanvas.style.mask = "";
} else {
htmlCanvas.style.mask = "url('offscreencanvas_mask.svg#fade_mask_both')";
}
} else if (msg.count === 20) {
var snapshotFallback = snapshotWindow(window);
worker.terminate();
document.body.removeChild(htmlCanvas);
var results = compareSnapshots(snapshotFallback, getRefSnapshot(initWithMask), true);
ok(results[0], "after dynamic fallback, screenshots should be the same");
if (initWithMask) {
SimpleTest.finish();
} else {
runTest(true);
}
}
}
}
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
worker.postMessage({test: 'webgl_fallback', canvas: offscreenCanvas}, [offscreenCanvas]);
}
SpecialPowers.pushPrefEnv({'set': [
['gfx.offscreencanvas.enabled', true],
['webgl.force-enabled', true],
]}, runTest.bind(false));
</script>
</body>
</html>

View File

@ -350,6 +350,7 @@ NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver)
HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: nsGenericHTMLElement(aNodeInfo),
mResetLayer(true) ,
mWriteOnly(false)
{
}
@ -690,6 +691,7 @@ HTMLCanvasElement::ExtractData(nsAString& aType,
aOptions,
GetSize(),
mCurrentContext,
mAsyncCanvasRenderer,
aStream);
}
@ -773,7 +775,10 @@ HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
renderer->SetWidth(sz.width);
renderer->SetHeight(sz.height);
mOffscreenCanvas = new OffscreenCanvas(sz.width, sz.height, renderer);
mOffscreenCanvas = new OffscreenCanvas(sz.width,
sz.height,
GetCompositorBackendType(),
renderer);
mContextObserver = new HTMLCanvasElementObserver(this);
} else {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@ -1042,7 +1047,8 @@ HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
}
if (mOffscreenCanvas) {
if (aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
if (!mResetLayer &&
aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
nsRefPtr<CanvasLayer> ret = aOldLayer;
return ret.forget();
}
@ -1055,7 +1061,12 @@ HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
LayerUserData* userData = nullptr;
layer->SetUserData(&sOffscreenCanvasLayerUserDataDummy, userData);
layer->SetAsyncRenderer(GetAsyncCanvasRenderer());
CanvasLayer::Data data;
data.mRenderer = GetAsyncCanvasRenderer();
data.mSize = GetWidthHeight();
layer->Initialize(data);
layer->Updated();
return layer.forget();
}
@ -1201,6 +1212,18 @@ HTMLCanvasElement::GetAsyncCanvasRenderer()
return mAsyncCanvasRenderer;
}
layers::LayersBackend
HTMLCanvasElement::GetCompositorBackendType() const
{
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc());
if (docWidget) {
layers::LayerManager* layerManager = docWidget->GetLayerManager();
return layerManager->GetCompositorBackendType();
}
return LayersBackend::LAYERS_NONE;
}
void
HTMLCanvasElement::OnVisibilityChange()
{
@ -1235,8 +1258,9 @@ HTMLCanvasElement::OnVisibilityChange()
};
nsRefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
if (mAsyncCanvasRenderer->mActiveThread) {
mAsyncCanvasRenderer->mActiveThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
if (activeThread) {
activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
}
return;
}
@ -1276,8 +1300,9 @@ HTMLCanvasElement::OnMemoryPressure()
};
nsRefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
if (mAsyncCanvasRenderer->mActiveThread) {
mAsyncCanvasRenderer->mActiveThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
if (activeThread) {
activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
}
return;
}
@ -1295,6 +1320,10 @@ HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer
return;
}
if (element->GetWidthHeight() == aRenderer->GetSize()) {
return;
}
gfx::IntSize asyncCanvasSize = aRenderer->GetSize();
ErrorResult rv;
@ -1307,6 +1336,19 @@ HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer
if (rv.Failed()) {
NS_WARNING("Failed to set height attribute to a canvas element asynchronously.");
}
element->mResetLayer = true;
}
/* static */ void
HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
{
HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
if (!element) {
return;
}
element->InvalidateCanvasContent(nullptr);
}
} // namespace dom

View File

@ -18,6 +18,7 @@
#include "mozilla/dom/CanvasRenderingContextHelper.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/layers/LayersTypes.h"
class nsICanvasRenderingContextInternal;
class nsITimerCallback;
@ -330,11 +331,14 @@ public:
nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
layers::LayersBackend GetCompositorBackendType() const;
void OnVisibilityChange();
void OnMemoryPressure();
static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
protected:
virtual ~HTMLCanvasElement();
@ -360,6 +364,7 @@ protected:
AsyncCanvasRenderer* GetAsyncCanvasRenderer();
bool mResetLayer;
nsRefPtr<HTMLCanvasElement> mOriginalCanvas;
nsRefPtr<PrintCallback> mPrintCallback;
nsRefPtr<HTMLCanvasPrintState> mPrintState;

View File

@ -533,8 +533,8 @@ ReadPixelsIntoDataSurface(GLContext* gl, DataSourceSurface* dest)
#endif
}
static already_AddRefed<DataSourceSurface>
YInvertImageSurface(DataSourceSurface* aSurf)
already_AddRefed<gfx::DataSourceSurface>
YInvertImageSurface(gfx::DataSourceSurface* aSurf)
{
RefPtr<DataSourceSurface> temp =
Factory::CreateDataSourceSurfaceWithStride(aSurf->GetSize(),
@ -560,8 +560,8 @@ YInvertImageSurface(DataSourceSurface* aSurf)
return nullptr;
}
dt->SetTransform(Matrix::Translation(0.0, aSurf->GetSize().height) *
Matrix::Scaling(1.0, -1.0));
dt->SetTransform(Matrix::Scaling(1.0, -1.0) *
Matrix::Translation(0.0, aSurf->GetSize().height));
Rect rect(0, 0, aSurf->GetSize().width, aSurf->GetSize().height);
dt->DrawSurface(aSurf, rect, rect, DrawSurfaceOptions(),
DrawOptions(1.0, CompositionOp::OP_SOURCE, AntialiasMode::NONE));

View File

@ -34,6 +34,9 @@ void ReadPixelsIntoDataSurface(GLContext* aGL,
already_AddRefed<gfx::DataSourceSurface>
ReadBackSurface(GLContext* gl, GLuint aTexture, bool aYInvert, gfx::SurfaceFormat aFormat);
already_AddRefed<gfx::DataSourceSurface>
YInvertImageSurface(gfx::DataSourceSurface* aSurf);
class GLReadTexImageHelper final
{
// The GLContext is the sole owner of the GLBlitHelper.

View File

@ -8,8 +8,12 @@
#include "gfxUtils.h"
#include "GLContext.h"
#include "GLReadTexImageHelper.h"
#include "GLScreenBuffer.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/layers/CanvasClient.h"
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/ReentrantMonitor.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
@ -17,10 +21,15 @@ namespace mozilla {
namespace layers {
AsyncCanvasRenderer::AsyncCanvasRenderer()
: mWidth(0)
: mHTMLCanvasElement(nullptr)
, mContext(nullptr)
, mGLContext(nullptr)
, mIsAlphaPremultiplied(true)
, mWidth(0)
, mHeight(0)
, mCanvasClientAsyncID(0)
, mCanvasClient(nullptr)
, mMutex("AsyncCanvasRenderer::mMutex")
{
MOZ_COUNT_CTOR(AsyncCanvasRenderer);
}
@ -65,6 +74,41 @@ AsyncCanvasRenderer::NotifyElementAboutAttributesChanged()
}
}
void
AsyncCanvasRenderer::NotifyElementAboutInvalidation()
{
class Runnable final : public nsRunnable
{
public:
Runnable(AsyncCanvasRenderer* aRenderer)
: mRenderer(aRenderer)
{}
NS_IMETHOD Run()
{
if (mRenderer) {
dom::HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(mRenderer);
}
return NS_OK;
}
void Revoke()
{
mRenderer = nullptr;
}
private:
nsRefPtr<AsyncCanvasRenderer> mRenderer;
};
nsRefPtr<nsRunnable> runnable = new Runnable(this);
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch a runnable to the main-thread.");
}
}
void
AsyncCanvasRenderer::SetCanvasClient(CanvasClient* aClient)
{
@ -76,5 +120,58 @@ AsyncCanvasRenderer::SetCanvasClient(CanvasClient* aClient)
}
}
void
AsyncCanvasRenderer::SetActiveThread()
{
MutexAutoLock lock(mMutex);
mActiveThread = NS_GetCurrentThread();
}
void
AsyncCanvasRenderer::ResetActiveThread()
{
MutexAutoLock lock(mMutex);
mActiveThread = nullptr;
}
already_AddRefed<nsIThread>
AsyncCanvasRenderer::GetActiveThread()
{
MutexAutoLock lock(mMutex);
nsCOMPtr<nsIThread> result = mActiveThread;
return result.forget();
}
already_AddRefed<gfx::DataSourceSurface>
AsyncCanvasRenderer::UpdateTarget()
{
// This function will be implemented in a later patch.
return nullptr;
}
already_AddRefed<gfx::DataSourceSurface>
AsyncCanvasRenderer::GetSurface()
{
MOZ_ASSERT(NS_IsMainThread());
return UpdateTarget();
}
nsresult
AsyncCanvasRenderer::GetInputStream(const char *aMimeType,
const char16_t *aEncoderOptions,
nsIInputStream **aStream)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<gfx::DataSourceSurface> surface = GetSurface();
if (!surface) {
return NS_ERROR_FAILURE;
}
// Handle y flip.
RefPtr<gfx::DataSourceSurface> dataSurf = gl::YInvertImageSurface(surface);
return gfxUtils::GetInputStream(dataSurf, false, aMimeType, aEncoderOptions, aStream);
}
} // namespace layers
} // namespace mozilla

View File

@ -7,15 +7,22 @@
#ifndef MOZILLA_LAYERS_ASYNCCANVASRENDERER_H_
#define MOZILLA_LAYERS_ASYNCCANVASRENDERER_H_
#include "LayersTypes.h"
#include "mozilla/gfx/Point.h" // for IntSize
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h" // for nsAutoPtr, nsRefPtr, etc
#include "nsCOMPtr.h" // for nsCOMPtr
class nsICanvasRenderingContextInternal;
class nsIInputStream;
class nsIThread;
namespace mozilla {
namespace gfx {
class DataSourceSurface;
}
namespace gl {
class GLContext;
}
@ -36,8 +43,13 @@ class CanvasClient;
* Each HTMLCanvasElement object is responsible for creating
* AsyncCanvasRenderer object. Once Canvas is transfered to worker,
* OffscreenCanvas will keep reference pointer of this object.
* This object will pass to ImageBridgeChild for submitting frames to
* Compositor.
*
* Sometimes main thread needs AsyncCanvasRenderer's result, such as layers
* fallback to BasicLayerManager or calling toDataURL in Javascript. Simply call
* GetSurface() in main thread will readback the result to mSurface.
*
* If layers backend is LAYERS_CLIENT, this object will pass to ImageBridgeChild
* for submitting frames to Compositor.
*/
class AsyncCanvasRenderer final
{
@ -47,6 +59,7 @@ public:
AsyncCanvasRenderer();
void NotifyElementAboutAttributesChanged();
void NotifyElementAboutInvalidation();
void SetCanvasClient(CanvasClient* aClient);
@ -60,6 +73,28 @@ public:
mHeight = aHeight;
}
void SetIsAlphaPremultiplied(bool aIsAlphaPremultiplied)
{
mIsAlphaPremultiplied = aIsAlphaPremultiplied;
}
// Active thread means the thread which spawns GLContext.
void SetActiveThread();
void ResetActiveThread();
// This will readback surface and return the surface
// in the DataSourceSurface.
// Can be called in main thread only.
already_AddRefed<gfx::DataSourceSurface> GetSurface();
// Readback current WebGL's content and convert it to InputStream. This
// function called GetSurface implicitly and GetSurface handles only get
// called in the main thread. So this function can be called in main thread.
nsresult
GetInputStream(const char *aMimeType,
const char16_t *aEncoderOptions,
nsIInputStream **aStream);
gfx::IntSize GetSize() const
{
return gfx::IntSize(mWidth, mHeight);
@ -75,26 +110,44 @@ public:
return mCanvasClient;
}
already_AddRefed<nsIThread> GetActiveThread();
// The lifetime is controllered by HTMLCanvasElement.
// Only accessed in main thread.
dom::HTMLCanvasElement* mHTMLCanvasElement;
// Only accessed in active thread.
nsICanvasRenderingContextInternal* mContext;
// We need to keep a reference to the context around here, otherwise the
// canvas' surface texture destructor will deref and destroy it too early
// Only accessed in active thread.
RefPtr<gl::GLContext> mGLContext;
nsCOMPtr<nsIThread> mActiveThread;
private:
virtual ~AsyncCanvasRenderer();
// Readback current WebGL's content and return it as DataSourceSurface.
already_AddRefed<gfx::DataSourceSurface> UpdateTarget();
bool mIsAlphaPremultiplied;
uint32_t mWidth;
uint32_t mHeight;
uint64_t mCanvasClientAsyncID;
// The lifetime of this pointer is controlled by OffscreenCanvas
// Can be accessed in active thread and ImageBridge thread.
// But we never accessed it at the same time on both thread. So no
// need to protect this member.
CanvasClient* mCanvasClient;
// Protect non thread-safe objects.
Mutex mMutex;
// Can be accessed in any thread, need protect by mutex.
nsCOMPtr<nsIThread> mActiveThread;
};
} // namespace layers

View File

@ -64,6 +64,9 @@ CopyableCanvasLayer::Initialize(const Data& aData)
}
} else if (aData.mBufferProvider) {
mBufferProvider = aData.mBufferProvider;
} else if (aData.mRenderer) {
mAsyncRenderer = aData.mRenderer;
mOriginPos = gl::OriginPos::BottomLeft;
} else {
MOZ_CRASH("CanvasLayer created without mSurface, mDrawTarget or mGLContext?");
}
@ -80,11 +83,9 @@ CopyableCanvasLayer::IsDataValid(const Data& aData)
void
CopyableCanvasLayer::UpdateTarget(DrawTarget* aDestTarget)
{
if (!mBufferProvider && !mGLContext) {
return;
}
if (mBufferProvider) {
if (mAsyncRenderer) {
mSurface = mAsyncRenderer->GetSurface();
} else if (mBufferProvider) {
mSurface = mBufferProvider->GetSnapshot();
}
@ -99,10 +100,12 @@ CopyableCanvasLayer::UpdateTarget(DrawTarget* aDestTarget)
return;
}
if (mBufferProvider) {
if (mBufferProvider || mAsyncRenderer) {
return;
}
MOZ_ASSERT(mGLContext);
SharedSurface* frontbuffer = nullptr;
if (mGLFrontbuffer) {
frontbuffer = mGLFrontbuffer.get();

View File

@ -2118,12 +2118,6 @@ CanvasLayer::CanvasLayer(LayerManager* aManager, void* aImplData)
CanvasLayer::~CanvasLayer()
{}
void
CanvasLayer::SetAsyncRenderer(AsyncCanvasRenderer *aAsyncRenderer)
{
mAsyncRenderer = aAsyncRenderer;
}
void
CanvasLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix)
{

View File

@ -2269,15 +2269,17 @@ public:
Data()
: mBufferProvider(nullptr)
, mGLContext(nullptr)
, mRenderer(nullptr)
, mFrontbufferGLTex(0)
, mSize(0,0)
, mHasAlpha(false)
, mIsGLAlphaPremult(true)
{ }
// One of these two must be specified for Canvas2D, but never both
// One of these three must be specified for Canvas2D, but never more than one
PersistentBufferProvider* mBufferProvider; // A BufferProvider for the Canvas contents
mozilla::gl::GLContext* mGLContext; // or this, for GL.
AsyncCanvasRenderer* mRenderer; // or this, for OffscreenCanvas
// Frontbuffer override
uint32_t mFrontbufferGLTex;
@ -2398,8 +2400,6 @@ public:
return !!mAsyncRenderer;
}
void SetAsyncRenderer(AsyncCanvasRenderer *aAsyncRenderer);
protected:
CanvasLayer(LayerManager* aManager, void* aImplData);
virtual ~CanvasLayer();

View File

@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BasicCanvasLayer.h"
#include "AsyncCanvasRenderer.h"
#include "basic/BasicLayers.h" // for BasicLayerManager
#include "basic/BasicLayersImpl.h" // for GetEffectiveOperator
#include "mozilla/mozalloc.h" // for operator new

View File

@ -12,6 +12,7 @@
#include "gfxDrawable.h"
#include "imgIEncoder.h"
#include "mozilla/Base64.h"
#include "mozilla/dom/ImageEncoder.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/gfx/2D.h"
@ -1546,6 +1547,69 @@ gfxUtils::CopyAsDataURI(DrawTarget* aDT)
}
}
/* static */ void
gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface,
bool aIsAlphaPremultiplied,
uint8_t** outImageBuffer,
int32_t* outFormat)
{
*outImageBuffer = nullptr;
*outFormat = 0;
DataSourceSurface::MappedSurface map;
if (!aSurface->Map(DataSourceSurface::MapType::READ, &map))
return;
uint32_t bufferSize = aSurface->GetSize().width * aSurface->GetSize().height * 4;
uint8_t* imageBuffer = new (fallible) uint8_t[bufferSize];
if (!imageBuffer) {
aSurface->Unmap();
return;
}
memcpy(imageBuffer, map.mData, bufferSize);
aSurface->Unmap();
int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
if (!aIsAlphaPremultiplied) {
// We need to convert to INPUT_FORMAT_RGBA, otherwise
// we are automatically considered premult, and unpremult'd.
// Yes, it is THAT silly.
// Except for different lossy conversions by color,
// we could probably just change the label, and not change the data.
gfxUtils::ConvertBGRAtoRGBA(imageBuffer, bufferSize);
format = imgIEncoder::INPUT_FORMAT_RGBA;
}
*outImageBuffer = imageBuffer;
*outFormat = format;
}
/* static */ nsresult
gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
bool aIsAlphaPremultiplied,
const char* aMimeType,
const char16_t* aEncoderOptions,
nsIInputStream** outStream)
{
nsCString enccid("@mozilla.org/image/encoder;2?type=");
enccid += aMimeType;
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
if (!encoder)
return NS_ERROR_FAILURE;
nsAutoArrayPtr<uint8_t> imageBuffer;
int32_t format = 0;
GetImageBuffer(aSurface, aIsAlphaPremultiplied, getter_Transfers(imageBuffer), &format);
if (!imageBuffer)
return NS_ERROR_FAILURE;
return dom::ImageEncoder::GetInputStream(aSurface->GetSize().width,
aSurface->GetSize().height,
imageBuffer, format,
encoder, aEncoderOptions, outStream);
}
class GetFeatureStatusRunnable final : public dom::workers::WorkerMainThreadRunnable
{
public:

View File

@ -17,6 +17,7 @@
class gfxASurface;
class gfxDrawable;
class nsIInputStream;
class nsIGfxInfo;
class nsIntRegion;
class nsIPresShell;
@ -282,6 +283,17 @@ public:
static nsCString GetAsDataURI(DrawTarget* aDT);
static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface);
static void GetImageBuffer(DataSourceSurface* aSurface,
bool aIsAlphaPremultiplied,
uint8_t** outImageBuffer,
int32_t* outFormat);
static nsresult GetInputStream(DataSourceSurface* aSurface,
bool aIsAlphaPremultiplied,
const char* aMimeType,
const char16_t* aEncoderOptions,
nsIInputStream** outStream);
static nsresult ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo>& gfxInfo,
int32_t feature,
int32_t* status);