Bug 1383916. Prep and flush draw targets on the paint thread with OMTP. r=dvander

This commit is contained in:
Mason Chang 2017-07-31 20:56:01 -07:00
parent 91cfa95229
commit 788361e0d9
7 changed files with 223 additions and 49 deletions

View File

@ -113,12 +113,33 @@ PaintThread::IsOnPaintThread()
void
PaintThread::PaintContentsAsync(CompositorBridgeChild* aBridge,
gfx::DrawTargetCapture* aCapture,
gfx::DrawTarget* aTarget)
CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback)
{
MOZ_ASSERT(IsOnPaintThread());
MOZ_ASSERT(aCapture);
MOZ_ASSERT(aState);
DrawTarget* target = aState->mTarget;
Matrix oldTransform = target->GetTransform();
target->SetTransform(aState->mTargetTransform);
if (!aCallback(aState)) {
return;
}
// Draw all the things into the actual dest target.
aTarget->DrawCapturedDT(aCapture, Matrix());
target->DrawCapturedDT(aCapture, Matrix());
target->SetTransform(oldTransform);
// Textureclient forces a flush once we "end paint", so
// users of this texture expect all the drawing to be complete.
// Force a flush now.
// TODO: This might be a performance bottleneck because
// main thread painting only does one flush at the end of all paints
// whereas we force a flush after each draw target paint.
target->Flush();
if (aBridge) {
aBridge->NotifyFinishedAsyncPaint();
@ -127,9 +148,12 @@ PaintThread::PaintContentsAsync(CompositorBridgeChild* aBridge,
void
PaintThread::PaintContents(DrawTargetCapture* aCapture,
DrawTarget* aTarget)
CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCapture);
MOZ_ASSERT(aState);
// If painting asynchronously, we need to acquire the compositor bridge which
// owns the underlying MessageChannel. Otherwise we leave it null and use
@ -139,14 +163,17 @@ PaintThread::PaintContents(DrawTargetCapture* aCapture,
cbc = CompositorBridgeChild::Get();
cbc->NotifyBeginAsyncPaint();
}
RefPtr<DrawTargetCapture> capture(aCapture);
RefPtr<DrawTarget> target(aTarget);
RefPtr<CapturedPaintState> state(aState);
RefPtr<PaintThread> self = this;
RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PaintContents",
[self, cbc, capture, target]() -> void
[self, cbc, capture, state, aCallback]() -> void
{
self->PaintContentsAsync(cbc, capture, target);
self->PaintContentsAsync(cbc, capture,
state,
aCallback);
});
if (cbc) {

View File

@ -8,6 +8,7 @@
#define MOZILLA_LAYERS_PAINTTHREAD_H
#include "base/platform_thread.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/UniquePtr.h"
#include "nsThreadUtils.h"
@ -20,6 +21,38 @@ class DrawTargetCapture;
namespace layers {
// Holds the key parts from a RotatedBuffer::PaintState
// required to draw the captured paint state
class CapturedPaintState {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedPaintState)
public:
CapturedPaintState(nsIntRegion& aRegionToDraw,
gfx::DrawTarget* aTarget,
gfx::DrawTarget* aTargetOnWhite,
gfx::Matrix aTargetTransform,
SurfaceMode aSurfaceMode,
gfxContentType aContentType)
: mRegionToDraw(aRegionToDraw)
, mTarget(aTarget)
, mTargetOnWhite(aTargetOnWhite)
, mTargetTransform(aTargetTransform)
, mSurfaceMode(aSurfaceMode)
, mContentType(aContentType)
{}
nsIntRegion mRegionToDraw;
RefPtr<gfx::DrawTarget> mTarget;
RefPtr<gfx::DrawTarget> mTargetOnWhite;
gfx::Matrix mTargetTransform;
SurfaceMode mSurfaceMode;
gfxContentType mContentType;
protected:
virtual ~CapturedPaintState() {}
};
typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState* aPaintState);
class CompositorBridgeChild;
class PaintThread final
@ -31,7 +64,8 @@ public:
static void Shutdown();
static PaintThread* Get();
void PaintContents(gfx::DrawTargetCapture* aCapture,
gfx::DrawTarget* aTarget);
CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback);
// Sync Runnables need threads to be ref counted,
// But this thread lives through the whole process.
@ -49,7 +83,8 @@ private:
void InitOnPaintThread();
void PaintContentsAsync(CompositorBridgeChild* aBridge,
gfx::DrawTargetCapture* aCapture,
gfx::DrawTarget* aTarget);
CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback);
static StaticAutoPtr<PaintThread> sSingleton;
static StaticRefPtr<nsIThread> sThread;

View File

@ -27,6 +27,7 @@
#include "mozilla/gfx/Point.h" // for IntSize
#include "gfx2DGlue.h"
#include "nsLayoutUtils.h" // for invalidation debugging
#include "PaintThread.h"
namespace mozilla {
@ -730,6 +731,77 @@ RotatedContentBuffer::BeginPaint(PaintedLayer* aLayer,
return result;
}
DrawTarget*
RotatedContentBuffer::BorrowDrawTargetForRecording(PaintState& aPaintState,
DrawIterator* aIter /* = nullptr */)
{
if (aPaintState.mMode == SurfaceMode::SURFACE_NONE) {
return nullptr;
}
DrawTarget* result = BorrowDrawTargetForQuadrantUpdate(aPaintState.mRegionToDraw.GetBounds(),
BUFFER_BOTH, aIter);
if (!result) {
return nullptr;
}
ExpandDrawRegion(aPaintState, aIter, result->GetBackendType());
return result;
}
/*static */ bool
RotatedContentBuffer::PrepareDrawTargetForPainting(CapturedPaintState* aState)
{
RefPtr<DrawTarget> target = aState->mTarget;
RefPtr<DrawTarget> whiteTarget = aState->mTargetOnWhite;
if (aState->mSurfaceMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
if (!target || !target->IsValid() ||
!aState->mTargetOnWhite || !aState->mTargetOnWhite->IsValid()) {
// This can happen in release builds if allocating one of the two buffers
// failed. This in turn can happen if unreasonably large textures are
// requested.
return false;
}
for (auto iter = aState->mRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
const IntRect& rect = iter.Get();
target->FillRect(Rect(rect.x, rect.y, rect.width, rect.height),
ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
whiteTarget->FillRect(Rect(rect.x, rect.y, rect.width, rect.height),
ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
}
} else if (aState->mContentType == gfxContentType::COLOR_ALPHA &&
target->IsValid()) {
// HaveBuffer() => we have an existing buffer that we must clear
for (auto iter = aState->mRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
const IntRect& rect = iter.Get();
target->ClearRect(Rect(rect.x, rect.y, rect.width, rect.height));
}
}
return true;
}
void
RotatedContentBuffer::ExpandDrawRegion(PaintState& aPaintState,
DrawIterator* aIter,
BackendType aBackendType)
{
nsIntRegion* drawPtr = &aPaintState.mRegionToDraw;
if (aIter) {
// The iterators draw region currently only contains the bounds of the region,
// this makes it the precise region.
aIter->mDrawRegion.And(aIter->mDrawRegion, aPaintState.mRegionToDraw);
drawPtr = &aIter->mDrawRegion;
}
if (aBackendType == BackendType::DIRECT2D ||
aBackendType == BackendType::DIRECT2D1_1) {
// Simplify the draw region to avoid hitting expensive drawing paths
// for complex regions.
drawPtr->SimplifyOutwardByArea(100 * 100);
}
}
DrawTarget*
RotatedContentBuffer::BorrowDrawTargetForPainting(PaintState& aPaintState,
DrawIterator* aIter /* = nullptr */)
@ -744,41 +816,18 @@ RotatedContentBuffer::BorrowDrawTargetForPainting(PaintState& aPaintState,
return nullptr;
}
nsIntRegion* drawPtr = &aPaintState.mRegionToDraw;
if (aIter) {
// The iterators draw region currently only contains the bounds of the region,
// this makes it the precise region.
aIter->mDrawRegion.And(aIter->mDrawRegion, aPaintState.mRegionToDraw);
drawPtr = &aIter->mDrawRegion;
}
if (result->GetBackendType() == BackendType::DIRECT2D ||
result->GetBackendType() == BackendType::DIRECT2D1_1) {
// Simplify the draw region to avoid hitting expensive drawing paths
// for complex regions.
drawPtr->SimplifyOutwardByArea(100 * 100);
}
ExpandDrawRegion(aPaintState, aIter, result->GetBackendType());
// Can't stack allocate refcounted objects.
RefPtr<CapturedPaintState> capturedPaintState =
MakeAndAddRef<CapturedPaintState>(aPaintState.mRegionToDraw,
mDTBuffer,
mDTBufferOnWhite,
Matrix(),
aPaintState.mMode,
aPaintState.mContentType);
if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
if (!mDTBuffer || !mDTBuffer->IsValid() ||
!mDTBufferOnWhite || !mDTBufferOnWhite->IsValid()) {
// This can happen in release builds if allocating one of the two buffers
// failed. This in turn can happen if unreasonably large textures are
// requested.
return nullptr;
}
for (auto iter = drawPtr->RectIter(); !iter.Done(); iter.Next()) {
const IntRect& rect = iter.Get();
mDTBuffer->FillRect(Rect(rect.x, rect.y, rect.width, rect.height),
ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
mDTBufferOnWhite->FillRect(Rect(rect.x, rect.y, rect.width, rect.height),
ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
}
} else if (aPaintState.mContentType == gfxContentType::COLOR_ALPHA && HaveBuffer()) {
// HaveBuffer() => we have an existing buffer that we must clear
for (auto iter = drawPtr->RectIter(); !iter.Done(); iter.Next()) {
const IntRect& rect = iter.Get();
result->ClearRect(Rect(rect.x, rect.y, rect.width, rect.height));
}
if (!RotatedContentBuffer::PrepareDrawTargetForPainting(capturedPaintState)) {
return nullptr;
}
return result;

View File

@ -22,6 +22,10 @@
namespace mozilla {
namespace layers {
class CapturedPaintState;
typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
class TextureClient;
class PaintedLayer;
@ -293,6 +297,14 @@ public:
gfx::DrawTarget* BorrowDrawTargetForPainting(PaintState& aPaintState,
DrawIterator* aIter = nullptr);
gfx::DrawTarget* BorrowDrawTargetForRecording(PaintState& aPaintState,
DrawIterator* aIter = nullptr);
void ExpandDrawRegion(PaintState& aPaintState,
DrawIterator* aIter,
gfx::BackendType aBackendType);
static bool PrepareDrawTargetForPainting(CapturedPaintState*);
enum {
BUFFER_COMPONENT_ALPHA = 0x02 // Dual buffers should be created for drawing with
// component alpha.

View File

@ -25,6 +25,7 @@
#include "nsRect.h" // for mozilla::gfx::IntRect
#include "PaintThread.h"
#include "ReadbackProcessor.h"
#include "RotatedBuffer.h"
namespace mozilla {
namespace layers {
@ -188,6 +189,29 @@ ClientPaintedLayer::PaintThebes(nsTArray<ReadbackProcessor::Update>* aReadbackUp
}
}
/***
* If we can, let's paint this ClientPaintedLayer's contents off the main thread.
* The essential idea is that we ask the ContentClient for a DrawTarget and record
* the moz2d commands. On the Paint Thread, we replay those commands to the
* destination draw target. There are a couple of lifetime issues here though:
*
* 1) TextureClient owns the underlying buffer and DrawTarget. Because of this
* we have to keep the TextureClient and DrawTarget alive but trick the
* TextureClient into thinking it's already returned the DrawTarget
* since we iterate through different Rects to get DrawTargets*. If
* the TextureClient goes away, the DrawTarget and thus buffer can too.
* 2) When ContentClient::EndPaint happens, it flushes the DrawTarget. We have
* to Reflush on the Paint Thread
* 3) DrawTarget API is NOT thread safe. We get around this by recording
* on the main thread and painting on the paint thread. Logically,
* ClientLayerManager will force a flushed paint and block the main thread
* if we have another transaction. Thus we have a gap between when the main
* thread records, the paint thread paints, and we block the main thread
* from trying to paint again. The underlying API however is NOT thread safe.
* 4) We have both "sync" and "async" OMTP. Sync OMTP means we paint on the main thread
* but block the main thread while the paint thread paints. Async OMTP doesn't block
* the main thread. Sync OMTP is only meant to be used as a debugging tool.
*/
bool
ClientPaintedLayer::PaintOffMainThread()
{
@ -202,7 +226,8 @@ ClientPaintedLayer::PaintOffMainThread()
bool didUpdate = false;
RotatedContentBuffer::DrawIterator iter;
while (DrawTarget* target = mContentClient->BorrowDrawTargetForPainting(state, &iter)) {
// Debug Protip: Change to BorrowDrawTargetForPainting if using sync OMTP.
while (DrawTarget* target = mContentClient->BorrowDrawTargetForRecording(state, &iter)) {
if (!target || !target->IsValid()) {
if (target) {
mContentClient->ReturnDrawTargetToBuffer(target);
@ -210,14 +235,15 @@ ClientPaintedLayer::PaintOffMainThread()
continue;
}
// We don't clear the rect here like WRPaintedBlobLayers do
// because ContentClient already clears the surface for us during BeginPaint.
RefPtr<DrawTargetCapture> captureDT =
Factory::CreateCaptureDrawTarget(target->GetBackendType(),
target->GetSize(),
target->GetFormat());
captureDT->SetTransform(target->GetTransform());
Matrix capturedTransform = target->GetTransform();
captureDT->SetTransform(capturedTransform);
// TODO: Capture AA Flags and reset them in PaintThread
SetAntialiasingFlags(this, captureDT);
SetAntialiasingFlags(this, target);
@ -234,12 +260,23 @@ ClientPaintedLayer::PaintOffMainThread()
ctx = nullptr;
PaintThread::Get()->PaintContents(captureDT, target);
// TODO: Fixup component alpha
DrawTarget* targetOnWhite = nullptr;
RefPtr<CapturedPaintState> capturedState
= MakeAndAddRef<CapturedPaintState>(state.mRegionToDraw,
target, targetOnWhite,
capturedTransform,
state.mMode,
state.mContentType);
PaintThread::Get()->PaintContents(captureDT,
capturedState,
RotatedContentBuffer::PrepareDrawTargetForPainting);
mContentClient->ReturnDrawTargetToBuffer(target);
didUpdate = true;
}
mContentClient->EndPaint(nullptr);
if (didUpdate) {

View File

@ -96,7 +96,8 @@ public:
virtual gfx::DrawTarget* BorrowDrawTargetForPainting(RotatedContentBuffer::PaintState& aPaintState,
RotatedContentBuffer::DrawIterator* aIter = nullptr) = 0;
virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) = 0;
virtual gfx::DrawTarget* BorrowDrawTargetForRecording(RotatedContentBuffer::PaintState& aPaintState,
RotatedContentBuffer::DrawIterator* aIter = nullptr) = 0;
// Called as part of the layers transation reply. Conveys data about our
// buffer(s) from the compositor. If appropriate we should swap references
// to our buffers.
@ -151,6 +152,11 @@ public:
{
return RotatedContentBuffer::BorrowDrawTargetForPainting(aPaintState, aIter);
}
virtual gfx::DrawTarget* BorrowDrawTargetForRecording(PaintState& aPaintState,
RotatedContentBuffer::DrawIterator* aIter = nullptr) override
{
return RotatedContentBuffer::BorrowDrawTargetForRecording(aPaintState, aIter);
}
virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) override
{
BorrowDrawTarget::ReturnDrawTarget(aReturned);
@ -234,6 +240,11 @@ public:
{
return RotatedContentBuffer::BorrowDrawTargetForPainting(aPaintState, aIter);
}
virtual gfx::DrawTarget* BorrowDrawTargetForRecording(PaintState& aPaintState,
RotatedContentBuffer::DrawIterator* aIter = nullptr) override
{
return RotatedContentBuffer::BorrowDrawTargetForRecording(aPaintState, aIter);
}
virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) override
{
BorrowDrawTarget::ReturnDrawTarget(aReturned);

View File

@ -5855,3 +5855,6 @@ pref("toolkit.crashreporter.include_context_heap", true);
// Open noopener links in a new process
pref("dom.noopener.newprocess.enabled", true);
pref("layers.omtp.enabled", false);
pref("layers.omtp.force-sync", false);