mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
ef0adc5cf8
With image offscreen surfaces enabled for content layers on GTK3, our Basic compositor needs a way to deal with image layers buffering and compositing in a performant way. This patch subclasses BasicCompositor into a new X11BasicCompositor and makes use of a new TextureSource (X11DataTextureSourceBasic) in order to buffer TextureHost's data into gfxXlibSurface on compositor side so that we can use XRender when available to composite layer contents directly to the Window. When this buffering will occur, switch to ContentClientSingleBuffered. --HG-- extra : rebase_source : adad6b1c05dcf516a1ea84c6a529df5f141c198f
960 lines
33 KiB
C++
960 lines
33 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/layers/ContentClient.h"
|
|
#include "BasicLayers.h" // for BasicLayerManager
|
|
#include "CompositorChild.h" // for CompositorChild
|
|
#include "gfxColor.h" // for gfxRGBA
|
|
#include "gfxContext.h" // for gfxContext, etc
|
|
#include "gfxPlatform.h" // for gfxPlatform
|
|
#include "gfxPrefs.h" // for gfxPrefs
|
|
#include "gfxPoint.h" // for gfxIntSize, gfxPoint
|
|
#include "gfxTeeSurface.h" // for gfxTeeSurface
|
|
#include "gfxUtils.h" // for gfxUtils
|
|
#include "ipc/ShadowLayers.h" // for ShadowLayerForwarder
|
|
#include "mozilla/ArrayUtils.h" // for ArrayLength
|
|
#include "mozilla/gfx/2D.h" // for DrawTarget, Factory
|
|
#include "mozilla/gfx/BasePoint.h" // for BasePoint
|
|
#include "mozilla/gfx/BaseSize.h" // for BaseSize
|
|
#include "mozilla/gfx/Rect.h" // for Rect
|
|
#include "mozilla/gfx/Types.h"
|
|
#include "mozilla/layers/LayerManagerComposite.h"
|
|
#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData
|
|
#include "mozilla/layers/LayersTypes.h"
|
|
#include "nsAutoPtr.h" // for nsRefPtr
|
|
#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc
|
|
#include "nsISupportsImpl.h" // for gfxContext::Release, etc
|
|
#include "nsIWidget.h" // for nsIWidget
|
|
#include "prenv.h" // for PR_GetEnv
|
|
#include "nsLayoutUtils.h"
|
|
#ifdef XP_WIN
|
|
#include "gfxWindowsPlatform.h"
|
|
#endif
|
|
#ifdef MOZ_WIDGET_GTK
|
|
#include "gfxPlatformGtk.h"
|
|
#endif
|
|
#include "gfx2DGlue.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace gfx;
|
|
|
|
namespace layers {
|
|
|
|
static TextureFlags TextureFlagsForRotatedContentBufferFlags(uint32_t aBufferFlags)
|
|
{
|
|
TextureFlags result = TextureFlags::NO_FLAGS;
|
|
|
|
if (aBufferFlags & RotatedContentBuffer::BUFFER_COMPONENT_ALPHA) {
|
|
result |= TextureFlags::COMPONENT_ALPHA;
|
|
}
|
|
|
|
if (aBufferFlags & RotatedContentBuffer::ALLOW_REPEAT) {
|
|
result |= TextureFlags::ALLOW_REPEAT;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* static */ TemporaryRef<ContentClient>
|
|
ContentClient::CreateContentClient(CompositableForwarder* aForwarder)
|
|
{
|
|
LayersBackend backend = aForwarder->GetCompositorBackendType();
|
|
if (backend != LayersBackend::LAYERS_OPENGL &&
|
|
backend != LayersBackend::LAYERS_D3D9 &&
|
|
backend != LayersBackend::LAYERS_D3D11 &&
|
|
backend != LayersBackend::LAYERS_BASIC) {
|
|
return nullptr;
|
|
}
|
|
|
|
bool useDoubleBuffering = false;
|
|
|
|
#ifdef XP_WIN
|
|
if (backend == LayersBackend::LAYERS_D3D11) {
|
|
useDoubleBuffering = !!gfxWindowsPlatform::GetPlatform()->GetD2DDevice();
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_WIDGET_GTK
|
|
// We can't use double buffering when using image content with
|
|
// Xrender support on Linux, as ContentHostDoubleBuffered is not
|
|
// suited for direct uploads to the server.
|
|
if (!gfxPlatformGtk::GetPlatform()->UseImageOffscreenSurfaces() ||
|
|
!gfxPlatformGtk::GetPlatform()->UseXRender())
|
|
#endif
|
|
{
|
|
useDoubleBuffering = (LayerManagerComposite::SupportsDirectTexturing() &&
|
|
backend != LayersBackend::LAYERS_D3D9) ||
|
|
backend == LayersBackend::LAYERS_BASIC;
|
|
}
|
|
|
|
if (useDoubleBuffering || PR_GetEnv("MOZ_FORCE_DOUBLE_BUFFERING")) {
|
|
return new ContentClientDoubleBuffered(aForwarder);
|
|
}
|
|
#ifdef XP_MACOSX
|
|
if (backend == LayersBackend::LAYERS_OPENGL) {
|
|
return new ContentClientIncremental(aForwarder);
|
|
}
|
|
#endif
|
|
return new ContentClientSingleBuffered(aForwarder);
|
|
}
|
|
|
|
void
|
|
ContentClient::EndPaint()
|
|
{
|
|
// It is very important that this is called after any overridden EndPaint behaviour,
|
|
// because destroying textures is a three stage process:
|
|
// 1. We are done with the buffer and move it to ContentClient::mOldTextures,
|
|
// that happens in DestroyBuffers which is may be called indirectly from
|
|
// PaintThebes.
|
|
// 2. The content client calls RemoveTextureClient on the texture clients in
|
|
// mOldTextures and forgets them. They then become invalid. The compositable
|
|
// client keeps a record of IDs. This happens in EndPaint.
|
|
// 3. An IPC message is sent to destroy the corresponding texture host. That
|
|
// happens from OnTransaction.
|
|
// It is important that these steps happen in order.
|
|
OnTransaction();
|
|
}
|
|
|
|
// We pass a null pointer for the ContentClient Forwarder argument, which means
|
|
// this client will not have a ContentHost on the other side.
|
|
ContentClientBasic::ContentClientBasic()
|
|
: ContentClient(nullptr)
|
|
, RotatedContentBuffer(ContainsVisibleBounds)
|
|
{}
|
|
|
|
void
|
|
ContentClientBasic::CreateBuffer(ContentType aType,
|
|
const nsIntRect& aRect,
|
|
uint32_t aFlags,
|
|
RefPtr<gfx::DrawTarget>* aBlackDT,
|
|
RefPtr<gfx::DrawTarget>* aWhiteDT)
|
|
{
|
|
MOZ_ASSERT(!(aFlags & BUFFER_COMPONENT_ALPHA));
|
|
gfxImageFormat format =
|
|
gfxPlatform::GetPlatform()->OptimalFormatForContent(aType);
|
|
|
|
*aBlackDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
|
|
IntSize(aRect.width, aRect.height),
|
|
ImageFormatToSurfaceFormat(format));
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::DestroyBuffers()
|
|
{
|
|
if (!mTextureClient) {
|
|
return;
|
|
}
|
|
|
|
mOldTextures.AppendElement(mTextureClient);
|
|
mTextureClient = nullptr;
|
|
if (mTextureClientOnWhite) {
|
|
mOldTextures.AppendElement(mTextureClientOnWhite);
|
|
mTextureClientOnWhite = nullptr;
|
|
}
|
|
|
|
DestroyFrontBuffer();
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::BeginPaint()
|
|
{
|
|
EnsureBackBufferIfFrontBuffer();
|
|
|
|
// XXX: So we might not have a TextureClient yet.. because it will
|
|
// only be created by CreateBuffer.. which will deliver a locked surface!.
|
|
if (mTextureClient) {
|
|
SetBufferProvider(mTextureClient);
|
|
}
|
|
if (mTextureClientOnWhite) {
|
|
SetBufferProviderOnWhite(mTextureClientOnWhite);
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::EndPaint()
|
|
{
|
|
// XXX: We might still not have a texture client if PaintThebes
|
|
// decided we didn't need one yet because the region to draw was empty.
|
|
SetBufferProvider(nullptr);
|
|
SetBufferProviderOnWhite(nullptr);
|
|
for (unsigned i = 0; i< mOldTextures.Length(); ++i) {
|
|
if (mOldTextures[i]->IsLocked()) {
|
|
mOldTextures[i]->Unlock();
|
|
}
|
|
}
|
|
mOldTextures.Clear();
|
|
|
|
if (mTextureClient && mTextureClient->IsLocked()) {
|
|
mTextureClient->Unlock();
|
|
}
|
|
if (mTextureClientOnWhite && mTextureClientOnWhite->IsLocked()) {
|
|
mTextureClientOnWhite->Unlock();
|
|
}
|
|
ContentClientRemote::EndPaint();
|
|
}
|
|
|
|
bool
|
|
ContentClientRemoteBuffer::CreateAndAllocateTextureClient(RefPtr<TextureClient>& aClient,
|
|
TextureFlags aFlags)
|
|
{
|
|
TextureAllocationFlags allocFlags = TextureAllocationFlags::ALLOC_CLEAR_BUFFER;
|
|
if (aFlags & TextureFlags::ON_WHITE) {
|
|
allocFlags = TextureAllocationFlags::ALLOC_CLEAR_BUFFER_WHITE;
|
|
}
|
|
|
|
// gfx::BackendType::NONE means fallback to the content backend
|
|
aClient = CreateTextureClientForDrawing(mSurfaceFormat, mSize,
|
|
gfx::BackendType::NONE,
|
|
mTextureInfo.mTextureFlags | aFlags,
|
|
allocFlags);
|
|
if (!aClient) {
|
|
// try with ALLOC_FALLBACK
|
|
aClient = CreateTextureClientForDrawing(mSurfaceFormat, mSize,
|
|
gfx::BackendType::NONE,
|
|
mTextureInfo.mTextureFlags
|
|
| TextureFlags::ALLOC_FALLBACK
|
|
| aFlags,
|
|
allocFlags);
|
|
}
|
|
|
|
if (!aClient) {
|
|
return false;
|
|
}
|
|
|
|
NS_WARN_IF_FALSE(aClient->IsValid(), "Created an invalid texture client");
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::BuildTextureClients(SurfaceFormat aFormat,
|
|
const nsIntRect& aRect,
|
|
uint32_t aFlags)
|
|
{
|
|
// If we hit this assertion, then it might be due to an empty transaction
|
|
// followed by a real transaction. Our buffers should be created (but not
|
|
// painted in the empty transaction) and then painted (but not created) in the
|
|
// real transaction. That is kind of fragile, and this assert will catch
|
|
// circumstances where we screw that up, e.g., by unnecessarily recreating our
|
|
// buffers.
|
|
NS_ABORT_IF_FALSE(!mIsNewBuffer,
|
|
"Bad! Did we create a buffer twice without painting?");
|
|
|
|
mIsNewBuffer = true;
|
|
|
|
DestroyBuffers();
|
|
|
|
mSurfaceFormat = aFormat;
|
|
mSize = gfx::IntSize(aRect.width, aRect.height);
|
|
mTextureInfo.mTextureFlags = TextureFlagsForRotatedContentBufferFlags(aFlags);
|
|
|
|
if (aFlags & BUFFER_COMPONENT_ALPHA) {
|
|
mTextureInfo.mTextureFlags |= TextureFlags::COMPONENT_ALPHA;
|
|
}
|
|
|
|
CreateBackBuffer(mBufferRect);
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::CreateBackBuffer(const nsIntRect& aBufferRect)
|
|
{
|
|
if (!CreateAndAllocateTextureClient(mTextureClient, TextureFlags::ON_BLACK) ||
|
|
!AddTextureClient(mTextureClient)) {
|
|
AbortTextureClientCreation();
|
|
return;
|
|
}
|
|
if (mTextureInfo.mTextureFlags & TextureFlags::COMPONENT_ALPHA) {
|
|
if (!CreateAndAllocateTextureClient(mTextureClientOnWhite, TextureFlags::ON_WHITE) ||
|
|
!AddTextureClient(mTextureClientOnWhite)) {
|
|
AbortTextureClientCreation();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::CreateBuffer(ContentType aType,
|
|
const nsIntRect& aRect,
|
|
uint32_t aFlags,
|
|
RefPtr<gfx::DrawTarget>* aBlackDT,
|
|
RefPtr<gfx::DrawTarget>* aWhiteDT)
|
|
{
|
|
BuildTextureClients(gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType), aRect, aFlags);
|
|
if (!mTextureClient) {
|
|
return;
|
|
}
|
|
|
|
// We just created the textures and we are about to get their draw targets
|
|
// so we have to lock them here.
|
|
DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
|
|
MOZ_ASSERT(locked, "Could not lock the TextureClient");
|
|
|
|
*aBlackDT = mTextureClient->BorrowDrawTarget();
|
|
if (aFlags & BUFFER_COMPONENT_ALPHA) {
|
|
locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE);
|
|
MOZ_ASSERT(locked, "Could not lock the second TextureClient for component alpha");
|
|
|
|
*aWhiteDT = mTextureClientOnWhite->BorrowDrawTarget();
|
|
}
|
|
}
|
|
|
|
nsIntRegion
|
|
ContentClientRemoteBuffer::GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
|
|
const nsIntRegion& aVisibleRegion,
|
|
bool aDidSelfCopy)
|
|
{
|
|
nsIntRegion updatedRegion;
|
|
if (mIsNewBuffer || aDidSelfCopy) {
|
|
// A buffer reallocation clears both buffers. The front buffer has all the
|
|
// content by now, but the back buffer is still clear. Here, in effect, we
|
|
// are saying to copy all of the pixels of the front buffer to the back.
|
|
// Also when we self-copied in the buffer, the buffer space
|
|
// changes and some changed buffer content isn't reflected in the
|
|
// draw or invalidate region (on purpose!). When this happens, we
|
|
// need to read back the entire buffer too.
|
|
updatedRegion = aVisibleRegion;
|
|
mIsNewBuffer = false;
|
|
} else {
|
|
updatedRegion = aRegionToDraw;
|
|
}
|
|
|
|
NS_ASSERTION(BufferRect().Contains(aRegionToDraw.GetBounds()),
|
|
"Update outside of buffer rect!");
|
|
NS_ABORT_IF_FALSE(mTextureClient, "should have a back buffer by now");
|
|
|
|
return updatedRegion;
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw,
|
|
const nsIntRegion& aVisibleRegion,
|
|
bool aDidSelfCopy)
|
|
{
|
|
nsIntRegion updatedRegion = GetUpdatedRegion(aRegionToDraw,
|
|
aVisibleRegion,
|
|
aDidSelfCopy);
|
|
|
|
MOZ_ASSERT(mTextureClient);
|
|
if (mTextureClientOnWhite) {
|
|
mForwarder->UseComponentAlphaTextures(this, mTextureClient,
|
|
mTextureClientOnWhite);
|
|
} else {
|
|
mForwarder->UseTexture(this, mTextureClient);
|
|
}
|
|
mForwarder->UpdateTextureRegion(this,
|
|
ThebesBufferData(BufferRect(),
|
|
BufferRotation()),
|
|
updatedRegion);
|
|
}
|
|
|
|
void
|
|
ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
|
|
{
|
|
mFrontAndBackBufferDiffer = true;
|
|
}
|
|
|
|
void
|
|
ContentClientDoubleBuffered::DestroyFrontBuffer()
|
|
{
|
|
if (mFrontClient) {
|
|
mOldTextures.AppendElement(mFrontClient);
|
|
mFrontClient = nullptr;
|
|
}
|
|
|
|
if (mFrontClientOnWhite) {
|
|
mOldTextures.AppendElement(mFrontClientOnWhite);
|
|
mFrontClientOnWhite = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientDoubleBuffered::Updated(const nsIntRegion& aRegionToDraw,
|
|
const nsIntRegion& aVisibleRegion,
|
|
bool aDidSelfCopy)
|
|
{
|
|
ContentClientRemoteBuffer::Updated(aRegionToDraw, aVisibleRegion, aDidSelfCopy);
|
|
|
|
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
|
|
if (mFrontClient) {
|
|
// remove old buffer from CompositableHost
|
|
RefPtr<AsyncTransactionTracker> tracker = new RemoveTextureFromCompositableTracker();
|
|
// Hold TextureClient until transaction complete.
|
|
tracker->SetTextureClient(mFrontClient);
|
|
mFrontClient->SetRemoveFromCompositableTracker(tracker);
|
|
// RemoveTextureFromCompositableAsync() expects CompositorChild's presence.
|
|
GetForwarder()->RemoveTextureFromCompositableAsync(tracker, this, mFrontClient);
|
|
}
|
|
|
|
if (mFrontClientOnWhite) {
|
|
// remove old buffer from CompositableHost
|
|
RefPtr<AsyncTransactionTracker> tracker = new RemoveTextureFromCompositableTracker();
|
|
// Hold TextureClient until transaction complete.
|
|
tracker->SetTextureClient(mFrontClientOnWhite);
|
|
mFrontClientOnWhite->SetRemoveFromCompositableTracker(tracker);
|
|
// RemoveTextureFromCompositableAsync() expects CompositorChild's presence.
|
|
GetForwarder()->RemoveTextureFromCompositableAsync(tracker, this, mFrontClientOnWhite);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
ContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
|
|
{
|
|
mFrontUpdatedRegion = aFrontUpdatedRegion;
|
|
|
|
RefPtr<TextureClient> oldBack = mTextureClient;
|
|
mTextureClient = mFrontClient;
|
|
mFrontClient = oldBack;
|
|
|
|
oldBack = mTextureClientOnWhite;
|
|
mTextureClientOnWhite = mFrontClientOnWhite;
|
|
mFrontClientOnWhite = oldBack;
|
|
|
|
nsIntRect oldBufferRect = mBufferRect;
|
|
mBufferRect = mFrontBufferRect;
|
|
mFrontBufferRect = oldBufferRect;
|
|
|
|
nsIntPoint oldBufferRotation = mBufferRotation;
|
|
mBufferRotation = mFrontBufferRotation;
|
|
mFrontBufferRotation = oldBufferRotation;
|
|
|
|
MOZ_ASSERT(mFrontClient);
|
|
|
|
ContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion);
|
|
}
|
|
|
|
void
|
|
ContentClientDoubleBuffered::BeginPaint()
|
|
{
|
|
ContentClientRemoteBuffer::BeginPaint();
|
|
|
|
mIsNewBuffer = false;
|
|
|
|
if (!mFrontAndBackBufferDiffer) {
|
|
return;
|
|
}
|
|
|
|
if (mDidSelfCopy) {
|
|
// We can't easily draw our front buffer into us, since we're going to be
|
|
// copying stuff around anyway it's easiest if we just move our situation
|
|
// to non-rotated while we're at it. If this situation occurs we'll have
|
|
// hit a self-copy path in PaintThebes before as well anyway.
|
|
mBufferRect.MoveTo(mFrontBufferRect.TopLeft());
|
|
mBufferRotation = nsIntPoint();
|
|
return;
|
|
}
|
|
mBufferRect = mFrontBufferRect;
|
|
mBufferRotation = mFrontBufferRotation;
|
|
}
|
|
|
|
// Sync front/back buffers content
|
|
// After executing, the new back buffer has the same (interesting) pixels as
|
|
// the new front buffer, and mValidRegion et al. are correct wrt the new
|
|
// back buffer (i.e. as they were for the old back buffer)
|
|
void
|
|
ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
|
{
|
|
if (mTextureClient) {
|
|
DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
|
|
MOZ_ASSERT(locked);
|
|
}
|
|
if (mTextureClientOnWhite) {
|
|
DebugOnly<bool> locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE);
|
|
MOZ_ASSERT(locked);
|
|
}
|
|
|
|
if (!mFrontAndBackBufferDiffer) {
|
|
MOZ_ASSERT(!mDidSelfCopy, "If we have to copy the world, then our buffers are different, right?");
|
|
return;
|
|
}
|
|
MOZ_ASSERT(mFrontClient);
|
|
if (!mFrontClient) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LAYERS_LOG(("BasicShadowableThebes(%p): reading back <x=%d,y=%d,w=%d,h=%d>",
|
|
this,
|
|
mFrontUpdatedRegion.GetBounds().x,
|
|
mFrontUpdatedRegion.GetBounds().y,
|
|
mFrontUpdatedRegion.GetBounds().width,
|
|
mFrontUpdatedRegion.GetBounds().height));
|
|
|
|
mFrontAndBackBufferDiffer = false;
|
|
|
|
nsIntRegion updateRegion = mFrontUpdatedRegion;
|
|
if (mDidSelfCopy) {
|
|
mDidSelfCopy = false;
|
|
updateRegion = mBufferRect;
|
|
}
|
|
|
|
// No point in sync'ing what we are going to draw over anyway. And if there is
|
|
// nothing to sync at all, there is nothing to do and we can go home early.
|
|
updateRegion.Sub(updateRegion, aRegionToDraw);
|
|
if (updateRegion.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// We need to ensure that we lock these two buffers in the same
|
|
// order as the compositor to prevent deadlocks.
|
|
if (!mFrontClient->Lock(OpenMode::OPEN_READ_ONLY)) {
|
|
return;
|
|
}
|
|
if (mFrontClientOnWhite &&
|
|
!mFrontClientOnWhite->Lock(OpenMode::OPEN_READ_ONLY)) {
|
|
mFrontClient->Unlock();
|
|
return;
|
|
}
|
|
{
|
|
// Restrict the DrawTargets and frontBuffer to a scope to make
|
|
// sure there is no more external references to the DrawTargets
|
|
// when we Unlock the TextureClients.
|
|
RefPtr<DrawTarget> dt = mFrontClient->BorrowDrawTarget();
|
|
RefPtr<DrawTarget> dtOnWhite = mFrontClientOnWhite
|
|
? mFrontClientOnWhite->BorrowDrawTarget()
|
|
: nullptr;
|
|
RotatedBuffer frontBuffer(dt,
|
|
dtOnWhite,
|
|
mFrontBufferRect,
|
|
mFrontBufferRotation);
|
|
UpdateDestinationFrom(frontBuffer, updateRegion);
|
|
}
|
|
|
|
mFrontClient->Unlock();
|
|
if (mFrontClientOnWhite) {
|
|
mFrontClientOnWhite->Unlock();
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientDoubleBuffered::EnsureBackBufferIfFrontBuffer()
|
|
{
|
|
if (!mTextureClient && mFrontClient) {
|
|
CreateBackBuffer(mFrontBufferRect);
|
|
|
|
mBufferRect = mFrontBufferRect;
|
|
mBufferRotation = mFrontBufferRotation;
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource,
|
|
const nsIntRegion& aUpdateRegion)
|
|
{
|
|
DrawIterator iter;
|
|
while (DrawTarget* destDT =
|
|
BorrowDrawTargetForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_BLACK, &iter)) {
|
|
bool isClippingCheap = IsClippingCheap(destDT, iter.mDrawRegion);
|
|
if (isClippingCheap) {
|
|
gfxUtils::ClipToRegion(destDT, iter.mDrawRegion);
|
|
}
|
|
|
|
aSource.DrawBufferWithRotation(destDT, BUFFER_BLACK, 1.0, CompositionOp::OP_SOURCE);
|
|
if (isClippingCheap) {
|
|
destDT->PopClip();
|
|
}
|
|
// Flush the destination before the sources become inaccessible (Unlock).
|
|
destDT->Flush();
|
|
ReturnDrawTargetToBuffer(destDT);
|
|
}
|
|
|
|
if (aSource.HaveBufferOnWhite()) {
|
|
MOZ_ASSERT(HaveBufferOnWhite());
|
|
DrawIterator whiteIter;
|
|
while (DrawTarget* destDT =
|
|
BorrowDrawTargetForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_WHITE, &whiteIter)) {
|
|
bool isClippingCheap = IsClippingCheap(destDT, whiteIter.mDrawRegion);
|
|
if (isClippingCheap) {
|
|
gfxUtils::ClipToRegion(destDT, whiteIter.mDrawRegion);
|
|
}
|
|
|
|
aSource.DrawBufferWithRotation(destDT, BUFFER_WHITE, 1.0, CompositionOp::OP_SOURCE);
|
|
if (isClippingCheap) {
|
|
destDT->PopClip();
|
|
}
|
|
// Flush the destination before the sources become inaccessible (Unlock).
|
|
destDT->Flush();
|
|
ReturnDrawTargetToBuffer(destDT);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientSingleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
|
{
|
|
if (mTextureClient) {
|
|
DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
|
|
MOZ_ASSERT(locked);
|
|
}
|
|
if (mTextureClientOnWhite) {
|
|
DebugOnly<bool> locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE);
|
|
MOZ_ASSERT(locked);
|
|
}
|
|
}
|
|
|
|
static void
|
|
WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize)
|
|
{
|
|
if (*aRotationPoint < 0) {
|
|
*aRotationPoint += aSize;
|
|
} else if (*aRotationPoint >= aSize) {
|
|
*aRotationPoint -= aSize;
|
|
}
|
|
}
|
|
|
|
static void
|
|
FillSurface(DrawTarget* aDT, const nsIntRegion& aRegion,
|
|
const nsIntPoint& aOffset, const gfxRGBA& aColor)
|
|
{
|
|
nsIntRegionRectIterator iter(aRegion);
|
|
const nsIntRect* r;
|
|
while ((r = iter.Next()) != nullptr) {
|
|
aDT->FillRect(Rect(r->x - aOffset.x, r->y - aOffset.y,
|
|
r->width, r->height),
|
|
ColorPattern(ToColor(aColor)));
|
|
}
|
|
}
|
|
|
|
void
|
|
ContentClientIncremental::NotifyBufferCreated(ContentType aType, TextureFlags aFlags)
|
|
{
|
|
mTextureInfo.mTextureFlags = aFlags;
|
|
mContentType = aType;
|
|
|
|
mForwarder->CreatedIncrementalBuffer(this,
|
|
mTextureInfo,
|
|
mBufferRect);
|
|
|
|
}
|
|
|
|
RotatedContentBuffer::PaintState
|
|
ContentClientIncremental::BeginPaintBuffer(ThebesLayer* aLayer,
|
|
uint32_t aFlags)
|
|
{
|
|
mTextureInfo.mDeprecatedTextureHostFlags = DeprecatedTextureHostFlags::DEFAULT;
|
|
PaintState result;
|
|
// We need to disable rotation if we're going to be resampled when
|
|
// drawing, because we might sample across the rotation boundary.
|
|
bool canHaveRotation = !(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE);
|
|
|
|
nsIntRegion validRegion = aLayer->GetValidRegion();
|
|
|
|
bool canUseOpaqueSurface = aLayer->CanUseOpaqueSurface();
|
|
ContentType contentType =
|
|
canUseOpaqueSurface ? gfxContentType::COLOR :
|
|
gfxContentType::COLOR_ALPHA;
|
|
|
|
SurfaceMode mode;
|
|
nsIntRegion neededRegion;
|
|
bool canReuseBuffer;
|
|
nsIntRect destBufferRect;
|
|
|
|
while (true) {
|
|
mode = aLayer->GetSurfaceMode();
|
|
neededRegion = aLayer->GetVisibleRegion();
|
|
// If we're going to resample, we need a buffer that's in clamp mode.
|
|
canReuseBuffer = neededRegion.GetBounds().Size() <= mBufferRect.Size() &&
|
|
mHasBuffer &&
|
|
(!(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) ||
|
|
!(mTextureInfo.mTextureFlags & TextureFlags::ALLOW_REPEAT));
|
|
|
|
if (canReuseBuffer) {
|
|
if (mBufferRect.Contains(neededRegion.GetBounds())) {
|
|
// We don't need to adjust mBufferRect.
|
|
destBufferRect = mBufferRect;
|
|
} else {
|
|
// The buffer's big enough but doesn't contain everything that's
|
|
// going to be visible. We'll move it.
|
|
destBufferRect = nsIntRect(neededRegion.GetBounds().TopLeft(), mBufferRect.Size());
|
|
}
|
|
} else {
|
|
destBufferRect = neededRegion.GetBounds();
|
|
}
|
|
|
|
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
if (!gfxPrefs::ComponentAlphaEnabled() ||
|
|
!aLayer->GetParent() ||
|
|
!aLayer->GetParent()->SupportsComponentAlphaChildren()) {
|
|
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
|
} else {
|
|
contentType = gfxContentType::COLOR;
|
|
}
|
|
}
|
|
|
|
if ((aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) &&
|
|
(!neededRegion.GetBounds().IsEqualInterior(destBufferRect) ||
|
|
neededRegion.GetNumRects() > 1)) {
|
|
// The area we add to neededRegion might not be painted opaquely
|
|
if (mode == SurfaceMode::SURFACE_OPAQUE) {
|
|
contentType = gfxContentType::COLOR_ALPHA;
|
|
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
|
}
|
|
// For component alpha layers, we leave contentType as gfxContentType::COLOR.
|
|
|
|
// We need to validate the entire buffer, to make sure that only valid
|
|
// pixels are sampled
|
|
neededRegion = destBufferRect;
|
|
}
|
|
|
|
if (mHasBuffer &&
|
|
(mContentType != contentType ||
|
|
(mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mHasBufferOnWhite)) {
|
|
#ifdef MOZ_DUMP_PAINTING
|
|
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
|
if (mContentType != contentType) {
|
|
printf_stderr("Layer's content type has changed\n");
|
|
}
|
|
if ((mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mHasBufferOnWhite) {
|
|
printf_stderr("Layer's component alpha status has changed\n");
|
|
}
|
|
printf_stderr("Invalidating entire layer %p\n", aLayer);
|
|
}
|
|
#endif
|
|
// We're effectively clearing the valid region, so we need to draw
|
|
// the entire needed region now.
|
|
result.mRegionToInvalidate = aLayer->GetValidRegion();
|
|
validRegion.SetEmpty();
|
|
mHasBuffer = false;
|
|
mHasBufferOnWhite = false;
|
|
mBufferRect.SetRect(0, 0, 0, 0);
|
|
mBufferRotation.MoveTo(0, 0);
|
|
// Restart decision process with the cleared buffer. We can only go
|
|
// around the loop one more iteration, since mTexImage is null now.
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
result.mRegionToDraw.Sub(neededRegion, validRegion);
|
|
if (result.mRegionToDraw.IsEmpty())
|
|
return result;
|
|
|
|
if (destBufferRect.width > mForwarder->GetMaxTextureSize() ||
|
|
destBufferRect.height > mForwarder->GetMaxTextureSize()) {
|
|
return result;
|
|
}
|
|
|
|
// BlitTextureImage depends on the FBO texture target being
|
|
// TEXTURE_2D. This isn't the case on some older X1600-era Radeons.
|
|
if (!mForwarder->SupportsTextureBlitting() ||
|
|
!mForwarder->SupportsPartialUploads()) {
|
|
result.mRegionToDraw = neededRegion;
|
|
validRegion.SetEmpty();
|
|
mHasBuffer = false;
|
|
mHasBufferOnWhite = false;
|
|
mBufferRect.SetRect(0, 0, 0, 0);
|
|
mBufferRotation.MoveTo(0, 0);
|
|
canReuseBuffer = false;
|
|
}
|
|
|
|
nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
|
|
bool createdBuffer = false;
|
|
|
|
TextureFlags bufferFlags = TextureFlags::NO_FLAGS;
|
|
if (canHaveRotation) {
|
|
bufferFlags |= TextureFlags::ALLOW_REPEAT;
|
|
}
|
|
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
bufferFlags |= TextureFlags::COMPONENT_ALPHA;
|
|
}
|
|
if (canReuseBuffer) {
|
|
nsIntRect keepArea;
|
|
if (keepArea.IntersectRect(destBufferRect, mBufferRect)) {
|
|
// Set mBufferRotation so that the pixels currently in mBuffer
|
|
// will still be rendered in the right place when mBufferRect
|
|
// changes to destBufferRect.
|
|
nsIntPoint newRotation = mBufferRotation +
|
|
(destBufferRect.TopLeft() - mBufferRect.TopLeft());
|
|
WrapRotationAxis(&newRotation.x, mBufferRect.width);
|
|
WrapRotationAxis(&newRotation.y, mBufferRect.height);
|
|
NS_ASSERTION(nsIntRect(nsIntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
|
|
"newRotation out of bounds");
|
|
int32_t xBoundary = destBufferRect.XMost() - newRotation.x;
|
|
int32_t yBoundary = destBufferRect.YMost() - newRotation.y;
|
|
if ((drawBounds.x < xBoundary && xBoundary < drawBounds.XMost()) ||
|
|
(drawBounds.y < yBoundary && yBoundary < drawBounds.YMost()) ||
|
|
(newRotation != nsIntPoint(0,0) && !canHaveRotation)) {
|
|
// The stuff we need to redraw will wrap around an edge of the
|
|
// buffer, so we will need to do a self-copy
|
|
// If mBufferRotation == nsIntPoint(0,0) we could do a real
|
|
// self-copy but we're not going to do that in GL yet.
|
|
// We can't do a real self-copy because the buffer is rotated.
|
|
// So allocate a new buffer for the destination.
|
|
destBufferRect = neededRegion.GetBounds();
|
|
createdBuffer = true;
|
|
} else {
|
|
mBufferRect = destBufferRect;
|
|
mBufferRotation = newRotation;
|
|
}
|
|
} else {
|
|
// No pixels are going to be kept. The whole visible region
|
|
// will be redrawn, so we don't need to copy anything, so we don't
|
|
// set destBuffer.
|
|
mBufferRect = destBufferRect;
|
|
mBufferRotation = nsIntPoint(0,0);
|
|
}
|
|
} else {
|
|
// The buffer's not big enough, so allocate a new one
|
|
createdBuffer = true;
|
|
}
|
|
NS_ASSERTION(!(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) ||
|
|
destBufferRect == neededRegion.GetBounds(),
|
|
"If we're resampling, we need to validate the entire buffer");
|
|
|
|
if (!createdBuffer && !mHasBuffer) {
|
|
return result;
|
|
}
|
|
|
|
if (createdBuffer) {
|
|
if (mHasBuffer &&
|
|
(mode != SurfaceMode::SURFACE_COMPONENT_ALPHA || mHasBufferOnWhite)) {
|
|
mTextureInfo.mDeprecatedTextureHostFlags = DeprecatedTextureHostFlags::COPY_PREVIOUS;
|
|
}
|
|
|
|
mHasBuffer = true;
|
|
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
mHasBufferOnWhite = true;
|
|
}
|
|
mBufferRect = destBufferRect;
|
|
mBufferRotation = nsIntPoint(0,0);
|
|
NotifyBufferCreated(contentType, bufferFlags);
|
|
}
|
|
|
|
NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0),
|
|
"Rotation disabled, but we have nonzero rotation?");
|
|
|
|
nsIntRegion invalidate;
|
|
invalidate.Sub(aLayer->GetValidRegion(), destBufferRect);
|
|
result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate);
|
|
|
|
// If we do partial updates, we have to clip drawing to the regionToDraw.
|
|
// If we don't clip, background images will be fillrect'd to the region correctly,
|
|
// while text or lines will paint outside of the regionToDraw. This becomes apparent
|
|
// with concave regions. Right now the scrollbars invalidate a narrow strip of the bar
|
|
// although they never cover it. This leads to two draw rects, the narow strip and the actually
|
|
// newly exposed area. It would be wise to fix this glitch in any way to have simpler
|
|
// clip and draw regions.
|
|
result.mClip = DrawRegionClip::DRAW;
|
|
result.mMode = mode;
|
|
|
|
return result;
|
|
}
|
|
|
|
DrawTarget*
|
|
ContentClientIncremental::BorrowDrawTargetForPainting(PaintState& aPaintState,
|
|
RotatedContentBuffer::DrawIterator* aIter)
|
|
{
|
|
if (aPaintState.mMode == SurfaceMode::SURFACE_NONE) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (aIter) {
|
|
if (aIter->mCount++ > 0) {
|
|
return nullptr;
|
|
}
|
|
aIter->mDrawRegion = aPaintState.mRegionToDraw;
|
|
}
|
|
|
|
DrawTarget* result = nullptr;
|
|
|
|
nsIntRect drawBounds = aPaintState.mRegionToDraw.GetBounds();
|
|
MOZ_ASSERT(!mLoanedDrawTarget);
|
|
|
|
// BeginUpdate is allowed to modify the given region,
|
|
// if it wants more to be repainted than we request.
|
|
if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
nsIntRegion drawRegionCopy = aPaintState.mRegionToDraw;
|
|
RefPtr<DrawTarget> onBlack = GetUpdateSurface(BUFFER_BLACK, drawRegionCopy);
|
|
RefPtr<DrawTarget> onWhite = GetUpdateSurface(BUFFER_WHITE, aPaintState.mRegionToDraw);
|
|
if (onBlack && onWhite) {
|
|
NS_ASSERTION(aPaintState.mRegionToDraw == drawRegionCopy,
|
|
"BeginUpdate should always modify the draw region in the same way!");
|
|
FillSurface(onBlack, aPaintState.mRegionToDraw, nsIntPoint(drawBounds.x, drawBounds.y), gfxRGBA(0.0, 0.0, 0.0, 1.0));
|
|
FillSurface(onWhite, aPaintState.mRegionToDraw, nsIntPoint(drawBounds.x, drawBounds.y), gfxRGBA(1.0, 1.0, 1.0, 1.0));
|
|
mLoanedDrawTarget = Factory::CreateDualDrawTarget(onBlack, onWhite);
|
|
} else {
|
|
mLoanedDrawTarget = nullptr;
|
|
}
|
|
} else {
|
|
mLoanedDrawTarget = GetUpdateSurface(BUFFER_BLACK, aPaintState.mRegionToDraw);
|
|
}
|
|
if (!mLoanedDrawTarget) {
|
|
NS_WARNING("unable to get context for update");
|
|
return nullptr;
|
|
}
|
|
|
|
result = mLoanedDrawTarget;
|
|
mLoanedTransform = mLoanedDrawTarget->GetTransform();
|
|
mLoanedTransform.Translate(-drawBounds.x, -drawBounds.y);
|
|
result->SetTransform(mLoanedTransform);
|
|
mLoanedTransform.Translate(drawBounds.x, drawBounds.y);
|
|
|
|
if (mContentType == gfxContentType::COLOR_ALPHA) {
|
|
gfxUtils::ClipToRegion(result, aPaintState.mRegionToDraw);
|
|
nsIntRect bounds = aPaintState.mRegionToDraw.GetBounds();
|
|
result->ClearRect(Rect(bounds.x, bounds.y, bounds.width, bounds.height));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
ContentClientIncremental::Updated(const nsIntRegion& aRegionToDraw,
|
|
const nsIntRegion& aVisibleRegion,
|
|
bool aDidSelfCopy)
|
|
{
|
|
if (IsSurfaceDescriptorValid(mUpdateDescriptor)) {
|
|
mForwarder->UpdateTextureIncremental(this,
|
|
TextureIdentifier::Front,
|
|
mUpdateDescriptor,
|
|
aRegionToDraw,
|
|
mBufferRect,
|
|
mBufferRotation);
|
|
mUpdateDescriptor = SurfaceDescriptor();
|
|
}
|
|
if (IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite)) {
|
|
mForwarder->UpdateTextureIncremental(this,
|
|
TextureIdentifier::OnWhiteFront,
|
|
mUpdateDescriptorOnWhite,
|
|
aRegionToDraw,
|
|
mBufferRect,
|
|
mBufferRotation);
|
|
mUpdateDescriptorOnWhite = SurfaceDescriptor();
|
|
}
|
|
|
|
}
|
|
|
|
TemporaryRef<DrawTarget>
|
|
ContentClientIncremental::GetUpdateSurface(BufferType aType,
|
|
const nsIntRegion& aUpdateRegion)
|
|
{
|
|
nsIntRect rgnSize = aUpdateRegion.GetBounds();
|
|
if (!mBufferRect.Contains(rgnSize)) {
|
|
NS_ERROR("update outside of image");
|
|
return nullptr;
|
|
}
|
|
SurfaceDescriptor desc;
|
|
if (!mForwarder->AllocSurfaceDescriptor(rgnSize.Size().ToIntSize(),
|
|
mContentType,
|
|
&desc)) {
|
|
NS_WARNING("creating SurfaceDescriptor failed!");
|
|
Clear();
|
|
return nullptr;
|
|
}
|
|
|
|
if (aType == BUFFER_BLACK) {
|
|
MOZ_ASSERT(!IsSurfaceDescriptorValid(mUpdateDescriptor));
|
|
mUpdateDescriptor = desc;
|
|
} else {
|
|
MOZ_ASSERT(!IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite));
|
|
MOZ_ASSERT(aType == BUFFER_WHITE);
|
|
mUpdateDescriptorOnWhite = desc;
|
|
}
|
|
|
|
return GetDrawTargetForDescriptor(desc, gfx::BackendType::COREGRAPHICS);
|
|
}
|
|
|
|
}
|
|
}
|