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