This introduces two new statistics to the overlay. The first is the ratio of pixel shader invocations (as determined by the GPU) to the number of pixels we determined need to be redrawn. The ideal ratio is 1.0, indicating that we filled every pixel exactly once. Anything over 1.0 indicates overdraw. We also add the ratio of shaded pixels to window size. This indicates how well we computed the invalid region, and whether or not we overfilled that region. Note that the OpenGL and Basic compositors do not yet query the GPU for this statistic, so they will estimate shader invocations by the area of DrawQuad calls. Finally, we remove the feature where layout can request the most recent overdraw statistic. It was not implemented on all compositors, and the only test that used it was disabled.
668 lines
22 KiB
C++
668 lines
22 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/Compositor.h"
|
|
#include "base/message_loop.h" // for MessageLoop
|
|
#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent
|
|
#include "mozilla/layers/Diagnostics.h"
|
|
#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc
|
|
#include "mozilla/layers/TextureClient.h"
|
|
#include "mozilla/layers/TextureHost.h"
|
|
#include "mozilla/layers/CompositorThread.h"
|
|
#include "mozilla/mozalloc.h" // for operator delete, etc
|
|
#include "gfx2DGlue.h"
|
|
#include "nsAppRunner.h"
|
|
|
|
namespace mozilla {
|
|
|
|
namespace layers {
|
|
|
|
Compositor::Compositor(widget::CompositorWidget* aWidget,
|
|
CompositorBridgeParent* aParent)
|
|
: mCompositorID(0)
|
|
, mDiagnosticTypes(DiagnosticTypes::NO_DIAGNOSTIC)
|
|
, mParent(aParent)
|
|
, mPixelsPerFrame(0)
|
|
, mPixelsFilled(0)
|
|
, mScreenRotation(ROTATION_0)
|
|
, mWidget(aWidget)
|
|
, mIsDestroyed(false)
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
// If the default color isn't white for Fennec, there is a black
|
|
// flash before the first page of a tab is loaded.
|
|
, mClearColor(1.0, 1.0, 1.0, 1.0)
|
|
, mDefaultClearColor(1.0, 1.0, 1.0, 1.0)
|
|
#else
|
|
, mClearColor(0.0, 0.0, 0.0, 0.0)
|
|
, mDefaultClearColor(0.0, 0.0, 0.0, 0.0)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
Compositor::~Compositor()
|
|
{
|
|
ReadUnlockTextures();
|
|
}
|
|
|
|
void
|
|
Compositor::Destroy()
|
|
{
|
|
TextureSourceProvider::Destroy();
|
|
FlushPendingNotifyNotUsed();
|
|
mIsDestroyed = true;
|
|
}
|
|
|
|
void
|
|
Compositor::EndFrame()
|
|
{
|
|
ReadUnlockTextures();
|
|
mLastCompositionEndTime = TimeStamp::Now();
|
|
}
|
|
|
|
/* static */ void
|
|
Compositor::AssertOnCompositorThread()
|
|
{
|
|
MOZ_ASSERT(!CompositorThreadHolder::Loop() ||
|
|
CompositorThreadHolder::Loop() == MessageLoop::current(),
|
|
"Can only call this from the compositor thread!");
|
|
}
|
|
|
|
bool
|
|
Compositor::ShouldDrawDiagnostics(DiagnosticFlags aFlags)
|
|
{
|
|
if ((aFlags & DiagnosticFlags::TILE) && !(mDiagnosticTypes & DiagnosticTypes::TILE_BORDERS)) {
|
|
return false;
|
|
}
|
|
if ((aFlags & DiagnosticFlags::BIGIMAGE) &&
|
|
!(mDiagnosticTypes & DiagnosticTypes::BIGIMAGE_BORDERS)) {
|
|
return false;
|
|
}
|
|
if (mDiagnosticTypes == DiagnosticTypes::NO_DIAGNOSTIC) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Compositor::DrawDiagnostics(DiagnosticFlags aFlags,
|
|
const nsIntRegion& aVisibleRegion,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform,
|
|
uint32_t aFlashCounter)
|
|
{
|
|
if (!ShouldDrawDiagnostics(aFlags)) {
|
|
return;
|
|
}
|
|
|
|
if (aVisibleRegion.GetNumRects() > 1) {
|
|
for (auto iter = aVisibleRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
DrawDiagnostics(aFlags | DiagnosticFlags::REGION_RECT,
|
|
IntRectToRect(iter.Get()), aClipRect, aTransform,
|
|
aFlashCounter);
|
|
}
|
|
}
|
|
|
|
DrawDiagnostics(aFlags, IntRectToRect(aVisibleRegion.GetBounds()),
|
|
aClipRect, aTransform, aFlashCounter);
|
|
}
|
|
|
|
void
|
|
Compositor::DrawDiagnostics(DiagnosticFlags aFlags,
|
|
const gfx::Rect& aVisibleRect,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform,
|
|
uint32_t aFlashCounter)
|
|
{
|
|
if (!ShouldDrawDiagnostics(aFlags)) {
|
|
return;
|
|
}
|
|
|
|
DrawDiagnosticsInternal(aFlags, aVisibleRect, aClipRect, aTransform,
|
|
aFlashCounter);
|
|
}
|
|
|
|
void
|
|
Compositor::DrawDiagnosticsInternal(DiagnosticFlags aFlags,
|
|
const gfx::Rect& aVisibleRect,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform,
|
|
uint32_t aFlashCounter)
|
|
{
|
|
#ifdef ANDROID
|
|
int lWidth = 10;
|
|
#else
|
|
int lWidth = 2;
|
|
#endif
|
|
|
|
gfx::Color color;
|
|
if (aFlags & DiagnosticFlags::CONTENT) {
|
|
color = gfx::Color(0.0f, 1.0f, 0.0f, 1.0f); // green
|
|
if (aFlags & DiagnosticFlags::COMPONENT_ALPHA) {
|
|
color = gfx::Color(0.0f, 1.0f, 1.0f, 1.0f); // greenish blue
|
|
}
|
|
} else if (aFlags & DiagnosticFlags::IMAGE) {
|
|
if (aFlags & DiagnosticFlags::NV12) {
|
|
color = gfx::Color(1.0f, 1.0f, 0.0f, 1.0f); // yellow
|
|
} else if (aFlags & DiagnosticFlags::YCBCR) {
|
|
color = gfx::Color(1.0f, 0.55f, 0.0f, 1.0f); // orange
|
|
} else {
|
|
color = gfx::Color(1.0f, 0.0f, 0.0f, 1.0f); // red
|
|
}
|
|
} else if (aFlags & DiagnosticFlags::COLOR) {
|
|
color = gfx::Color(0.0f, 0.0f, 1.0f, 1.0f); // blue
|
|
} else if (aFlags & DiagnosticFlags::CONTAINER) {
|
|
color = gfx::Color(0.8f, 0.0f, 0.8f, 1.0f); // purple
|
|
}
|
|
|
|
// make tile borders a bit more transparent to keep layer borders readable.
|
|
if (aFlags & DiagnosticFlags::TILE ||
|
|
aFlags & DiagnosticFlags::BIGIMAGE ||
|
|
aFlags & DiagnosticFlags::REGION_RECT) {
|
|
lWidth = 1;
|
|
color.r *= 0.7f;
|
|
color.g *= 0.7f;
|
|
color.b *= 0.7f;
|
|
color.a = color.a * 0.5f;
|
|
} else {
|
|
color.a = color.a * 0.7f;
|
|
}
|
|
|
|
|
|
if (mDiagnosticTypes & DiagnosticTypes::FLASH_BORDERS) {
|
|
float flash = (float)aFlashCounter / (float)DIAGNOSTIC_FLASH_COUNTER_MAX;
|
|
color.r *= flash;
|
|
color.g *= flash;
|
|
color.b *= flash;
|
|
}
|
|
|
|
SlowDrawRect(aVisibleRect, color, aClipRect, aTransform, lWidth);
|
|
}
|
|
|
|
static void
|
|
UpdateTextureCoordinates(gfx::TexturedTriangle& aTriangle,
|
|
const gfx::Rect& aRect,
|
|
const gfx::Rect& aIntersection,
|
|
const gfx::Rect& aTextureCoords)
|
|
{
|
|
// Calculate the relative offset of the intersection within the layer.
|
|
float dx = (aIntersection.x - aRect.x) / aRect.width;
|
|
float dy = (aIntersection.y - aRect.y) / aRect.height;
|
|
|
|
// Update the texture offset.
|
|
float x = aTextureCoords.x + dx * aTextureCoords.width;
|
|
float y = aTextureCoords.y + dy * aTextureCoords.height;
|
|
|
|
// Scale the texture width and height.
|
|
float w = aTextureCoords.width * aIntersection.width / aRect.width;
|
|
float h = aTextureCoords.height * aIntersection.height / aRect.height;
|
|
|
|
static const auto Clamp = [](float& f)
|
|
{
|
|
if (f >= 1.0f) f = 1.0f;
|
|
if (f <= 0.0f) f = 0.0f;
|
|
};
|
|
|
|
auto UpdatePoint = [&](const gfx::Point& p, gfx::Point& t)
|
|
{
|
|
t.x = x + (p.x - aIntersection.x) / aIntersection.width * w;
|
|
t.y = y + (p.y - aIntersection.y) / aIntersection.height * h;
|
|
|
|
Clamp(t.x);
|
|
Clamp(t.y);
|
|
};
|
|
|
|
UpdatePoint(aTriangle.p1, aTriangle.textureCoords.p1);
|
|
UpdatePoint(aTriangle.p2, aTriangle.textureCoords.p2);
|
|
UpdatePoint(aTriangle.p3, aTriangle.textureCoords.p3);
|
|
}
|
|
|
|
void
|
|
Compositor::DrawGeometry(const gfx::Rect& aRect,
|
|
const gfx::IntRect& aClipRect,
|
|
const EffectChain& aEffectChain,
|
|
gfx::Float aOpacity,
|
|
const gfx::Matrix4x4& aTransform,
|
|
const gfx::Rect& aVisibleRect,
|
|
const Maybe<gfx::Polygon>& aGeometry)
|
|
{
|
|
if (aRect.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (!aGeometry || !SupportsLayerGeometry()) {
|
|
DrawQuad(aRect, aClipRect, aEffectChain,
|
|
aOpacity, aTransform, aVisibleRect);
|
|
return;
|
|
}
|
|
|
|
// Cull completely invisible polygons.
|
|
if (aRect.Intersect(aGeometry->BoundingBox()).IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const gfx::Polygon clipped = aGeometry->ClipPolygon(aRect);
|
|
|
|
// Cull polygons with no area.
|
|
if (clipped.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
DrawPolygon(clipped, aRect, aClipRect, aEffectChain,
|
|
aOpacity, aTransform, aVisibleRect);
|
|
}
|
|
|
|
void
|
|
Compositor::DrawTriangles(const nsTArray<gfx::TexturedTriangle>& aTriangles,
|
|
const gfx::Rect& aRect,
|
|
const gfx::IntRect& aClipRect,
|
|
const EffectChain& aEffectChain,
|
|
gfx::Float aOpacity,
|
|
const gfx::Matrix4x4& aTransform,
|
|
const gfx::Rect& aVisibleRect)
|
|
{
|
|
for (const gfx::TexturedTriangle& triangle : aTriangles) {
|
|
DrawTriangle(triangle, aClipRect, aEffectChain,
|
|
aOpacity, aTransform, aVisibleRect);
|
|
}
|
|
}
|
|
|
|
static nsTArray<gfx::TexturedTriangle>
|
|
GenerateTexturedTriangles(const gfx::Polygon& aPolygon,
|
|
const gfx::Rect& aRect,
|
|
const gfx::Rect& aTexRect)
|
|
{
|
|
nsTArray<gfx::TexturedTriangle> texturedTriangles;
|
|
|
|
gfx::Rect layerRects[4];
|
|
gfx::Rect textureRects[4];
|
|
size_t rects = DecomposeIntoNoRepeatRects(aRect, aTexRect,
|
|
&layerRects, &textureRects);
|
|
for (size_t i = 0; i < rects; ++i) {
|
|
const gfx::Rect& rect = layerRects[i];
|
|
const gfx::Rect& texRect = textureRects[i];
|
|
const gfx::Polygon clipped = aPolygon.ClipPolygon(rect);
|
|
|
|
if (clipped.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
for (const gfx::Triangle& triangle : clipped.ToTriangles()) {
|
|
const gfx::Rect intersection = rect.Intersect(triangle.BoundingBox());
|
|
|
|
// Cull completely invisible triangles.
|
|
if (intersection.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
MOZ_ASSERT(rect.width > 0.0f && rect.height > 0.0f);
|
|
MOZ_ASSERT(intersection.width > 0.0f && intersection.height > 0.0f);
|
|
|
|
// Since the texture was created for non-split geometry, we need to
|
|
// update the texture coordinates to account for the split.
|
|
gfx::TexturedTriangle t(triangle);
|
|
UpdateTextureCoordinates(t, rect, intersection, texRect);
|
|
texturedTriangles.AppendElement(Move(t));
|
|
}
|
|
}
|
|
|
|
return texturedTriangles;
|
|
}
|
|
|
|
nsTArray<TexturedVertex>
|
|
TexturedTrianglesToVertexArray(const nsTArray<gfx::TexturedTriangle>& aTriangles)
|
|
{
|
|
const auto VertexFromPoints = [](const gfx::Point& p, const gfx::Point& t) {
|
|
return TexturedVertex { { p.x, p.y }, { t.x, t.y } };
|
|
};
|
|
|
|
nsTArray<TexturedVertex> vertices;
|
|
|
|
for (const gfx::TexturedTriangle& t : aTriangles) {
|
|
vertices.AppendElement(VertexFromPoints(t.p1, t.textureCoords.p1));
|
|
vertices.AppendElement(VertexFromPoints(t.p2, t.textureCoords.p2));
|
|
vertices.AppendElement(VertexFromPoints(t.p3, t.textureCoords.p3));
|
|
}
|
|
|
|
return vertices;
|
|
}
|
|
|
|
void
|
|
Compositor::DrawPolygon(const gfx::Polygon& aPolygon,
|
|
const gfx::Rect& aRect,
|
|
const gfx::IntRect& aClipRect,
|
|
const EffectChain& aEffectChain,
|
|
gfx::Float aOpacity,
|
|
const gfx::Matrix4x4& aTransform,
|
|
const gfx::Rect& aVisibleRect)
|
|
{
|
|
nsTArray<gfx::TexturedTriangle> texturedTriangles;
|
|
|
|
TexturedEffect* texturedEffect =
|
|
aEffectChain.mPrimaryEffect->AsTexturedEffect();
|
|
|
|
if (texturedEffect) {
|
|
texturedTriangles =
|
|
GenerateTexturedTriangles(aPolygon, aRect, texturedEffect->mTextureCoords);
|
|
} else {
|
|
for (const gfx::Triangle& triangle : aPolygon.ToTriangles()) {
|
|
texturedTriangles.AppendElement(gfx::TexturedTriangle(triangle));
|
|
}
|
|
}
|
|
|
|
if (texturedTriangles.IsEmpty()) {
|
|
// Nothing to render.
|
|
return;
|
|
}
|
|
|
|
DrawTriangles(texturedTriangles, aRect, aClipRect, aEffectChain,
|
|
aOpacity, aTransform, aVisibleRect);
|
|
}
|
|
|
|
void
|
|
Compositor::SlowDrawRect(const gfx::Rect& aRect, const gfx::Color& aColor,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform, int aStrokeWidth)
|
|
{
|
|
// TODO This should draw a rect using a single draw call but since
|
|
// this is only used for debugging overlays it's not worth optimizing ATM.
|
|
float opacity = 1.0f;
|
|
EffectChain effects;
|
|
|
|
effects.mPrimaryEffect = new EffectSolidColor(aColor);
|
|
// left
|
|
this->DrawQuad(gfx::Rect(aRect.x, aRect.y,
|
|
aStrokeWidth, aRect.height),
|
|
aClipRect, effects, opacity,
|
|
aTransform);
|
|
// top
|
|
this->DrawQuad(gfx::Rect(aRect.x + aStrokeWidth, aRect.y,
|
|
aRect.width - 2 * aStrokeWidth, aStrokeWidth),
|
|
aClipRect, effects, opacity,
|
|
aTransform);
|
|
// right
|
|
this->DrawQuad(gfx::Rect(aRect.x + aRect.width - aStrokeWidth, aRect.y,
|
|
aStrokeWidth, aRect.height),
|
|
aClipRect, effects, opacity,
|
|
aTransform);
|
|
// bottom
|
|
this->DrawQuad(gfx::Rect(aRect.x + aStrokeWidth, aRect.y + aRect.height - aStrokeWidth,
|
|
aRect.width - 2 * aStrokeWidth, aStrokeWidth),
|
|
aClipRect, effects, opacity,
|
|
aTransform);
|
|
}
|
|
|
|
void
|
|
Compositor::FillRect(const gfx::Rect& aRect, const gfx::Color& aColor,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform)
|
|
{
|
|
float opacity = 1.0f;
|
|
EffectChain effects;
|
|
|
|
effects.mPrimaryEffect = new EffectSolidColor(aColor);
|
|
this->DrawQuad(aRect,
|
|
aClipRect, effects, opacity,
|
|
aTransform);
|
|
}
|
|
|
|
|
|
static float
|
|
WrapTexCoord(float v)
|
|
{
|
|
// This should return values in range [0, 1.0)
|
|
return v - floorf(v);
|
|
}
|
|
|
|
static void
|
|
SetRects(size_t n,
|
|
decomposedRectArrayT* aLayerRects,
|
|
decomposedRectArrayT* aTextureRects,
|
|
float x0, float y0, float x1, float y1,
|
|
float tx0, float ty0, float tx1, float ty1,
|
|
bool flip_y)
|
|
{
|
|
if (flip_y) {
|
|
std::swap(ty0, ty1);
|
|
}
|
|
(*aLayerRects)[n] = gfx::Rect(x0, y0, x1 - x0, y1 - y0);
|
|
(*aTextureRects)[n] = gfx::Rect(tx0, ty0, tx1 - tx0, ty1 - ty0);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static inline bool
|
|
FuzzyEqual(float a, float b)
|
|
{
|
|
return fabs(a - b) < 0.0001f;
|
|
}
|
|
static inline bool
|
|
FuzzyLTE(float a, float b)
|
|
{
|
|
return a <= b + 0.0001f;
|
|
}
|
|
#endif
|
|
|
|
size_t
|
|
DecomposeIntoNoRepeatRects(const gfx::Rect& aRect,
|
|
const gfx::Rect& aTexCoordRect,
|
|
decomposedRectArrayT* aLayerRects,
|
|
decomposedRectArrayT* aTextureRects)
|
|
{
|
|
gfx::Rect texCoordRect = aTexCoordRect;
|
|
|
|
// If the texture should be flipped, it will have negative height. Detect that
|
|
// here and compensate for it. We will flip each rect as we emit it.
|
|
bool flipped = false;
|
|
if (texCoordRect.height < 0) {
|
|
flipped = true;
|
|
texCoordRect.y += texCoordRect.height;
|
|
texCoordRect.height = -texCoordRect.height;
|
|
}
|
|
|
|
// Wrap the texture coordinates so they are within [0,1] and cap width/height
|
|
// at 1. We rely on this below.
|
|
texCoordRect = gfx::Rect(gfx::Point(WrapTexCoord(texCoordRect.x),
|
|
WrapTexCoord(texCoordRect.y)),
|
|
gfx::Size(std::min(texCoordRect.width, 1.0f),
|
|
std::min(texCoordRect.height, 1.0f)));
|
|
|
|
NS_ASSERTION(texCoordRect.x >= 0.0f && texCoordRect.x <= 1.0f &&
|
|
texCoordRect.y >= 0.0f && texCoordRect.y <= 1.0f &&
|
|
texCoordRect.width >= 0.0f && texCoordRect.width <= 1.0f &&
|
|
texCoordRect.height >= 0.0f && texCoordRect.height <= 1.0f &&
|
|
texCoordRect.XMost() >= 0.0f && texCoordRect.XMost() <= 2.0f &&
|
|
texCoordRect.YMost() >= 0.0f && texCoordRect.YMost() <= 2.0f,
|
|
"We just wrapped the texture coordinates, didn't we?");
|
|
|
|
// Get the top left and bottom right points of the rectangle. Note that
|
|
// tl.x/tl.y are within [0,1] but br.x/br.y are within [0,2].
|
|
gfx::Point tl = texCoordRect.TopLeft();
|
|
gfx::Point br = texCoordRect.BottomRight();
|
|
|
|
NS_ASSERTION(tl.x >= 0.0f && tl.x <= 1.0f &&
|
|
tl.y >= 0.0f && tl.y <= 1.0f &&
|
|
br.x >= tl.x && br.x <= 2.0f &&
|
|
br.y >= tl.y && br.y <= 2.0f &&
|
|
FuzzyLTE(br.x - tl.x, 1.0f) &&
|
|
FuzzyLTE(br.y - tl.y, 1.0f),
|
|
"Somehow generated invalid texture coordinates");
|
|
|
|
// Then check if we wrap in either the x or y axis.
|
|
bool xwrap = br.x > 1.0f;
|
|
bool ywrap = br.y > 1.0f;
|
|
|
|
// If xwrap is false, the texture will be sampled from tl.x .. br.x.
|
|
// If xwrap is true, then it will be split into tl.x .. 1.0, and
|
|
// 0.0 .. WrapTexCoord(br.x). Same for the Y axis. The destination
|
|
// rectangle is also split appropriately, according to the calculated
|
|
// xmid/ymid values.
|
|
if (!xwrap && !ywrap) {
|
|
SetRects(0, aLayerRects, aTextureRects,
|
|
aRect.x, aRect.y, aRect.XMost(), aRect.YMost(),
|
|
tl.x, tl.y, br.x, br.y,
|
|
flipped);
|
|
return 1;
|
|
}
|
|
|
|
// If we are dealing with wrapping br.x and br.y are greater than 1.0 so
|
|
// wrap them here as well.
|
|
br = gfx::Point(xwrap ? WrapTexCoord(br.x) : br.x,
|
|
ywrap ? WrapTexCoord(br.y) : br.y);
|
|
|
|
// If we wrap around along the x axis, we will draw first from
|
|
// tl.x .. 1.0 and then from 0.0 .. br.x (which we just wrapped above).
|
|
// The same applies for the Y axis. The midpoints we calculate here are
|
|
// only valid if we actually wrap around.
|
|
GLfloat xmid = aRect.x + (1.0f - tl.x) / texCoordRect.width * aRect.width;
|
|
GLfloat ymid = aRect.y + (1.0f - tl.y) / texCoordRect.height * aRect.height;
|
|
|
|
// Due to floating-point inaccuracy, we have to use XMost()-x and YMost()-y
|
|
// to calculate width and height, respectively, to ensure that size will
|
|
// remain consistent going from absolute to relative and back again.
|
|
NS_ASSERTION(!xwrap ||
|
|
(xmid >= aRect.x &&
|
|
xmid <= aRect.XMost() &&
|
|
FuzzyEqual((xmid - aRect.x) + (aRect.XMost() - xmid), aRect.XMost() - aRect.x)),
|
|
"xmid should be within [x,XMost()] and the wrapped rect should have the same width");
|
|
NS_ASSERTION(!ywrap ||
|
|
(ymid >= aRect.y &&
|
|
ymid <= aRect.YMost() &&
|
|
FuzzyEqual((ymid - aRect.y) + (aRect.YMost() - ymid), aRect.YMost() - aRect.y)),
|
|
"ymid should be within [y,YMost()] and the wrapped rect should have the same height");
|
|
|
|
if (!xwrap && ywrap) {
|
|
SetRects(0, aLayerRects, aTextureRects,
|
|
aRect.x, aRect.y, aRect.XMost(), ymid,
|
|
tl.x, tl.y, br.x, 1.0f,
|
|
flipped);
|
|
SetRects(1, aLayerRects, aTextureRects,
|
|
aRect.x, ymid, aRect.XMost(), aRect.YMost(),
|
|
tl.x, 0.0f, br.x, br.y,
|
|
flipped);
|
|
return 2;
|
|
}
|
|
|
|
if (xwrap && !ywrap) {
|
|
SetRects(0, aLayerRects, aTextureRects,
|
|
aRect.x, aRect.y, xmid, aRect.YMost(),
|
|
tl.x, tl.y, 1.0f, br.y,
|
|
flipped);
|
|
SetRects(1, aLayerRects, aTextureRects,
|
|
xmid, aRect.y, aRect.XMost(), aRect.YMost(),
|
|
0.0f, tl.y, br.x, br.y,
|
|
flipped);
|
|
return 2;
|
|
}
|
|
|
|
SetRects(0, aLayerRects, aTextureRects,
|
|
aRect.x, aRect.y, xmid, ymid,
|
|
tl.x, tl.y, 1.0f, 1.0f,
|
|
flipped);
|
|
SetRects(1, aLayerRects, aTextureRects,
|
|
xmid, aRect.y, aRect.XMost(), ymid,
|
|
0.0f, tl.y, br.x, 1.0f,
|
|
flipped);
|
|
SetRects(2, aLayerRects, aTextureRects,
|
|
aRect.x, ymid, xmid, aRect.YMost(),
|
|
tl.x, 0.0f, 1.0f, br.y,
|
|
flipped);
|
|
SetRects(3, aLayerRects, aTextureRects,
|
|
xmid, ymid, aRect.XMost(), aRect.YMost(),
|
|
0.0f, 0.0f, br.x, br.y,
|
|
flipped);
|
|
return 4;
|
|
}
|
|
|
|
gfx::IntRect
|
|
Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform,
|
|
gfx::Matrix4x4* aOutTransform,
|
|
gfx::Rect* aOutLayerQuad)
|
|
{
|
|
// Compute the clip.
|
|
gfx::IntPoint rtOffset = GetCurrentRenderTarget()->GetOrigin();
|
|
gfx::IntSize rtSize = GetCurrentRenderTarget()->GetSize();
|
|
|
|
gfx::IntRect renderBounds(0, 0, rtSize.width, rtSize.height);
|
|
renderBounds.IntersectRect(renderBounds, aClipRect);
|
|
renderBounds.MoveBy(rtOffset);
|
|
|
|
// Apply the layer transform.
|
|
gfx::RectDouble dest = aTransform.TransformAndClipBounds(
|
|
gfx::RectDouble(aRect.x, aRect.y, aRect.width, aRect.height),
|
|
gfx::RectDouble(renderBounds.x, renderBounds.y, renderBounds.width, renderBounds.height));
|
|
dest -= rtOffset;
|
|
|
|
// Ensure we don't round out to -1, which trips up Direct3D.
|
|
dest.IntersectRect(dest, gfx::RectDouble(0, 0, rtSize.width, rtSize.height));
|
|
|
|
if (aOutLayerQuad) {
|
|
*aOutLayerQuad = gfx::Rect(dest.x, dest.y, dest.width, dest.height);
|
|
}
|
|
|
|
// Round out to integer.
|
|
gfx::IntRect result;
|
|
dest.RoundOut();
|
|
dest.ToIntRect(&result);
|
|
|
|
// Create a transform from adjusted clip space to render target space,
|
|
// translate it for the backdrop rect, then transform it into the backdrop's
|
|
// uv-space.
|
|
gfx::Matrix4x4 transform;
|
|
transform.PostScale(rtSize.width, rtSize.height, 1.0);
|
|
transform.PostTranslate(-result.x, -result.y, 0.0);
|
|
transform.PostScale(1 / float(result.width), 1 / float(result.height), 1.0);
|
|
*aOutTransform = transform;
|
|
return result;
|
|
}
|
|
|
|
gfx::IntRect
|
|
Compositor::ComputeBackdropCopyRect(const gfx::Triangle& aTriangle,
|
|
const gfx::IntRect& aClipRect,
|
|
const gfx::Matrix4x4& aTransform,
|
|
gfx::Matrix4x4* aOutTransform,
|
|
gfx::Rect* aOutLayerQuad)
|
|
{
|
|
gfx::Rect boundingBox = aTriangle.BoundingBox();
|
|
return ComputeBackdropCopyRect(boundingBox, aClipRect, aTransform,
|
|
aOutTransform, aOutLayerQuad);
|
|
}
|
|
|
|
void
|
|
Compositor::SetInvalid()
|
|
{
|
|
mParent = nullptr;
|
|
}
|
|
|
|
bool
|
|
Compositor::IsValid() const
|
|
{
|
|
return !!mParent;
|
|
}
|
|
|
|
void
|
|
Compositor::SetDispAcquireFence(Layer* aLayer)
|
|
{
|
|
}
|
|
|
|
bool
|
|
Compositor::NotifyNotUsedAfterComposition(TextureHost* aTextureHost)
|
|
{
|
|
if (IsDestroyed() || AsBasicCompositor()) {
|
|
return false;
|
|
}
|
|
return TextureSourceProvider::NotifyNotUsedAfterComposition(aTextureHost);
|
|
}
|
|
|
|
void
|
|
Compositor::GetFrameStats(GPUStats* aStats)
|
|
{
|
|
aStats->mInvalidPixels = mPixelsPerFrame;
|
|
aStats->mPixelsFilled = mPixelsFilled;
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|