Bug 1830741 - Add tests for mach try perf comparators. r=perftest-reviewers,Alexandru...
[gecko.git] / gfx / layers / PersistentBufferProvider.cpp
blob4ce356bea9913d7dc58c405790c61243a5a3a442
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/. */
7 #include "PersistentBufferProvider.h"
9 #include "mozilla/layers/TextureClient.h"
10 #include "mozilla/layers/TextureForwarder.h"
11 #include "mozilla/gfx/gfxVars.h"
12 #include "mozilla/gfx/DrawTargetWebgl.h"
13 #include "mozilla/gfx/Logging.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/StaticPrefs_layers.h"
16 #include "pratom.h"
17 #include "gfxPlatform.h"
19 namespace mozilla {
21 using namespace gfx;
23 namespace layers {
25 PersistentBufferProviderBasic::PersistentBufferProviderBasic(DrawTarget* aDt)
26 : mDrawTarget(aDt) {
27 MOZ_COUNT_CTOR(PersistentBufferProviderBasic);
30 PersistentBufferProviderBasic::~PersistentBufferProviderBasic() {
31 MOZ_COUNT_DTOR(PersistentBufferProviderBasic);
32 Destroy();
35 already_AddRefed<gfx::DrawTarget>
36 PersistentBufferProviderBasic::BorrowDrawTarget(
37 const gfx::IntRect& aPersistedRect) {
38 MOZ_ASSERT(!mSnapshot);
39 RefPtr<gfx::DrawTarget> dt(mDrawTarget);
40 return dt.forget();
43 bool PersistentBufferProviderBasic::ReturnDrawTarget(
44 already_AddRefed<gfx::DrawTarget> aDT) {
45 RefPtr<gfx::DrawTarget> dt(aDT);
46 MOZ_ASSERT(mDrawTarget == dt);
47 if (dt) {
48 // Since SkiaGL default to storing drawing command until flush
49 // we have to flush it before present.
50 dt->Flush();
52 return true;
55 already_AddRefed<gfx::SourceSurface>
56 PersistentBufferProviderBasic::BorrowSnapshot(gfx::DrawTarget* aTarget) {
57 mSnapshot = mDrawTarget->Snapshot();
58 RefPtr<SourceSurface> snapshot = mSnapshot;
59 return snapshot.forget();
62 void PersistentBufferProviderBasic::ReturnSnapshot(
63 already_AddRefed<gfx::SourceSurface> aSnapshot) {
64 RefPtr<SourceSurface> snapshot = aSnapshot;
65 MOZ_ASSERT(!snapshot || snapshot == mSnapshot);
66 mSnapshot = nullptr;
69 void PersistentBufferProviderBasic::Destroy() {
70 mSnapshot = nullptr;
71 mDrawTarget = nullptr;
74 // static
75 already_AddRefed<PersistentBufferProviderBasic>
76 PersistentBufferProviderBasic::Create(gfx::IntSize aSize,
77 gfx::SurfaceFormat aFormat,
78 gfx::BackendType aBackend) {
79 RefPtr<DrawTarget> dt =
80 gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(aBackend, aSize,
81 aFormat);
83 if (dt) {
84 // This is simply to ensure the DrawTarget gets initialized, and will detect
85 // a device reset, even if we're on the main thread.
86 dt->ClearRect(Rect(0, 0, 0, 0));
89 if (!dt || !dt->IsValid()) {
90 return nullptr;
93 RefPtr<PersistentBufferProviderBasic> provider =
94 new PersistentBufferProviderBasic(dt);
96 return provider.forget();
99 PersistentBufferProviderAccelerated::PersistentBufferProviderAccelerated(
100 DrawTarget* aDt)
101 : PersistentBufferProviderBasic(aDt) {
102 MOZ_COUNT_CTOR(PersistentBufferProviderAccelerated);
103 MOZ_ASSERT(aDt->GetBackendType() == BackendType::WEBGL);
106 PersistentBufferProviderAccelerated::~PersistentBufferProviderAccelerated() {
107 MOZ_COUNT_DTOR(PersistentBufferProviderAccelerated);
110 inline gfx::DrawTargetWebgl*
111 PersistentBufferProviderAccelerated::GetDrawTargetWebgl() const {
112 return static_cast<gfx::DrawTargetWebgl*>(mDrawTarget.get());
115 Maybe<layers::SurfaceDescriptor>
116 PersistentBufferProviderAccelerated::GetFrontBuffer() {
117 return GetDrawTargetWebgl()->GetFrontBuffer();
120 already_AddRefed<gfx::DrawTarget>
121 PersistentBufferProviderAccelerated::BorrowDrawTarget(
122 const gfx::IntRect& aPersistedRect) {
123 GetDrawTargetWebgl()->BeginFrame(aPersistedRect);
124 return PersistentBufferProviderBasic::BorrowDrawTarget(aPersistedRect);
127 bool PersistentBufferProviderAccelerated::ReturnDrawTarget(
128 already_AddRefed<gfx::DrawTarget> aDT) {
129 bool result = PersistentBufferProviderBasic::ReturnDrawTarget(std::move(aDT));
130 GetDrawTargetWebgl()->EndFrame();
131 return result;
134 already_AddRefed<gfx::SourceSurface>
135 PersistentBufferProviderAccelerated::BorrowSnapshot(gfx::DrawTarget* aTarget) {
136 mSnapshot = GetDrawTargetWebgl()->GetOptimizedSnapshot(aTarget);
137 return do_AddRef(mSnapshot);
140 bool PersistentBufferProviderAccelerated::RequiresRefresh() const {
141 return GetDrawTargetWebgl()->RequiresRefresh();
144 void PersistentBufferProviderAccelerated::OnMemoryPressure() {
145 GetDrawTargetWebgl()->OnMemoryPressure();
148 static already_AddRefed<TextureClient> CreateTexture(
149 KnowsCompositor* aKnowsCompositor, gfx::SurfaceFormat aFormat,
150 gfx::IntSize aSize, bool aWillReadFrequently) {
151 TextureAllocationFlags flags = ALLOC_DEFAULT;
152 if (aWillReadFrequently) {
153 flags = TextureAllocationFlags(flags | ALLOC_DO_NOT_ACCELERATE);
155 return TextureClient::CreateForDrawing(
156 aKnowsCompositor, aFormat, aSize, BackendSelector::Canvas,
157 TextureFlags::DEFAULT | TextureFlags::NON_BLOCKING_READ_LOCK, flags);
160 // static
161 already_AddRefed<PersistentBufferProviderShared>
162 PersistentBufferProviderShared::Create(gfx::IntSize aSize,
163 gfx::SurfaceFormat aFormat,
164 KnowsCompositor* aKnowsCompositor,
165 bool aWillReadFrequently) {
166 if (!aKnowsCompositor || !aKnowsCompositor->GetTextureForwarder() ||
167 !aKnowsCompositor->GetTextureForwarder()->IPCOpen()) {
168 return nullptr;
171 if (!StaticPrefs::layers_shared_buffer_provider_enabled()) {
172 return nullptr;
175 #ifdef XP_WIN
176 // Bug 1285271 - Disable shared buffer provider on Windows with D2D due to
177 // instability, unless we are remoting the canvas drawing to the GPU process.
178 if (gfxPlatform::GetPlatform()->GetPreferredCanvasBackend() ==
179 BackendType::DIRECT2D1_1 &&
180 !TextureData::IsRemote(aKnowsCompositor, BackendSelector::Canvas)) {
181 return nullptr;
183 #endif
185 RefPtr<TextureClient> texture =
186 CreateTexture(aKnowsCompositor, aFormat, aSize, aWillReadFrequently);
187 if (!texture) {
188 return nullptr;
191 RefPtr<PersistentBufferProviderShared> provider =
192 new PersistentBufferProviderShared(aSize, aFormat, aKnowsCompositor,
193 texture, aWillReadFrequently);
194 return provider.forget();
197 PersistentBufferProviderShared::PersistentBufferProviderShared(
198 gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
199 KnowsCompositor* aKnowsCompositor, RefPtr<TextureClient>& aTexture,
200 bool aWillReadFrequently)
202 : mSize(aSize),
203 mFormat(aFormat),
204 mKnowsCompositor(aKnowsCompositor),
205 mFront(Nothing()),
206 mWillReadFrequently(aWillReadFrequently) {
207 MOZ_ASSERT(aKnowsCompositor);
208 if (mTextures.append(aTexture)) {
209 mBack = Some<uint32_t>(0);
212 // XXX KnowsCompositor could be used for mMaxAllowedTextures
213 if (gfxVars::UseWebRenderTripleBufferingWin()) {
214 ++mMaxAllowedTextures;
217 MOZ_COUNT_CTOR(PersistentBufferProviderShared);
220 PersistentBufferProviderShared::~PersistentBufferProviderShared() {
221 MOZ_COUNT_DTOR(PersistentBufferProviderShared);
223 if (IsActivityTracked()) {
224 mKnowsCompositor->GetActiveResourceTracker()->RemoveObject(this);
227 Destroy();
230 bool PersistentBufferProviderShared::SetKnowsCompositor(
231 KnowsCompositor* aKnowsCompositor, bool& aOutLostFrontTexture) {
232 MOZ_ASSERT(aKnowsCompositor);
233 MOZ_ASSERT(!aOutLostFrontTexture);
234 if (!aKnowsCompositor) {
235 return false;
238 if (mKnowsCompositor == aKnowsCompositor) {
239 // The forwarder should not change most of the time.
240 return true;
243 if (IsActivityTracked()) {
244 mKnowsCompositor->GetActiveResourceTracker()->RemoveObject(this);
247 if (mKnowsCompositor->GetTextureForwarder() !=
248 aKnowsCompositor->GetTextureForwarder() ||
249 mKnowsCompositor->GetCompositorBackendType() !=
250 aKnowsCompositor->GetCompositorBackendType()) {
251 // We are going to be used with an different and/or incompatible forwarder.
252 // This should be extremely rare. We have to copy the front buffer into a
253 // texture that is compatible with the new forwarder.
255 // Grab the current front buffer.
256 RefPtr<TextureClient> prevTexture = GetTexture(mFront);
258 // Get rid of everything else
259 Destroy();
261 if (prevTexture && !prevTexture->IsValid()) {
262 aOutLostFrontTexture = true;
263 } else if (prevTexture && prevTexture->IsValid()) {
264 RefPtr<TextureClient> newTexture =
265 CreateTexture(aKnowsCompositor, mFormat, mSize, mWillReadFrequently);
267 MOZ_ASSERT(newTexture);
268 if (!newTexture) {
269 return false;
272 // If we early-return in one of the following branches, we will
273 // leave the buffer provider in an empty state, since we called
274 // Destroy. Not ideal but at least we won't try to use it with a
275 // an incompatible ipc channel.
277 if (!newTexture->Lock(OpenMode::OPEN_WRITE)) {
278 return false;
281 if (!prevTexture->Lock(OpenMode::OPEN_READ)) {
282 newTexture->Unlock();
283 return false;
286 bool success =
287 prevTexture->CopyToTextureClient(newTexture, nullptr, nullptr);
289 prevTexture->Unlock();
290 newTexture->Unlock();
292 if (!success) {
293 return false;
296 if (!mTextures.append(newTexture)) {
297 return false;
299 mFront = Some<uint32_t>(mTextures.length() - 1);
300 mBack = mFront;
304 mKnowsCompositor = aKnowsCompositor;
306 return true;
309 TextureClient* PersistentBufferProviderShared::GetTexture(
310 const Maybe<uint32_t>& aIndex) {
311 if (aIndex.isNothing() || !CheckIndex(aIndex.value())) {
312 return nullptr;
314 return mTextures[aIndex.value()];
317 already_AddRefed<gfx::DrawTarget>
318 PersistentBufferProviderShared::BorrowDrawTarget(
319 const gfx::IntRect& aPersistedRect) {
320 if (!mKnowsCompositor->GetTextureForwarder() ||
321 !mKnowsCompositor->GetTextureForwarder()->IPCOpen()) {
322 return nullptr;
325 MOZ_ASSERT(!mSnapshot);
327 if (IsActivityTracked()) {
328 mKnowsCompositor->GetActiveResourceTracker()->MarkUsed(this);
329 } else {
330 mKnowsCompositor->GetActiveResourceTracker()->AddObject(this);
333 if (mDrawTarget) {
334 RefPtr<gfx::DrawTarget> dt(mDrawTarget);
335 return dt.forget();
338 auto previousBackBuffer = mBack;
340 TextureClient* tex = GetTexture(mBack);
342 // First try to reuse the current back buffer. If we can do that it means
343 // we can skip copying its content to the new back buffer.
344 if (tex && tex->IsReadLocked()) {
345 // The back buffer is currently used by the compositor, we can't draw
346 // into it.
347 tex = nullptr;
350 if (!tex) {
351 // Try to grab an already allocated texture if any is available.
352 for (uint32_t i = 0; i < mTextures.length(); ++i) {
353 if (!mTextures[i]->IsReadLocked()) {
354 mBack = Some(i);
355 tex = mTextures[i];
356 break;
361 if (!tex) {
362 // We have to allocate a new texture.
363 if (mTextures.length() >= mMaxAllowedTextures) {
364 // We should never need to buffer that many textures, something's wrong.
365 // In theory we throttle the main thread when the compositor can't keep
366 // up, so we shoud never get in a situation where we sent 4 textures to
367 // the compositor and the latter has not released any of them. In
368 // practice, though, the throttling mechanism appears to have some issues,
369 // especially when switching between layer managers (during tab-switch).
370 // To make sure we don't get too far ahead of the compositor, we send a
371 // sync ping to the compositor thread...
372 mKnowsCompositor->SyncWithCompositor();
373 // ...and try again.
374 for (uint32_t i = 0; i < mTextures.length(); ++i) {
375 if (!mTextures[i]->IsReadLocked()) {
376 gfxCriticalNote << "Managed to allocate after flush.";
377 mBack = Some(i);
378 tex = mTextures[i];
379 break;
383 if (!tex) {
384 gfxCriticalNote << "Unexpected BufferProvider over-production.";
385 // It would be pretty bad to keep piling textures up at this point so we
386 // call NotifyInactive to remove some of our textures.
387 NotifyInactive();
388 // Give up now. The caller can fall-back to a non-shared buffer
389 // provider.
390 return nullptr;
394 RefPtr<TextureClient> newTexture =
395 CreateTexture(mKnowsCompositor, mFormat, mSize, mWillReadFrequently);
397 MOZ_ASSERT(newTexture);
398 if (newTexture) {
399 if (mTextures.append(newTexture)) {
400 tex = newTexture;
401 mBack = Some<uint32_t>(mTextures.length() - 1);
406 if (!tex) {
407 return nullptr;
410 if (mPermanentBackBuffer) {
411 // If we have a permanent back buffer lock the selected one and switch to
412 // the permanent one before borrowing the DrawTarget. We will copy back into
413 // the selected one when ReturnDrawTarget is called, before we make it the
414 // new front buffer.
415 if (!tex->Lock(OpenMode::OPEN_WRITE)) {
416 return nullptr;
418 tex = mPermanentBackBuffer;
419 } else {
420 // Copy from the previous back buffer if required.
421 Maybe<TextureClientAutoLock> autoReadLock;
422 TextureClient* previous = nullptr;
423 if (mBack != previousBackBuffer && !aPersistedRect.IsEmpty()) {
424 if (tex->HasSynchronization()) {
425 // We are about to read lock a texture that is in use by the compositor
426 // and has synchronization. To prevent possible future contention we
427 // switch to using a permanent back buffer.
428 mPermanentBackBuffer = CreateTexture(mKnowsCompositor, mFormat, mSize,
429 mWillReadFrequently);
430 if (!mPermanentBackBuffer) {
431 return nullptr;
433 if (!tex->Lock(OpenMode::OPEN_WRITE)) {
434 return nullptr;
436 tex = mPermanentBackBuffer;
439 previous = GetTexture(previousBackBuffer);
440 if (previous) {
441 autoReadLock.emplace(previous, OpenMode::OPEN_READ);
445 if (!tex->Lock(OpenMode::OPEN_READ_WRITE)) {
446 return nullptr;
449 if (autoReadLock.isSome() && autoReadLock->Succeeded() && previous) {
450 DebugOnly<bool> success =
451 previous->CopyToTextureClient(tex, &aPersistedRect, nullptr);
452 MOZ_ASSERT(success);
456 mDrawTarget = tex->BorrowDrawTarget();
457 if (mDrawTarget) {
458 // This is simply to ensure the DrawTarget gets initialized, and will detect
459 // a device reset, even if we're on the main thread.
460 mDrawTarget->ClearRect(Rect(0, 0, 0, 0));
462 if (!mDrawTarget->IsValid()) {
463 mDrawTarget = nullptr;
467 RefPtr<gfx::DrawTarget> dt(mDrawTarget);
468 return dt.forget();
471 bool PersistentBufferProviderShared::ReturnDrawTarget(
472 already_AddRefed<gfx::DrawTarget> aDT) {
473 RefPtr<gfx::DrawTarget> dt(aDT);
474 MOZ_ASSERT(mDrawTarget == dt);
475 // Can't change the current front buffer while its snapshot is borrowed!
476 MOZ_ASSERT(!mSnapshot);
478 TextureClient* back = GetTexture(mBack);
479 MOZ_ASSERT(back);
481 mDrawTarget = nullptr;
482 dt = nullptr;
484 // If we have a permanent back buffer we have actually been drawing to that,
485 // so now we must copy to the shared one.
486 if (mPermanentBackBuffer && back) {
487 DebugOnly<bool> success =
488 mPermanentBackBuffer->CopyToTextureClient(back, nullptr, nullptr);
489 MOZ_ASSERT(success);
491 // Let our permanent back buffer know that we have finished drawing.
492 mPermanentBackBuffer->EndDraw();
495 if (back) {
496 back->Unlock();
497 mFront = mBack;
500 return !!back;
503 TextureClient* PersistentBufferProviderShared::GetTextureClient() {
504 // Can't access the front buffer while drawing.
505 MOZ_ASSERT(!mDrawTarget);
506 TextureClient* texture = GetTexture(mFront);
507 if (!texture) {
508 gfxCriticalNote
509 << "PersistentBufferProviderShared: front buffer unavailable";
510 return nullptr;
513 // Sometimes, for example on tab switch, we re-forward our texture. So if we
514 // are and it is still read locked, then borrow and return our DrawTarget to
515 // force it to be copied to a texture that we will safely read lock.
516 if (texture->IsReadLocked()) {
517 RefPtr<DrawTarget> dt =
518 BorrowDrawTarget(IntRect(0, 0, mSize.width, mSize.height));
520 // If we failed to borrow a DrawTarget then all our textures must be read
521 // locked or we failed to create one, so we'll just return the current front
522 // buffer even though that might lead to contention.
523 if (dt) {
524 ReturnDrawTarget(dt.forget());
525 texture = GetTexture(mFront);
526 if (!texture) {
527 gfxCriticalNote
528 << "PersistentBufferProviderShared: front buffer unavailable";
529 return nullptr;
532 } else {
533 // If it isn't read locked then make sure it is set as updated, so that we
534 // will always read lock even if we've forwarded the texture before.
535 texture->SetUpdated();
538 return texture;
541 already_AddRefed<gfx::SourceSurface>
542 PersistentBufferProviderShared::BorrowSnapshot(gfx::DrawTarget* aTarget) {
543 // If we have a permanent back buffer we can always use that to snapshot.
544 if (mPermanentBackBuffer) {
545 mSnapshot = mPermanentBackBuffer->BorrowSnapshot();
546 return do_AddRef(mSnapshot);
549 if (mDrawTarget) {
550 auto back = GetTexture(mBack);
551 MOZ_ASSERT(back && back->IsLocked());
552 mSnapshot = back->BorrowSnapshot();
553 return do_AddRef(mSnapshot);
556 auto front = GetTexture(mFront);
557 if (!front || front->IsLocked()) {
558 MOZ_ASSERT(false);
559 return nullptr;
562 if (front->IsReadLocked() && front->HasSynchronization()) {
563 // We are about to read lock a texture that is in use by the compositor and
564 // has synchronization. To prevent possible future contention we switch to
565 // using a permanent back buffer.
566 mPermanentBackBuffer =
567 CreateTexture(mKnowsCompositor, mFormat, mSize, mWillReadFrequently);
568 if (!mPermanentBackBuffer ||
569 !mPermanentBackBuffer->Lock(OpenMode::OPEN_READ_WRITE)) {
570 return nullptr;
573 if (!front->Lock(OpenMode::OPEN_READ)) {
574 return nullptr;
577 DebugOnly<bool> success =
578 front->CopyToTextureClient(mPermanentBackBuffer, nullptr, nullptr);
579 MOZ_ASSERT(success);
580 front->Unlock();
581 mSnapshot = mPermanentBackBuffer->BorrowSnapshot();
582 return do_AddRef(mSnapshot);
585 if (!front->Lock(OpenMode::OPEN_READ)) {
586 return nullptr;
589 mSnapshot = front->BorrowSnapshot();
591 return do_AddRef(mSnapshot);
594 void PersistentBufferProviderShared::ReturnSnapshot(
595 already_AddRefed<gfx::SourceSurface> aSnapshot) {
596 RefPtr<SourceSurface> snapshot = aSnapshot;
597 MOZ_ASSERT(!snapshot || snapshot == mSnapshot);
599 mSnapshot = nullptr;
600 snapshot = nullptr;
602 if (mDrawTarget || mPermanentBackBuffer) {
603 return;
606 auto front = GetTexture(mFront);
607 if (front) {
608 front->Unlock();
612 void PersistentBufferProviderShared::NotifyInactive() {
613 ClearCachedResources();
616 void PersistentBufferProviderShared::ClearCachedResources() {
617 RefPtr<TextureClient> front = GetTexture(mFront);
618 RefPtr<TextureClient> back = GetTexture(mBack);
620 // Clear all textures (except the front and back ones that we just kept).
621 mTextures.clear();
622 mPermanentBackBuffer = nullptr;
624 if (back) {
625 if (mTextures.append(back)) {
626 mBack = Some<uint32_t>(0);
628 if (front == back) {
629 mFront = mBack;
633 if (front && front != back) {
634 if (mTextures.append(front)) {
635 mFront = Some<uint32_t>(mTextures.length() - 1);
640 void PersistentBufferProviderShared::Destroy() {
641 mSnapshot = nullptr;
642 mDrawTarget = nullptr;
644 if (mPermanentBackBuffer) {
645 mPermanentBackBuffer->Unlock();
646 mPermanentBackBuffer = nullptr;
649 for (auto& texture : mTextures) {
650 if (texture && texture->IsLocked()) {
651 MOZ_ASSERT(false);
652 texture->Unlock();
656 mTextures.clear();
659 bool PersistentBufferProviderShared::IsAccelerated() const {
660 #ifdef XP_WIN
661 // Detect if we're using D2D canvas.
662 if (mWillReadFrequently || mTextures.empty() || !mTextures[0]) {
663 return false;
665 auto* data = mTextures[0]->GetInternalData();
666 if (data && data->AsD3D11TextureData()) {
667 return true;
669 #endif
670 return false;
673 } // namespace layers
674 } // namespace mozilla