1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "OffscreenCanvasDisplayHelper.h"
8 #include "mozilla/gfx/2D.h"
9 #include "mozilla/gfx/CanvasManagerChild.h"
10 #include "mozilla/gfx/Swizzle.h"
11 #include "mozilla/layers/ImageBridgeChild.h"
12 #include "mozilla/layers/TextureClientSharedSurface.h"
13 #include "mozilla/layers/TextureWrapperImage.h"
14 #include "mozilla/SVGObserverUtils.h"
15 #include "nsICanvasRenderingContextInternal.h"
16 
17 namespace mozilla::dom {
18 
OffscreenCanvasDisplayHelper(HTMLCanvasElement * aCanvasElement,uint32_t aWidth,uint32_t aHeight)19 OffscreenCanvasDisplayHelper::OffscreenCanvasDisplayHelper(
20     HTMLCanvasElement* aCanvasElement, uint32_t aWidth, uint32_t aHeight)
21     : mMutex("mozilla::dom::OffscreenCanvasDisplayHelper"),
22       mCanvasElement(aCanvasElement),
23       mImageProducerID(layers::ImageContainer::AllocateProducerID()) {
24   mData.mSize.width = aWidth;
25   mData.mSize.height = aHeight;
26 }
27 
28 OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() = default;
29 
Destroy()30 void OffscreenCanvasDisplayHelper::Destroy() {
31   MOZ_ASSERT(NS_IsMainThread());
32 
33   MutexAutoLock lock(mMutex);
34   mCanvasElement = nullptr;
35 }
36 
GetContextType() const37 CanvasContextType OffscreenCanvasDisplayHelper::GetContextType() const {
38   MutexAutoLock lock(mMutex);
39   return mType;
40 }
41 
GetImageContainer() const42 RefPtr<layers::ImageContainer> OffscreenCanvasDisplayHelper::GetImageContainer()
43     const {
44   MutexAutoLock lock(mMutex);
45   return mImageContainer;
46 }
47 
UpdateContext(CanvasContextType aType,const Maybe<int32_t> & aChildId)48 void OffscreenCanvasDisplayHelper::UpdateContext(
49     CanvasContextType aType, const Maybe<int32_t>& aChildId) {
50   MutexAutoLock lock(mMutex);
51   mImageContainer =
52       MakeRefPtr<layers::ImageContainer>(layers::ImageContainer::ASYNCHRONOUS);
53   mType = aType;
54   mContextChildId = aChildId;
55 
56   if (aChildId) {
57     mContextManagerId = Some(gfx::CanvasManagerChild::Get()->Id());
58   } else {
59     mContextManagerId.reset();
60   }
61 
62   MaybeQueueInvalidateElement();
63 }
64 
CommitFrameToCompositor(nsICanvasRenderingContextInternal * aContext,layers::TextureType aTextureType,const Maybe<OffscreenCanvasDisplayData> & aData)65 bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
66     nsICanvasRenderingContextInternal* aContext,
67     layers::TextureType aTextureType,
68     const Maybe<OffscreenCanvasDisplayData>& aData) {
69   MutexAutoLock lock(mMutex);
70 
71   gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
72   layers::TextureFlags flags = layers::TextureFlags::IMMUTABLE;
73 
74   if (!mCanvasElement) {
75     // Our weak reference to the canvas element has been cleared, so we cannot
76     // present directly anymore.
77     return false;
78   }
79 
80   if (aData) {
81     mData = aData.ref();
82     MaybeQueueInvalidateElement();
83   }
84 
85   if (mData.mIsOpaque) {
86     flags |= layers::TextureFlags::IS_OPAQUE;
87     format = gfx::SurfaceFormat::B8G8R8X8;
88   } else if (!mData.mIsAlphaPremult) {
89     flags |= layers::TextureFlags::NON_PREMULTIPLIED;
90   }
91 
92   switch (mData.mOriginPos) {
93     case gl::OriginPos::BottomLeft:
94       flags |= layers::TextureFlags::ORIGIN_BOTTOM_LEFT;
95       break;
96     case gl::OriginPos::TopLeft:
97       break;
98     default:
99       MOZ_ASSERT_UNREACHABLE("Unhandled origin position!");
100       break;
101   }
102 
103   auto imageBridge = layers::ImageBridgeChild::GetSingleton();
104   if (!imageBridge) {
105     return false;
106   }
107 
108   if (mData.mDoPaintCallbacks) {
109     aContext->OnBeforePaintTransaction();
110   }
111 
112   RefPtr<layers::Image> image;
113   RefPtr<gfx::SourceSurface> surface;
114 
115   Maybe<layers::SurfaceDescriptor> desc =
116       aContext->PresentFrontBuffer(nullptr, aTextureType);
117   if (desc) {
118     RefPtr<layers::TextureClient> texture =
119         layers::SharedSurfaceTextureData::CreateTextureClient(
120             *desc, format, mData.mSize, flags, imageBridge);
121     if (texture) {
122       image = new layers::TextureWrapperImage(
123           texture, gfx::IntRect(gfx::IntPoint(0, 0), mData.mSize));
124     }
125   } else {
126     surface = aContext->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
127     if (surface) {
128       auto surfaceImage = MakeRefPtr<layers::SourceSurfaceImage>(surface);
129       surfaceImage->SetTextureFlags(flags);
130       image = surfaceImage;
131     }
132   }
133 
134   if (mData.mDoPaintCallbacks) {
135     aContext->OnDidPaintTransaction();
136   }
137 
138   if (image) {
139     AutoTArray<layers::ImageContainer::NonOwningImage, 1> imageList;
140     imageList.AppendElement(layers::ImageContainer::NonOwningImage(
141         image, TimeStamp(), mLastFrameID++, mImageProducerID));
142     mImageContainer->SetCurrentImages(imageList);
143   } else {
144     mImageContainer->ClearAllImages();
145   }
146 
147   // We save the current surface because we might need it in GetSnapshot. If we
148   // are on a worker thread and not WebGL, then this will be the only way we can
149   // access the pixel data on the main thread.
150   mFrontBufferSurface = surface;
151   return true;
152 }
153 
MaybeQueueInvalidateElement()154 void OffscreenCanvasDisplayHelper::MaybeQueueInvalidateElement() {
155   mMutex.AssertCurrentThreadOwns();
156 
157   if (!mPendingInvalidate) {
158     mPendingInvalidate = true;
159     NS_DispatchToMainThread(NS_NewRunnableFunction(
160         "OffscreenCanvasDisplayHelper::InvalidateElement",
161         [self = RefPtr{this}] { self->InvalidateElement(); }));
162   }
163 }
164 
InvalidateElement()165 void OffscreenCanvasDisplayHelper::InvalidateElement() {
166   MOZ_ASSERT(NS_IsMainThread());
167 
168   HTMLCanvasElement* canvasElement;
169   gfx::IntSize size;
170 
171   {
172     MutexAutoLock lock(mMutex);
173     MOZ_ASSERT(mPendingInvalidate);
174     mPendingInvalidate = false;
175     canvasElement = mCanvasElement;
176     size = mData.mSize;
177   }
178 
179   if (canvasElement) {
180     SVGObserverUtils::InvalidateDirectRenderingObservers(canvasElement);
181     canvasElement->InvalidateCanvasPlaceholder(size.width, size.height);
182     canvasElement->InvalidateCanvasContent(nullptr);
183   }
184 }
185 
TransformSurface(const gfx::DataSourceSurface::ScopedMap & aSrcMap,const gfx::DataSourceSurface::ScopedMap & aDstMap,gfx::SurfaceFormat aFormat,const gfx::IntSize & aSize,bool aNeedsPremult,gl::OriginPos aOriginPos) const186 bool OffscreenCanvasDisplayHelper::TransformSurface(
187     const gfx::DataSourceSurface::ScopedMap& aSrcMap,
188     const gfx::DataSourceSurface::ScopedMap& aDstMap,
189     gfx::SurfaceFormat aFormat, const gfx::IntSize& aSize, bool aNeedsPremult,
190     gl::OriginPos aOriginPos) const {
191   if (!aSrcMap.IsMapped() || !aDstMap.IsMapped()) {
192     return false;
193   }
194 
195   switch (aOriginPos) {
196     case gl::OriginPos::BottomLeft:
197       if (aNeedsPremult) {
198         return gfx::PremultiplyYFlipData(aSrcMap.GetData(), aSrcMap.GetStride(),
199                                          aFormat, aDstMap.GetData(),
200                                          aDstMap.GetStride(), aFormat, aSize);
201       }
202       return gfx::SwizzleYFlipData(aSrcMap.GetData(), aSrcMap.GetStride(),
203                                    aFormat, aDstMap.GetData(),
204                                    aDstMap.GetStride(), aFormat, aSize);
205     case gl::OriginPos::TopLeft:
206       if (aNeedsPremult) {
207         return gfx::PremultiplyData(aSrcMap.GetData(), aSrcMap.GetStride(),
208                                     aFormat, aDstMap.GetData(),
209                                     aDstMap.GetStride(), aFormat, aSize);
210       }
211       return gfx::SwizzleData(aSrcMap.GetData(), aSrcMap.GetStride(), aFormat,
212                               aDstMap.GetData(), aDstMap.GetStride(), aFormat,
213                               aSize);
214     default:
215       MOZ_ASSERT_UNREACHABLE("Unhandled origin position!");
216       break;
217   }
218 
219   return false;
220 }
221 
222 already_AddRefed<gfx::SourceSurface>
GetSurfaceSnapshot()223 OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
224   MOZ_ASSERT(NS_IsMainThread());
225 
226   Maybe<layers::SurfaceDescriptor> desc;
227 
228   bool hasAlpha;
229   bool isAlphaPremult;
230   gl::OriginPos originPos;
231   Maybe<uint32_t> managerId;
232   Maybe<int32_t> childId;
233   HTMLCanvasElement* canvasElement;
234   RefPtr<gfx::SourceSurface> surface;
235 
236   {
237     MutexAutoLock lock(mMutex);
238     hasAlpha = !mData.mIsOpaque;
239     isAlphaPremult = mData.mIsAlphaPremult;
240     originPos = mData.mOriginPos;
241     managerId = mContextManagerId;
242     childId = mContextChildId;
243     canvasElement = mCanvasElement;
244     surface = mFrontBufferSurface;
245   }
246 
247   if (surface) {
248     // We already have a copy of the front buffer in our process.
249 
250     if (originPos == gl::OriginPos::TopLeft && (!hasAlpha || isAlphaPremult)) {
251       // If we don't need to y-flip, and it is either opaque or premultiplied,
252       // we can just return the same surface.
253       return surface.forget();
254     }
255 
256     // Otherwise we need to copy and apply the necessary transformations.
257     RefPtr<gfx::DataSourceSurface> srcSurface = surface->GetDataSurface();
258     if (!srcSurface) {
259       return nullptr;
260     }
261 
262     const auto size = srcSurface->GetSize();
263     const auto format = srcSurface->GetFormat();
264 
265     RefPtr<gfx::DataSourceSurface> dstSurface =
266         gfx::Factory::CreateDataSourceSurface(size, format, /* aZero */ false);
267     if (!dstSurface) {
268       return nullptr;
269     }
270 
271     gfx::DataSourceSurface::ScopedMap srcMap(srcSurface,
272                                              gfx::DataSourceSurface::READ);
273     gfx::DataSourceSurface::ScopedMap dstMap(dstSurface,
274                                              gfx::DataSourceSurface::WRITE);
275     if (!TransformSurface(srcMap, dstMap, format, size,
276                           hasAlpha && !isAlphaPremult, originPos)) {
277       return nullptr;
278     }
279 
280     return dstSurface.forget();
281   }
282 
283 #ifdef MOZ_WIDGET_ANDROID
284   // On Android, we cannot both display a GL context and read back the pixels.
285   if (canvasElement) {
286     return nullptr;
287   }
288 #endif
289 
290   if (managerId && childId) {
291     // We don't have a usable surface, and the context lives in the compositor
292     // process.
293     return gfx::CanvasManagerChild::Get()->GetSnapshot(
294         managerId.value(), childId.value(), hasAlpha);
295   }
296 
297   // If we don't have any protocol IDs, or an existing surface, it is possible
298   // it is a main thread OffscreenCanvas instance. If so, then the element's
299   // OffscreenCanvas is not neutered and has access to the context. We can use
300   // that to get the snapshot directly.
301   if (!canvasElement) {
302     return nullptr;
303   }
304 
305   const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas();
306   nsICanvasRenderingContextInternal* context = offscreenCanvas->GetContext();
307   if (!context) {
308     return nullptr;
309   }
310 
311   surface = context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
312   if (!surface) {
313     return nullptr;
314   }
315 
316   if (originPos == gl::OriginPos::TopLeft && (!hasAlpha || isAlphaPremult)) {
317     // If we don't need to y-flip, and it is either opaque or premultiplied,
318     // we can just return the same surface.
319     return surface.forget();
320   }
321 
322   // Otherwise we need to apply the necessary transformations in place.
323   RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
324   if (!dataSurface) {
325     return nullptr;
326   }
327 
328   gfx::DataSourceSurface::ScopedMap map(dataSurface,
329                                         gfx::DataSourceSurface::READ_WRITE);
330   if (!TransformSurface(map, map, dataSurface->GetFormat(),
331                         dataSurface->GetSize(), hasAlpha && !isAlphaPremult,
332                         originPos)) {
333     return nullptr;
334   }
335 
336   return surface.forget();
337 }
338 
GetAsImage()339 already_AddRefed<layers::Image> OffscreenCanvasDisplayHelper::GetAsImage() {
340   MOZ_ASSERT(NS_IsMainThread());
341 
342   RefPtr<gfx::SourceSurface> surface = GetSurfaceSnapshot();
343   if (!surface) {
344     return nullptr;
345   }
346   return MakeAndAddRef<layers::SourceSurfaceImage>(surface);
347 }
348 
349 }  // namespace mozilla::dom
350