diff --git a/dom/base/ImageEncoder.cpp b/dom/base/ImageEncoder.cpp index 366d26ef117d..74dff8734e46 100644 --- a/dom/base/ImageEncoder.cpp +++ b/dom/base/ImageEncoder.cpp @@ -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 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. diff --git a/dom/base/ImageEncoder.h b/dom/base/ImageEncoder.h index fe3522daff55..a38bbd209140 100644 --- a/dom/base/ImageEncoder.h +++ b/dom/base/ImageEncoder.h @@ -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); diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp index 8489ca9e2803..79e4806d7521 100644 --- a/dom/canvas/OffscreenCanvas.cpp +++ b/dom/canvas/OffscreenCanvas.cpp @@ -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 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 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(mCurrentContext.get())->GL(); + if (mCanvasRenderer) { + WebGLContext* webGL = static_cast(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 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 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 @@ -174,7 +199,8 @@ OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData) { MOZ_ASSERT(aData); nsRefPtr wc = - new OffscreenCanvas(aData->mWidth, aData->mHeight, aData->mRenderer); + new OffscreenCanvas(aData->mWidth, aData->mHeight, + aData->mCompositorBackendType, aData->mRenderer); if (aData->mNeutered) { wc->SetNeutered(); } diff --git a/dom/canvas/OffscreenCanvas.h b/dom/canvas/OffscreenCanvas.h index f2bddd0af01c..cdad6bcce8e7 100644 --- a/dom/canvas/OffscreenCanvas.h +++ b/dom/canvas/OffscreenCanvas.h @@ -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 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 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 mCanvasRenderer; }; diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index 2ffeaf694bd0..777d1f6787c0 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -1064,32 +1064,8 @@ WebGLContext::GetImageBuffer(uint8_t** out_imageBuffer, int32_t* out_format) RefPtr 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 @@ -1101,20 +1077,18 @@ WebGLContext::GetInputStream(const char* mimeType, if (!gl) return NS_ERROR_FAILURE; - nsCString enccid("@mozilla.org/image/encoder;2?type="); - enccid += mimeType; - nsCOMPtr encoder = do_CreateInstance(enccid.get()); - if (!encoder) + // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied + bool premult; + RefPtr snapshot = + GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult); + if (!snapshot) return NS_ERROR_FAILURE; - nsAutoArrayPtr 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 dataSurface = snapshot->GetDataSurface(); + return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType, + encoderOptions, out_stream); } void @@ -1234,11 +1208,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; } diff --git a/dom/canvas/test/mochitest.ini b/dom/canvas/test/mochitest.ini index 12b23e2df386..01565543a663 100644 --- a/dom/canvas/test/mochitest.ini +++ b/dom/canvas/test/mochitest.ini @@ -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] diff --git a/dom/canvas/test/offscreencanvas.js b/dom/canvas/test/offscreencanvas.js index 584954c3ef71..6dec8220f462 100644 --- a/dom/canvas/test/offscreencanvas.js +++ b/dom/canvas/test/offscreencanvas.js @@ -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") { diff --git a/dom/canvas/test/offscreencanvas_mask.svg b/dom/canvas/test/offscreencanvas_mask.svg new file mode 100644 index 000000000000..34347b68b37b --- /dev/null +++ b/dom/canvas/test/offscreencanvas_mask.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_basic_webgl.html b/dom/canvas/test/test_offscreencanvas_basic_webgl.html index dd3ac21e748b..55b186812050 100644 --- a/dom/canvas/test/test_offscreencanvas_basic_webgl.html +++ b/dom/canvas/test/test_offscreencanvas_basic_webgl.html @@ -7,10 +7,23 @@ + + + + + + + + diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index daa493f8ca51..1d2c8dae1821 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -350,6 +350,7 @@ NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver) HTMLCanvasElement::HTMLCanvasElement(already_AddRefed& 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 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 runnable = new Runnable(mAsyncCanvasRenderer); - if (mAsyncCanvasRenderer->mActiveThread) { - mAsyncCanvasRenderer->mActiveThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + nsCOMPtr activeThread = mAsyncCanvasRenderer->GetActiveThread(); + if (activeThread) { + activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); } return; } @@ -1276,8 +1300,9 @@ HTMLCanvasElement::OnMemoryPressure() }; nsRefPtr runnable = new Runnable(mAsyncCanvasRenderer); - if (mAsyncCanvasRenderer->mActiveThread) { - mAsyncCanvasRenderer->mActiveThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + nsCOMPtr 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 diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index 129ec0fe2c4f..c149c5aa55d5 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -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 mOriginalCanvas; nsRefPtr mPrintCallback; nsRefPtr mPrintState; diff --git a/gfx/gl/GLReadTexImageHelper.cpp b/gfx/gl/GLReadTexImageHelper.cpp index 0d7ef47b6006..0efea4c92eec 100644 --- a/gfx/gl/GLReadTexImageHelper.cpp +++ b/gfx/gl/GLReadTexImageHelper.cpp @@ -533,8 +533,8 @@ ReadPixelsIntoDataSurface(GLContext* gl, DataSourceSurface* dest) #endif } -static already_AddRefed -YInvertImageSurface(DataSourceSurface* aSurf) +already_AddRefed +YInvertImageSurface(gfx::DataSourceSurface* aSurf) { RefPtr temp = Factory::CreateDataSourceSurfaceWithStride(aSurf->GetSize(), diff --git a/gfx/gl/GLReadTexImageHelper.h b/gfx/gl/GLReadTexImageHelper.h index 821c003a96a1..7adc4edba482 100644 --- a/gfx/gl/GLReadTexImageHelper.h +++ b/gfx/gl/GLReadTexImageHelper.h @@ -34,6 +34,9 @@ void ReadPixelsIntoDataSurface(GLContext* aGL, already_AddRefed ReadBackSurface(GLContext* gl, GLuint aTexture, bool aYInvert, gfx::SurfaceFormat aFormat); +already_AddRefed +YInvertImageSurface(gfx::DataSourceSurface* aSurf); + class GLReadTexImageHelper final { // The GLContext is the sole owner of the GLBlitHelper. diff --git a/gfx/layers/AsyncCanvasRenderer.cpp b/gfx/layers/AsyncCanvasRenderer.cpp index 8ec64a72b5a4..49ca60b9d0fd 100644 --- a/gfx/layers/AsyncCanvasRenderer.cpp +++ b/gfx/layers/AsyncCanvasRenderer.cpp @@ -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: + explicit Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer) { + dom::HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(mRenderer); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + nsRefPtr mRenderer; + }; + + nsRefPtr 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 +AsyncCanvasRenderer::GetActiveThread() +{ + MutexAutoLock lock(mMutex); + nsCOMPtr result = mActiveThread; + return result.forget(); +} + +already_AddRefed +AsyncCanvasRenderer::UpdateTarget() +{ + // This function will be implemented in a later patch. + return nullptr; +} + +already_AddRefed +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 surface = GetSurface(); + if (!surface) { + return NS_ERROR_FAILURE; + } + + // Handle y flip. + RefPtr dataSurf = gl::YInvertImageSurface(surface); + + return gfxUtils::GetInputStream(dataSurf, false, aMimeType, aEncoderOptions, aStream); +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/AsyncCanvasRenderer.h b/gfx/layers/AsyncCanvasRenderer.h index a5af5fea7bfa..993669141bef 100644 --- a/gfx/layers/AsyncCanvasRenderer.h +++ b/gfx/layers/AsyncCanvasRenderer.h @@ -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 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 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 mGLContext; - - nsCOMPtr mActiveThread; private: virtual ~AsyncCanvasRenderer(); + // Readback current WebGL's content and return it as DataSourceSurface. + already_AddRefed 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 mActiveThread; }; } // namespace layers diff --git a/gfx/layers/CopyableCanvasLayer.cpp b/gfx/layers/CopyableCanvasLayer.cpp index 22acc69e26f4..0fad0aa3668b 100644 --- a/gfx/layers/CopyableCanvasLayer.cpp +++ b/gfx/layers/CopyableCanvasLayer.cpp @@ -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(); diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 5b66d882b78a..ab618aea97c1 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -2146,12 +2146,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) { diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index 6c3de1949659..f7e5f66e5775 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -2273,15 +2273,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; @@ -2402,8 +2404,6 @@ public: return !!mAsyncRenderer; } - void SetAsyncRenderer(AsyncCanvasRenderer *aAsyncRenderer); - protected: CanvasLayer(LayerManager* aManager, void* aImplData); virtual ~CanvasLayer(); diff --git a/gfx/layers/basic/BasicCanvasLayer.cpp b/gfx/layers/basic/BasicCanvasLayer.cpp index 6d4d977c34a1..139330d72730 100644 --- a/gfx/layers/basic/BasicCanvasLayer.cpp +++ b/gfx/layers/basic/BasicCanvasLayer.cpp @@ -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 diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index c7adf743625d..5b0eb47d1d4c 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -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 encoder = do_CreateInstance(enccid.get()); + if (!encoder) + return NS_ERROR_FAILURE; + + nsAutoArrayPtr 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: diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 539093f56f25..9f6a7ca179f9 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -16,6 +16,7 @@ class gfxASurface; class gfxDrawable; +class nsIInputStream; class nsIGfxInfo; class nsIntRegion; class nsIPresShell; @@ -281,6 +282,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& gfxInfo, int32_t feature, int32_t* status);