1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "CanvasChild.h"
8
9 #include "MainThreadUtils.h"
10 #include "mozilla/gfx/DrawTargetRecording.h"
11 #include "mozilla/gfx/Tools.h"
12 #include "mozilla/gfx/Rect.h"
13 #include "mozilla/gfx/Point.h"
14 #include "mozilla/ipc/Endpoint.h"
15 #include "mozilla/ipc/ProcessChild.h"
16 #include "mozilla/layers/CanvasDrawEventRecorder.h"
17 #include "nsIObserverService.h"
18 #include "RecordedCanvasEventImpl.h"
19
20 namespace mozilla {
21 namespace layers {
22
23 class RingBufferWriterServices final
24 : public CanvasEventRingBuffer::WriterServices {
25 public:
RingBufferWriterServices(RefPtr<CanvasChild> aCanvasChild)26 explicit RingBufferWriterServices(RefPtr<CanvasChild> aCanvasChild)
27 : mCanvasChild(std::move(aCanvasChild)) {}
28
29 ~RingBufferWriterServices() final = default;
30
ReaderClosed()31 bool ReaderClosed() final {
32 return !mCanvasChild->GetIPCChannel()->CanSend() ||
33 ipc::ProcessChild::ExpectingShutdown();
34 }
35
ResumeReader()36 void ResumeReader() final { mCanvasChild->ResumeTranslation(); }
37
38 private:
39 RefPtr<CanvasChild> mCanvasChild;
40 };
41
42 class SourceSurfaceCanvasRecording final : public gfx::SourceSurface {
43 public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording,final)44 MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final)
45
46 SourceSurfaceCanvasRecording(
47 const RefPtr<gfx::SourceSurface>& aRecordedSuface,
48 CanvasChild* aCanvasChild,
49 const RefPtr<CanvasDrawEventRecorder>& aRecorder)
50 : mRecordedSurface(aRecordedSuface),
51 mCanvasChild(aCanvasChild),
52 mRecorder(aRecorder) {
53 mRecorder->RecordEvent(RecordedAddSurfaceAlias(this, aRecordedSuface));
54 mRecorder->AddStoredObject(this);
55 }
56
~SourceSurfaceCanvasRecording()57 ~SourceSurfaceCanvasRecording() {
58 ReleaseOnMainThread(std::move(mRecorder), this, std::move(mRecordedSurface),
59 std::move(mCanvasChild));
60 }
61
GetType() const62 gfx::SurfaceType GetType() const final { return mRecordedSurface->GetType(); }
63
GetSize() const64 gfx::IntSize GetSize() const final { return mRecordedSurface->GetSize(); }
65
GetFormat() const66 gfx::SurfaceFormat GetFormat() const final {
67 return mRecordedSurface->GetFormat();
68 }
69
GetDataSurface()70 already_AddRefed<gfx::DataSourceSurface> GetDataSurface() final {
71 EnsureDataSurfaceOnMainThread();
72 return do_AddRef(mDataSourceSurface);
73 }
74
75 private:
EnsureDataSurfaceOnMainThread()76 void EnsureDataSurfaceOnMainThread() {
77 // The data can only be retrieved on the main thread.
78 if (!mDataSourceSurface && NS_IsMainThread()) {
79 mDataSourceSurface = mCanvasChild->GetDataSurface(mRecordedSurface);
80 }
81 }
82
83 // Used to ensure that clean-up that requires it is done on the main thread.
ReleaseOnMainThread(RefPtr<CanvasDrawEventRecorder> aRecorder,ReferencePtr aSurfaceAlias,RefPtr<gfx::SourceSurface> aAliasedSurface,RefPtr<CanvasChild> aCanvasChild)84 static void ReleaseOnMainThread(RefPtr<CanvasDrawEventRecorder> aRecorder,
85 ReferencePtr aSurfaceAlias,
86 RefPtr<gfx::SourceSurface> aAliasedSurface,
87 RefPtr<CanvasChild> aCanvasChild) {
88 if (!NS_IsMainThread()) {
89 NS_DispatchToMainThread(NewRunnableFunction(
90 "SourceSurfaceCanvasRecording::ReleaseOnMainThread",
91 SourceSurfaceCanvasRecording::ReleaseOnMainThread,
92 std::move(aRecorder), aSurfaceAlias, std::move(aAliasedSurface),
93 std::move(aCanvasChild)));
94 return;
95 }
96
97 aRecorder->RemoveStoredObject(aSurfaceAlias);
98 aRecorder->RecordEvent(RecordedRemoveSurfaceAlias(aSurfaceAlias));
99 aAliasedSurface = nullptr;
100 aCanvasChild = nullptr;
101 aRecorder = nullptr;
102 }
103
104 RefPtr<gfx::SourceSurface> mRecordedSurface;
105 RefPtr<CanvasChild> mCanvasChild;
106 RefPtr<CanvasDrawEventRecorder> mRecorder;
107 RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
108 };
109
CanvasChild(Endpoint<PCanvasChild> && aEndpoint)110 CanvasChild::CanvasChild(Endpoint<PCanvasChild>&& aEndpoint) {
111 aEndpoint.Bind(this);
112 }
113
114 CanvasChild::~CanvasChild() = default;
115
NotifyCanvasDeviceReset()116 static void NotifyCanvasDeviceReset() {
117 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
118 if (obs) {
119 obs->NotifyObservers(nullptr, "canvas-device-reset", nullptr);
120 }
121 }
122
RecvNotifyDeviceChanged()123 ipc::IPCResult CanvasChild::RecvNotifyDeviceChanged() {
124 NotifyCanvasDeviceReset();
125 mRecorder->RecordEvent(RecordedDeviceChangeAcknowledged());
126 return IPC_OK();
127 }
128
129 /* static */ bool CanvasChild::mDeactivated = false;
130
RecvDeactivate()131 ipc::IPCResult CanvasChild::RecvDeactivate() {
132 mDeactivated = true;
133 NotifyCanvasDeviceReset();
134 return IPC_OK();
135 }
136
EnsureRecorder(TextureType aTextureType)137 void CanvasChild::EnsureRecorder(TextureType aTextureType) {
138 if (!mRecorder) {
139 MOZ_ASSERT(mTextureType == TextureType::Unknown);
140 mTextureType = aTextureType;
141 mRecorder = MakeAndAddRef<CanvasDrawEventRecorder>();
142 SharedMemoryBasic::Handle handle;
143 CrossProcessSemaphoreHandle readerSem;
144 CrossProcessSemaphoreHandle writerSem;
145 if (!mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem,
146 MakeUnique<RingBufferWriterServices>(this))) {
147 mRecorder = nullptr;
148 return;
149 }
150
151 if (CanSend()) {
152 Unused << SendInitTranslator(mTextureType, std::move(handle),
153 std::move(readerSem), std::move(writerSem));
154 }
155 }
156
157 MOZ_RELEASE_ASSERT(mTextureType == aTextureType,
158 "We only support one remote TextureType currently.");
159 }
160
ActorDestroy(ActorDestroyReason aWhy)161 void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) {
162 // Explicitly drop our reference to the recorder, because it holds a reference
163 // to us via the ResumeTranslation callback.
164 mRecorder = nullptr;
165 }
166
ResumeTranslation()167 void CanvasChild::ResumeTranslation() {
168 if (CanSend()) {
169 SendResumeTranslation();
170 }
171 }
172
Destroy()173 void CanvasChild::Destroy() {
174 if (CanSend()) {
175 Close();
176 }
177 }
178
OnTextureWriteLock()179 void CanvasChild::OnTextureWriteLock() {
180 // We drop mRecorder in ActorDestroy to break the reference cycle.
181 if (!mRecorder) {
182 return;
183 }
184
185 mHasOutstandingWriteLock = true;
186 mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint();
187 }
188
OnTextureForwarded()189 void CanvasChild::OnTextureForwarded() {
190 // We drop mRecorder in ActorDestroy to break the reference cycle.
191 if (!mRecorder) {
192 return;
193 }
194
195 if (mHasOutstandingWriteLock) {
196 mRecorder->RecordEvent(RecordedCanvasFlush());
197 if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) {
198 gfxWarning() << "Timed out waiting for last write lock to be processed.";
199 }
200
201 mHasOutstandingWriteLock = false;
202 }
203
204 // We hold onto the last transaction's external surfaces until we have waited
205 // for the write locks in this transaction. This means we know that the
206 // surfaces have been picked up in the canvas threads and there is no race
207 // with them being removed from SharedSurfacesParent. Note this releases the
208 // current contents of mLastTransactionExternalSurfaces.
209 mRecorder->TakeExternalSurfaces(mLastTransactionExternalSurfaces);
210 }
211
EnsureBeginTransaction()212 void CanvasChild::EnsureBeginTransaction() {
213 // We drop mRecorder in ActorDestroy to break the reference cycle.
214 if (!mRecorder) {
215 return;
216 }
217
218 if (!mIsInTransaction) {
219 mRecorder->RecordEvent(RecordedCanvasBeginTransaction());
220 mIsInTransaction = true;
221 }
222 }
223
EndTransaction()224 void CanvasChild::EndTransaction() {
225 // We drop mRecorder in ActorDestroy to break the reference cycle.
226 if (!mRecorder) {
227 return;
228 }
229
230 if (mIsInTransaction) {
231 mRecorder->RecordEvent(RecordedCanvasEndTransaction());
232 mIsInTransaction = false;
233 mLastNonEmptyTransaction = TimeStamp::NowLoRes();
234 }
235
236 ++mTransactionsSinceGetDataSurface;
237 }
238
ShouldBeCleanedUp() const239 bool CanvasChild::ShouldBeCleanedUp() const {
240 // Always return true if we've been deactivated.
241 if (Deactivated()) {
242 return true;
243 }
244
245 // We can only be cleaned up if nothing else references our recorder.
246 if (mRecorder && !mRecorder->hasOneRef()) {
247 return false;
248 }
249
250 static const TimeDuration kCleanUpCanvasThreshold =
251 TimeDuration::FromSeconds(10);
252 return TimeStamp::NowLoRes() - mLastNonEmptyTransaction >
253 kCleanUpCanvasThreshold;
254 }
255
CreateDrawTarget(gfx::IntSize aSize,gfx::SurfaceFormat aFormat)256 already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
257 gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
258 // We drop mRecorder in ActorDestroy to break the reference cycle.
259 if (!mRecorder) {
260 return nullptr;
261 }
262
263 RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
264 gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
265 RefPtr<gfx::DrawTarget> dt = MakeAndAddRef<gfx::DrawTargetRecording>(
266 mRecorder, dummyDt, gfx::IntRect(gfx::IntPoint(0, 0), aSize));
267 return dt.forget();
268 }
269
RecordEvent(const gfx::RecordedEvent & aEvent)270 void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) {
271 // We drop mRecorder in ActorDestroy to break the reference cycle.
272 if (!mRecorder) {
273 return;
274 }
275
276 mRecorder->RecordEvent(aEvent);
277 }
278
GetDataSurface(const gfx::SourceSurface * aSurface)279 already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
280 const gfx::SourceSurface* aSurface) {
281 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
282 MOZ_ASSERT(aSurface);
283
284 // We drop mRecorder in ActorDestroy to break the reference cycle.
285 if (!mRecorder) {
286 return nullptr;
287 }
288
289 mTransactionsSinceGetDataSurface = 0;
290 EnsureBeginTransaction();
291 mRecorder->RecordEvent(RecordedPrepareDataForSurface(aSurface));
292 uint32_t checkpoint = mRecorder->CreateCheckpoint();
293
294 gfx::IntSize ssSize = aSurface->GetSize();
295 gfx::SurfaceFormat ssFormat = aSurface->GetFormat();
296 size_t dataFormatWidth = ssSize.width * BytesPerPixel(ssFormat);
297 RefPtr<gfx::DataSourceSurface> dataSurface =
298 gfx::Factory::CreateDataSourceSurfaceWithStride(ssSize, ssFormat,
299 dataFormatWidth);
300 if (!dataSurface) {
301 gfxWarning() << "Failed to create DataSourceSurface.";
302 return nullptr;
303 }
304 gfx::DataSourceSurface::ScopedMap map(dataSurface,
305 gfx::DataSourceSurface::READ_WRITE);
306 char* dest = reinterpret_cast<char*>(map.GetData());
307 if (!mRecorder->WaitForCheckpoint(checkpoint)) {
308 gfxWarning() << "Timed out preparing data for DataSourceSurface.";
309 return dataSurface.forget();
310 }
311
312 mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface));
313 mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth);
314
315 return dataSurface.forget();
316 }
317
WrapSurface(const RefPtr<gfx::SourceSurface> & aSurface)318 already_AddRefed<gfx::SourceSurface> CanvasChild::WrapSurface(
319 const RefPtr<gfx::SourceSurface>& aSurface) {
320 MOZ_ASSERT(aSurface);
321 // We drop mRecorder in ActorDestroy to break the reference cycle.
322 if (!mRecorder) {
323 return nullptr;
324 }
325
326 return MakeAndAddRef<SourceSurfaceCanvasRecording>(aSurface, this, mRecorder);
327 }
328
329 } // namespace layers
330 } // namespace mozilla
331