import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

1526
gfx/2d/2D.h Normal file

File diff suppressed because it is too large Load diff

86
gfx/2d/AutoHelpersWin.h Normal file
View file

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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/. */
#ifndef mozilla_gfx_AutoHelpersWin_h
#define mozilla_gfx_AutoHelpersWin_h
#include <windows.h>
namespace mozilla {
namespace gfx {
// Get the global device context, and auto-release it on destruction.
class AutoDC
{
public:
AutoDC() {
mDC = ::GetDC(nullptr);
}
~AutoDC() {
::ReleaseDC(nullptr, mDC);
}
HDC GetDC() {
return mDC;
}
private:
HDC mDC;
};
// Select a font into the given DC, and auto-restore.
class AutoSelectFont
{
public:
AutoSelectFont(HDC aDC, LOGFONTW *aLogFont)
: mOwnsFont(false)
{
mFont = ::CreateFontIndirectW(aLogFont);
if (mFont) {
mOwnsFont = true;
mDC = aDC;
mOldFont = (HFONT)::SelectObject(aDC, mFont);
} else {
mOldFont = nullptr;
}
}
AutoSelectFont(HDC aDC, HFONT aFont)
: mOwnsFont(false)
{
mDC = aDC;
mFont = aFont;
mOldFont = (HFONT)::SelectObject(aDC, aFont);
}
~AutoSelectFont() {
if (mOldFont) {
::SelectObject(mDC, mOldFont);
if (mOwnsFont) {
::DeleteObject(mFont);
}
}
}
bool IsValid() const {
return mFont != nullptr;
}
HFONT GetFont() const {
return mFont;
}
private:
HDC mDC;
HFONT mFont;
HFONT mOldFont;
bool mOwnsFont;
};
} // gfx
} // mozilla
#endif // mozilla_gfx_AutoHelpersWin_h

110
gfx/2d/BaseCoord.h Normal file
View file

@ -0,0 +1,110 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_BASECOORD_H_
#define MOZILLA_GFX_BASECOORD_H_
#include "mozilla/Attributes.h"
namespace mozilla {
namespace gfx {
/**
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass. This allows methods to safely
* cast 'this' to 'Sub*'.
*/
template <class T, class Sub>
struct BaseCoord {
T value;
// Constructors
constexpr BaseCoord() : value(0) {}
explicit constexpr BaseCoord(T aValue) : value(aValue) {}
// Note that '=' isn't defined so we'll get the
// compiler generated default assignment operator
operator T() const { return value; }
friend bool operator==(Sub aA, Sub aB) {
return aA.value == aB.value;
}
friend bool operator!=(Sub aA, Sub aB) {
return aA.value != aB.value;
}
friend Sub operator+(Sub aA, Sub aB) {
return Sub(aA.value + aB.value);
}
friend Sub operator-(Sub aA, Sub aB) {
return Sub(aA.value - aB.value);
}
friend Sub operator*(Sub aCoord, T aScale) {
return Sub(aCoord.value * aScale);
}
friend Sub operator*(T aScale, Sub aCoord) {
return Sub(aScale * aCoord.value);
}
friend Sub operator/(Sub aCoord, T aScale) {
return Sub(aCoord.value / aScale);
}
// 'scale / coord' is intentionally omitted because it doesn't make sense.
Sub& operator+=(Sub aCoord) {
value += aCoord.value;
return *static_cast<Sub*>(this);
}
Sub& operator-=(Sub aCoord) {
value -= aCoord.value;
return *static_cast<Sub*>(this);
}
Sub& operator*=(T aScale) {
value *= aScale;
return *static_cast<Sub*>(this);
}
Sub& operator/=(T aScale) {
value /= aScale;
return *static_cast<Sub*>(this);
}
// Since BaseCoord is implicitly convertible to its value type T, we need
// mixed-type operator overloads to avoid ambiguities at mixed-type call
// sites. As we transition more of our code to strongly-typed classes, we
// may be able to remove some or all of these overloads.
friend bool operator==(Sub aA, T aB) {
return aA.value == aB;
}
friend bool operator==(T aA, Sub aB) {
return aA == aB.value;
}
friend bool operator!=(Sub aA, T aB) {
return aA.value != aB;
}
friend bool operator!=(T aA, Sub aB) {
return aA != aB.value;
}
friend T operator+(Sub aA, T aB) {
return aA.value + aB;
}
friend T operator+(T aA, Sub aB) {
return aA + aB.value;
}
friend T operator-(Sub aA, T aB) {
return aA.value - aB;
}
friend T operator-(T aA, Sub aB) {
return aA - aB.value;
}
Sub operator-() const {
return Sub(-value);
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_BASECOORD_H_ */

152
gfx/2d/BaseMargin.h Normal file
View file

@ -0,0 +1,152 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_BASEMARGIN_H_
#define MOZILLA_GFX_BASEMARGIN_H_
#include <ostream>
#include "Types.h"
namespace mozilla {
/**
* Sides represents a set of physical sides.
*/
struct Sides final {
Sides() : mBits(0) {}
explicit Sides(SideBits aSideBits)
{
MOZ_ASSERT((aSideBits & ~eSideBitsAll) == 0, "illegal side bits");
mBits = aSideBits;
}
bool IsEmpty() const { return mBits == 0; }
bool Top() const { return (mBits & eSideBitsTop) != 0; }
bool Right() const { return (mBits & eSideBitsRight) != 0; }
bool Bottom() const { return (mBits & eSideBitsBottom) != 0; }
bool Left() const { return (mBits & eSideBitsLeft) != 0; }
bool Contains(SideBits aSideBits) const
{
MOZ_ASSERT((aSideBits & ~eSideBitsAll) == 0, "illegal side bits");
return (mBits & aSideBits) == aSideBits;
}
Sides operator|(Sides aOther) const
{
return Sides(SideBits(mBits | aOther.mBits));
}
Sides operator|(SideBits aSideBits) const
{
return *this | Sides(aSideBits);
}
Sides& operator|=(Sides aOther)
{
mBits |= aOther.mBits;
return *this;
}
Sides& operator|=(SideBits aSideBits)
{
return *this |= Sides(aSideBits);
}
bool operator==(Sides aOther) const
{
return mBits == aOther.mBits;
}
bool operator!=(Sides aOther) const
{
return !(*this == aOther);
}
private:
uint8_t mBits;
};
namespace gfx {
/**
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass.
*/
template <class T, class Sub>
struct BaseMargin {
typedef mozilla::Side SideT; // because we have a method named Side
// Do not change the layout of these members; the Side() methods below
// depend on this order.
T top, right, bottom, left;
// Constructors
BaseMargin() : top(0), right(0), bottom(0), left(0) {}
BaseMargin(T aTop, T aRight, T aBottom, T aLeft) :
top(aTop), right(aRight), bottom(aBottom), left(aLeft) {}
void SizeTo(T aTop, T aRight, T aBottom, T aLeft)
{
top = aTop; right = aRight; bottom = aBottom; left = aLeft;
}
T LeftRight() const { return left + right; }
T TopBottom() const { return top + bottom; }
T& Side(SideT aSide) {
// This is ugly!
return *(&top + int(aSide));
}
T Side(SideT aSide) const {
// This is ugly!
return *(&top + int(aSide));
}
void ApplySkipSides(Sides aSkipSides)
{
if (aSkipSides.Top()) {
top = 0;
}
if (aSkipSides.Right()) {
right = 0;
}
if (aSkipSides.Bottom()) {
bottom = 0;
}
if (aSkipSides.Left()) {
left = 0;
}
}
// Overloaded operators. Note that '=' isn't defined so we'll get the
// compiler generated default assignment operator
bool operator==(const Sub& aMargin) const {
return top == aMargin.top && right == aMargin.right &&
bottom == aMargin.bottom && left == aMargin.left;
}
bool operator!=(const Sub& aMargin) const {
return !(*this == aMargin);
}
Sub operator+(const Sub& aMargin) const {
return Sub(top + aMargin.top, right + aMargin.right,
bottom + aMargin.bottom, left + aMargin.left);
}
Sub operator-(const Sub& aMargin) const {
return Sub(top - aMargin.top, right - aMargin.right,
bottom - aMargin.bottom, left - aMargin.left);
}
Sub& operator+=(const Sub& aMargin) {
top += aMargin.top;
right += aMargin.right;
bottom += aMargin.bottom;
left += aMargin.left;
return *static_cast<Sub*>(this);
}
friend std::ostream& operator<<(std::ostream& aStream,
const BaseMargin& aMargin) {
return aStream << '(' << aMargin.top << ',' << aMargin.right << ','
<< aMargin.bottom << ',' << aMargin.left << ')';
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_BASEMARGIN_H_ */

121
gfx/2d/BasePoint.h Normal file
View file

@ -0,0 +1,121 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_BASEPOINT_H_
#define MOZILLA_GFX_BASEPOINT_H_
#include <cmath>
#include <ostream>
#include "mozilla/Attributes.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/TypeTraits.h"
namespace mozilla {
namespace gfx {
/**
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass. This allows methods to safely
* cast 'this' to 'Sub*'.
*/
template <class T, class Sub, class Coord = T>
struct BasePoint {
union {
struct {
T x, y;
};
T components[2];
};
// Constructors
constexpr BasePoint() : x(0), y(0) {}
constexpr BasePoint(Coord aX, Coord aY) : x(aX), y(aY) {}
void MoveTo(T aX, T aY) { x = aX; y = aY; }
void MoveBy(T aDx, T aDy) { x += aDx; y += aDy; }
// Note that '=' isn't defined so we'll get the
// compiler generated default assignment operator
bool operator==(const Sub& aPoint) const {
return x == aPoint.x && y == aPoint.y;
}
bool operator!=(const Sub& aPoint) const {
return x != aPoint.x || y != aPoint.y;
}
Sub operator+(const Sub& aPoint) const {
return Sub(x + aPoint.x, y + aPoint.y);
}
Sub operator-(const Sub& aPoint) const {
return Sub(x - aPoint.x, y - aPoint.y);
}
Sub& operator+=(const Sub& aPoint) {
x += aPoint.x;
y += aPoint.y;
return *static_cast<Sub*>(this);
}
Sub& operator-=(const Sub& aPoint) {
x -= aPoint.x;
y -= aPoint.y;
return *static_cast<Sub*>(this);
}
Sub operator*(T aScale) const {
return Sub(x * aScale, y * aScale);
}
Sub operator/(T aScale) const {
return Sub(x / aScale, y / aScale);
}
Sub operator-() const {
return Sub(-x, -y);
}
T DotProduct(const Sub& aPoint) const {
return x * aPoint.x + y * aPoint.y;
}
Coord Length() const {
return hypot(x, y);
}
T LengthSquare() const {
return x * x + y * y;
}
// Round() is *not* rounding to nearest integer if the values are negative.
// They are always rounding as floor(n + 0.5).
// See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14
Sub& Round() {
x = Coord(floor(T(x) + T(0.5)));
y = Coord(floor(T(y) + T(0.5)));
return *static_cast<Sub*>(this);
}
// "Finite" means not inf and not NaN
bool IsFinite() const
{
typedef typename mozilla::Conditional<mozilla::IsSame<T, float>::value, float, double>::Type FloatType;
return (mozilla::IsFinite(FloatType(x)) && mozilla::IsFinite(FloatType(y)));
return true;
}
void Clamp(T aMaxAbsValue)
{
x = std::max(std::min(x, aMaxAbsValue), -aMaxAbsValue);
y = std::max(std::min(y, aMaxAbsValue), -aMaxAbsValue);
}
friend std::ostream& operator<<(std::ostream& stream, const BasePoint<T, Sub, Coord>& aPoint) {
return stream << '(' << aPoint.x << ',' << aPoint.y << ')';
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_BASEPOINT_H_ */

122
gfx/2d/BasePoint3D.h Normal file
View file

@ -0,0 +1,122 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_BASEPOINT3D_H_
#define MOZILLA_BASEPOINT3D_H_
#include "mozilla/Assertions.h"
namespace mozilla {
namespace gfx {
/**
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass. This allows methods to safely
* cast 'this' to 'Sub*'.
*/
template <class T, class Sub>
struct BasePoint3D {
union {
struct {
T x, y, z;
};
T components[3];
};
// Constructors
BasePoint3D() : x(0), y(0), z(0) {}
BasePoint3D(T aX, T aY, T aZ) : x(aX), y(aY), z(aZ) {}
void MoveTo(T aX, T aY, T aZ) { x = aX; y = aY; z = aZ; }
void MoveBy(T aDx, T aDy, T aDz) { x += aDx; y += aDy; z += aDz; }
// Note that '=' isn't defined so we'll get the
// compiler generated default assignment operator
T& operator[](int aIndex) {
MOZ_ASSERT(aIndex >= 0 && aIndex <= 2);
return *((&x)+aIndex);
}
const T& operator[](int aIndex) const {
MOZ_ASSERT(aIndex >= 0 && aIndex <= 2);
return *((&x)+aIndex);
}
bool operator==(const Sub& aPoint) const {
return x == aPoint.x && y == aPoint.y && z == aPoint.z;
}
bool operator!=(const Sub& aPoint) const {
return x != aPoint.x || y != aPoint.y || z != aPoint.z;
}
Sub operator+(const Sub& aPoint) const {
return Sub(x + aPoint.x, y + aPoint.y, z + aPoint.z);
}
Sub operator-(const Sub& aPoint) const {
return Sub(x - aPoint.x, y - aPoint.y, z - aPoint.z);
}
Sub& operator+=(const Sub& aPoint) {
x += aPoint.x;
y += aPoint.y;
z += aPoint.z;
return *static_cast<Sub*>(this);
}
Sub& operator-=(const Sub& aPoint) {
x -= aPoint.x;
y -= aPoint.y;
z -= aPoint.z;
return *static_cast<Sub*>(this);
}
Sub operator*(T aScale) const {
return Sub(x * aScale, y * aScale, z * aScale);
}
Sub operator/(T aScale) const {
return Sub(x / aScale, y / aScale, z / aScale);
}
Sub& operator*=(T aScale) {
x *= aScale;
y *= aScale;
z *= aScale;
return *static_cast<Sub*>(this);
}
Sub& operator/=(T aScale) {
x /= aScale;
y /= aScale;
z /= aScale;
return *static_cast<Sub*>(this);
}
Sub operator-() const {
return Sub(-x, -y, -z);
}
Sub CrossProduct(const Sub& aPoint) const {
return Sub(y * aPoint.z - aPoint.y * z,
z * aPoint.x - aPoint.z * x,
x * aPoint.y - aPoint.x * y);
}
T DotProduct(const Sub& aPoint) const {
return x * aPoint.x + y * aPoint.y + z * aPoint.z;
}
T Length() const {
return sqrt(x*x + y*y + z*z);
}
// Invalid for points with distance from origin of 0.
void Normalize() {
*this /= Length();
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_BASEPOINT3D_H_ */

131
gfx/2d/BasePoint4D.h Normal file
View file

@ -0,0 +1,131 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_BASEPOINT4D_H_
#define MOZILLA_BASEPOINT4D_H_
#include "mozilla/Assertions.h"
namespace mozilla {
namespace gfx {
/**
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass. This allows methods to safely
* cast 'this' to 'Sub*'.
*/
template <class T, class Sub>
struct BasePoint4D {
union {
struct {
T x, y, z, w;
};
T components[4];
};
// Constructors
BasePoint4D() : x(0), y(0), z(0), w(0) {}
BasePoint4D(T aX, T aY, T aZ, T aW) : x(aX), y(aY), z(aZ), w(aW) {}
void MoveTo(T aX, T aY, T aZ, T aW) { x = aX; y = aY; z = aZ; w = aW; }
void MoveBy(T aDx, T aDy, T aDz, T aDw) { x += aDx; y += aDy; z += aDz; w += aDw; }
// Note that '=' isn't defined so we'll get the
// compiler generated default assignment operator
bool operator==(const Sub& aPoint) const {
return x == aPoint.x && y == aPoint.y &&
z == aPoint.z && w == aPoint.w;
}
bool operator!=(const Sub& aPoint) const {
return x != aPoint.x || y != aPoint.y ||
z != aPoint.z || w != aPoint.w;
}
Sub operator+(const Sub& aPoint) const {
return Sub(x + aPoint.x, y + aPoint.y, z + aPoint.z, w + aPoint.w);
}
Sub operator-(const Sub& aPoint) const {
return Sub(x - aPoint.x, y - aPoint.y, z - aPoint.z, w - aPoint.w);
}
Sub& operator+=(const Sub& aPoint) {
x += aPoint.x;
y += aPoint.y;
z += aPoint.z;
w += aPoint.w;
return *static_cast<Sub*>(this);
}
Sub& operator-=(const Sub& aPoint) {
x -= aPoint.x;
y -= aPoint.y;
z -= aPoint.z;
w -= aPoint.w;
return *static_cast<Sub*>(this);
}
Sub operator*(T aScale) const {
return Sub(x * aScale, y * aScale, z * aScale, w * aScale);
}
Sub operator/(T aScale) const {
return Sub(x / aScale, y / aScale, z / aScale, w / aScale);
}
Sub& operator*=(T aScale) {
x *= aScale;
y *= aScale;
z *= aScale;
w *= aScale;
return *static_cast<Sub*>(this);
}
Sub& operator/=(T aScale) {
x /= aScale;
y /= aScale;
z /= aScale;
w /= aScale;
return *static_cast<Sub*>(this);
}
Sub operator-() const {
return Sub(-x, -y, -z, -w);
}
T& operator[](int aIndex) {
MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index");
return *((&x)+aIndex);
}
const T& operator[](int aIndex) const {
MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index");
return *((&x)+aIndex);
}
T DotProduct(const Sub& aPoint) const {
return x * aPoint.x + y * aPoint.y + z * aPoint.z + w * aPoint.w;
}
// Ignores the 4th component!
Sub CrossProduct(const Sub& aPoint) const {
return Sub(y * aPoint.z - aPoint.y * z,
z * aPoint.x - aPoint.z * x,
x * aPoint.y - aPoint.x * y,
0);
}
T Length() const {
return sqrt(x*x + y*y + z*z + w*w);
}
void Normalize() {
*this /= Length();
}
bool HasPositiveWCoord() { return w > 0; }
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_BASEPOINT4D_H_ */

586
gfx/2d/BaseRect.h Normal file
View file

@ -0,0 +1,586 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_BASERECT_H_
#define MOZILLA_GFX_BASERECT_H_
#include <algorithm>
#include <cmath>
#include <ostream>
#include "mozilla/Assertions.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/TypeTraits.h"
#include "Types.h"
namespace mozilla {
namespace gfx {
/**
* Rectangles have two interpretations: a set of (zero-size) points,
* and a rectangular area of the plane. Most rectangle operations behave
* the same no matter what interpretation is being used, but some operations
* differ:
* -- Equality tests behave differently. When a rectangle represents an area,
* all zero-width and zero-height rectangles are equal to each other since they
* represent the empty area. But when a rectangle represents a set of
* mathematical points, zero-width and zero-height rectangles can be unequal.
* -- The union operation can behave differently. When rectangles represent
* areas, taking the union of a zero-width or zero-height rectangle with
* another rectangle can just ignore the empty rectangle. But when rectangles
* represent sets of mathematical points, we may need to extend the latter
* rectangle to include the points of a zero-width or zero-height rectangle.
*
* To ensure that these interpretations are explicitly disambiguated, we
* deny access to the == and != operators and require use of IsEqualEdges and
* IsEqualInterior instead. Similarly we provide separate Union and UnionEdges
* methods.
*
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass.
*/
template <class T, class Sub, class Point, class SizeT, class MarginT>
struct BaseRect {
T x, y, width, height;
// Constructors
BaseRect() : x(0), y(0), width(0), height(0) {}
BaseRect(const Point& aOrigin, const SizeT &aSize) :
x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height)
{
}
BaseRect(T aX, T aY, T aWidth, T aHeight) :
x(aX), y(aY), width(aWidth), height(aHeight)
{
}
// Emptiness. An empty rect is one that has no area, i.e. its height or width
// is <= 0
bool IsEmpty() const { return height <= 0 || width <= 0; }
void SetEmpty() { width = height = 0; }
// "Finite" means not inf and not NaN
bool IsFinite() const
{
typedef typename mozilla::Conditional<mozilla::IsSame<T, float>::value, float, double>::Type FloatType;
return (mozilla::IsFinite(FloatType(x)) &&
mozilla::IsFinite(FloatType(y)) &&
mozilla::IsFinite(FloatType(width)) &&
mozilla::IsFinite(FloatType(height)));
}
// Returns true if this rectangle contains the interior of aRect. Always
// returns true if aRect is empty, and always returns false is aRect is
// nonempty but this rect is empty.
bool Contains(const Sub& aRect) const
{
return aRect.IsEmpty() ||
(x <= aRect.x && aRect.XMost() <= XMost() &&
y <= aRect.y && aRect.YMost() <= YMost());
}
// Returns true if this rectangle contains the point. Points are considered
// in the rectangle if they are on the left or top edge, but outside if they
// are on the right or bottom edge.
bool Contains(T aX, T aY) const
{
return x <= aX && aX < XMost() &&
y <= aY && aY < YMost();
}
// Returns true if this rectangle contains the point. Points are considered
// in the rectangle if they are on the left or top edge, but outside if they
// are on the right or bottom edge.
bool Contains(const Point& aPoint) const { return Contains(aPoint.x, aPoint.y); }
// Intersection. Returns TRUE if the receiver's area has non-empty
// intersection with aRect's area, and FALSE otherwise.
// Always returns false if aRect is empty or 'this' is empty.
bool Intersects(const Sub& aRect) const
{
return !IsEmpty() && !aRect.IsEmpty() &&
x < aRect.XMost() && aRect.x < XMost() &&
y < aRect.YMost() && aRect.y < YMost();
}
// Returns the rectangle containing the intersection of the points
// (including edges) of *this and aRect. If there are no points in that
// intersection, returns an empty rectangle with x/y set to the std::max of the x/y
// of *this and aRect.
MOZ_MUST_USE Sub Intersect(const Sub& aRect) const
{
Sub result;
result.x = std::max<T>(x, aRect.x);
result.y = std::max<T>(y, aRect.y);
result.width = std::min<T>(x - result.x + width, aRect.x - result.x + aRect.width);
result.height = std::min<T>(y - result.y + height, aRect.y - result.y + aRect.height);
if (result.width < 0 || result.height < 0) {
result.SizeTo(0, 0);
}
return result;
}
// Sets *this to be the rectangle containing the intersection of the points
// (including edges) of *this and aRect. If there are no points in that
// intersection, sets *this to be an empty rectangle with x/y set to the std::max
// of the x/y of *this and aRect.
//
// 'this' can be the same object as either aRect1 or aRect2
bool IntersectRect(const Sub& aRect1, const Sub& aRect2)
{
*static_cast<Sub*>(this) = aRect1.Intersect(aRect2);
return !IsEmpty();
}
// Returns the smallest rectangle that contains both the area of both
// this and aRect2.
// Thus, empty input rectangles are ignored.
// If both rectangles are empty, returns this.
// WARNING! This is not safe against overflow, prefer using SafeUnion instead
// when dealing with int-based rects.
MOZ_MUST_USE Sub Union(const Sub& aRect) const
{
if (IsEmpty()) {
return aRect;
} else if (aRect.IsEmpty()) {
return *static_cast<const Sub*>(this);
} else {
return UnionEdges(aRect);
}
}
// Returns the smallest rectangle that contains both the points (including
// edges) of both aRect1 and aRect2.
// Thus, empty input rectangles are allowed to affect the result.
// WARNING! This is not safe against overflow, prefer using SafeUnionEdges
// instead when dealing with int-based rects.
MOZ_MUST_USE Sub UnionEdges(const Sub& aRect) const
{
Sub result;
result.x = std::min(x, aRect.x);
result.y = std::min(y, aRect.y);
result.width = std::max(XMost(), aRect.XMost()) - result.x;
result.height = std::max(YMost(), aRect.YMost()) - result.y;
return result;
}
// Computes the smallest rectangle that contains both the area of both
// aRect1 and aRect2, and fills 'this' with the result.
// Thus, empty input rectangles are ignored.
// If both rectangles are empty, sets 'this' to aRect2.
//
// 'this' can be the same object as either aRect1 or aRect2
void UnionRect(const Sub& aRect1, const Sub& aRect2)
{
*static_cast<Sub*>(this) = aRect1.Union(aRect2);
}
// Computes the smallest rectangle that contains both the points (including
// edges) of both aRect1 and aRect2.
// Thus, empty input rectangles are allowed to affect the result.
//
// 'this' can be the same object as either aRect1 or aRect2
void UnionRectEdges(const Sub& aRect1, const Sub& aRect2)
{
*static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2);
}
// Expands the rect to include the point
void ExpandToEnclose(const Point& aPoint)
{
if (aPoint.x < x) {
width = XMost() - aPoint.x;
x = aPoint.x;
} else if (aPoint.x > XMost()) {
width = aPoint.x - x;
}
if (aPoint.y < y) {
height = YMost() - aPoint.y;
y = aPoint.y;
} else if (aPoint.y > YMost()) {
height = aPoint.y - y;
}
}
void SetRect(T aX, T aY, T aWidth, T aHeight)
{
x = aX; y = aY; width = aWidth; height = aHeight;
}
void SetRect(const Point& aPt, const SizeT& aSize)
{
SetRect(aPt.x, aPt.y, aSize.width, aSize.height);
}
void MoveTo(T aX, T aY) { x = aX; y = aY; }
void MoveTo(const Point& aPoint) { x = aPoint.x; y = aPoint.y; }
void MoveBy(T aDx, T aDy) { x += aDx; y += aDy; }
void MoveBy(const Point& aPoint) { x += aPoint.x; y += aPoint.y; }
void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; }
void SizeTo(const SizeT& aSize) { width = aSize.width; height = aSize.height; }
void Inflate(T aD) { Inflate(aD, aD); }
void Inflate(T aDx, T aDy)
{
x -= aDx;
y -= aDy;
width += 2 * aDx;
height += 2 * aDy;
}
void Inflate(const MarginT& aMargin)
{
x -= aMargin.left;
y -= aMargin.top;
width += aMargin.LeftRight();
height += aMargin.TopBottom();
}
void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); }
void Deflate(T aD) { Deflate(aD, aD); }
void Deflate(T aDx, T aDy)
{
x += aDx;
y += aDy;
width = std::max(T(0), width - 2 * aDx);
height = std::max(T(0), height - 2 * aDy);
}
void Deflate(const MarginT& aMargin)
{
x += aMargin.left;
y += aMargin.top;
width = std::max(T(0), width - aMargin.LeftRight());
height = std::max(T(0), height - aMargin.TopBottom());
}
void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); }
// Return true if the rectangles contain the same set of points, including
// points on the edges.
// Use when we care about the exact x/y/width/height values being
// equal (i.e. we care about differences in empty rectangles).
bool IsEqualEdges(const Sub& aRect) const
{
return x == aRect.x && y == aRect.y &&
width == aRect.width && height == aRect.height;
}
// Return true if the rectangles contain the same area of the plane.
// Use when we do not care about differences in empty rectangles.
bool IsEqualInterior(const Sub& aRect) const
{
return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty());
}
friend Sub operator+(Sub aSub, const Point& aPoint)
{
aSub += aPoint;
return aSub;
}
friend Sub operator-(Sub aSub, const Point& aPoint)
{
aSub -= aPoint;
return aSub;
}
friend Sub operator+(Sub aSub, const SizeT& aSize)
{
aSub += aSize;
return aSub;
}
friend Sub operator-(Sub aSub, const SizeT& aSize)
{
aSub -= aSize;
return aSub;
}
Sub& operator+=(const Point& aPoint)
{
MoveBy(aPoint);
return *static_cast<Sub*>(this);
}
Sub& operator-=(const Point& aPoint)
{
MoveBy(-aPoint);
return *static_cast<Sub*>(this);
}
Sub& operator+=(const SizeT& aSize)
{
width += aSize.width;
height += aSize.height;
return *static_cast<Sub*>(this);
}
Sub& operator-=(const SizeT& aSize)
{
width -= aSize.width;
height -= aSize.height;
return *static_cast<Sub*>(this);
}
// Find difference as a Margin
MarginT operator-(const Sub& aRect) const
{
return MarginT(aRect.y - y,
XMost() - aRect.XMost(),
YMost() - aRect.YMost(),
aRect.x - x);
}
// Helpers for accessing the vertices
Point TopLeft() const { return Point(x, y); }
Point TopRight() const { return Point(XMost(), y); }
Point BottomLeft() const { return Point(x, YMost()); }
Point BottomRight() const { return Point(XMost(), YMost()); }
Point AtCorner(int aCorner) const {
switch (aCorner) {
case RectCorner::TopLeft: return TopLeft();
case RectCorner::TopRight: return TopRight();
case RectCorner::BottomRight: return BottomRight();
case RectCorner::BottomLeft: return BottomLeft();
}
MOZ_CRASH("GFX: Incomplete switch");
}
Point CCWCorner(mozilla::Side side) const {
switch (side) {
case NS_SIDE_TOP: return TopLeft();
case NS_SIDE_RIGHT: return TopRight();
case NS_SIDE_BOTTOM: return BottomRight();
case NS_SIDE_LEFT: return BottomLeft();
}
MOZ_CRASH("GFX: Incomplete switch");
}
Point CWCorner(mozilla::Side side) const {
switch (side) {
case NS_SIDE_TOP: return TopRight();
case NS_SIDE_RIGHT: return BottomRight();
case NS_SIDE_BOTTOM: return BottomLeft();
case NS_SIDE_LEFT: return TopLeft();
}
MOZ_CRASH("GFX: Incomplete switch");
}
Point Center() const { return Point(x, y) + Point(width, height)/2; }
SizeT Size() const { return SizeT(width, height); }
T Area() const { return width * height; }
// Helper methods for computing the extents
T X() const { return x; }
T Y() const { return y; }
T Width() const { return width; }
T Height() const { return height; }
T XMost() const { return x + width; }
T YMost() const { return y + height; }
// Get the coordinate of the edge on the given side.
T Edge(mozilla::Side aSide) const
{
switch (aSide) {
case NS_SIDE_TOP: return Y();
case NS_SIDE_RIGHT: return XMost();
case NS_SIDE_BOTTOM: return YMost();
case NS_SIDE_LEFT: return X();
}
MOZ_CRASH("GFX: Incomplete switch");
}
// Moves one edge of the rect without moving the opposite edge.
void SetLeftEdge(T aX) {
MOZ_ASSERT(aX <= XMost());
width = XMost() - aX;
x = aX;
}
void SetRightEdge(T aXMost) {
MOZ_ASSERT(aXMost >= x);
width = aXMost - x;
}
void SetTopEdge(T aY) {
MOZ_ASSERT(aY <= YMost());
height = YMost() - aY;
y = aY;
}
void SetBottomEdge(T aYMost) {
MOZ_ASSERT(aYMost >= y);
height = aYMost - y;
}
// Round the rectangle edges to integer coordinates, such that the rounded
// rectangle has the same set of pixel centers as the original rectangle.
// Edges at offset 0.5 round up.
// Suitable for most places where integral device coordinates
// are needed, but note that any translation should be applied first to
// avoid pixel rounding errors.
// Note that this is *not* rounding to nearest integer if the values are negative.
// They are always rounding as floor(n + 0.5).
// See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14
// If you need similar method which is using NS_round(), you should create
// new |RoundAwayFromZero()| method.
void Round()
{
T x0 = static_cast<T>(floor(T(X()) + 0.5));
T y0 = static_cast<T>(floor(T(Y()) + 0.5));
T x1 = static_cast<T>(floor(T(XMost()) + 0.5));
T y1 = static_cast<T>(floor(T(YMost()) + 0.5));
x = x0;
y = y0;
width = x1 - x0;
height = y1 - y0;
}
// Snap the rectangle edges to integer coordinates, such that the
// original rectangle contains the resulting rectangle.
void RoundIn()
{
T x0 = static_cast<T>(ceil(T(X())));
T y0 = static_cast<T>(ceil(T(Y())));
T x1 = static_cast<T>(floor(T(XMost())));
T y1 = static_cast<T>(floor(T(YMost())));
x = x0;
y = y0;
width = x1 - x0;
height = y1 - y0;
}
// Snap the rectangle edges to integer coordinates, such that the
// resulting rectangle contains the original rectangle.
void RoundOut()
{
T x0 = static_cast<T>(floor(T(X())));
T y0 = static_cast<T>(floor(T(Y())));
T x1 = static_cast<T>(ceil(T(XMost())));
T y1 = static_cast<T>(ceil(T(YMost())));
x = x0;
y = y0;
width = x1 - x0;
height = y1 - y0;
}
// Scale 'this' by aScale without doing any rounding.
void Scale(T aScale) { Scale(aScale, aScale); }
// Scale 'this' by aXScale and aYScale, without doing any rounding.
void Scale(T aXScale, T aYScale)
{
T right = XMost() * aXScale;
T bottom = YMost() * aYScale;
x = x * aXScale;
y = y * aYScale;
width = right - x;
height = bottom - y;
}
// Scale 'this' by aScale, converting coordinates to integers so that the result is
// the smallest integer-coordinate rectangle containing the unrounded result.
// Note: this can turn an empty rectangle into a non-empty rectangle
void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); }
// Scale 'this' by aXScale and aYScale, converting coordinates to integers so
// that the result is the smallest integer-coordinate rectangle containing the
// unrounded result.
// Note: this can turn an empty rectangle into a non-empty rectangle
void ScaleRoundOut(double aXScale, double aYScale)
{
T right = static_cast<T>(ceil(double(XMost()) * aXScale));
T bottom = static_cast<T>(ceil(double(YMost()) * aYScale));
x = static_cast<T>(floor(double(x) * aXScale));
y = static_cast<T>(floor(double(y) * aYScale));
width = right - x;
height = bottom - y;
}
// Scale 'this' by aScale, converting coordinates to integers so that the result is
// the largest integer-coordinate rectangle contained by the unrounded result.
void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); }
// Scale 'this' by aXScale and aYScale, converting coordinates to integers so
// that the result is the largest integer-coordinate rectangle contained by the
// unrounded result.
void ScaleRoundIn(double aXScale, double aYScale)
{
T right = static_cast<T>(floor(double(XMost()) * aXScale));
T bottom = static_cast<T>(floor(double(YMost()) * aYScale));
x = static_cast<T>(ceil(double(x) * aXScale));
y = static_cast<T>(ceil(double(y) * aYScale));
width = std::max<T>(0, right - x);
height = std::max<T>(0, bottom - y);
}
// Scale 'this' by 1/aScale, converting coordinates to integers so that the result is
// the smallest integer-coordinate rectangle containing the unrounded result.
// Note: this can turn an empty rectangle into a non-empty rectangle
void ScaleInverseRoundOut(double aScale) { ScaleInverseRoundOut(aScale, aScale); }
// Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so
// that the result is the smallest integer-coordinate rectangle containing the
// unrounded result.
// Note: this can turn an empty rectangle into a non-empty rectangle
void ScaleInverseRoundOut(double aXScale, double aYScale)
{
T right = static_cast<T>(ceil(double(XMost()) / aXScale));
T bottom = static_cast<T>(ceil(double(YMost()) / aYScale));
x = static_cast<T>(floor(double(x) / aXScale));
y = static_cast<T>(floor(double(y) / aYScale));
width = right - x;
height = bottom - y;
}
// Scale 'this' by 1/aScale, converting coordinates to integers so that the result is
// the largest integer-coordinate rectangle contained by the unrounded result.
void ScaleInverseRoundIn(double aScale) { ScaleInverseRoundIn(aScale, aScale); }
// Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so
// that the result is the largest integer-coordinate rectangle contained by the
// unrounded result.
void ScaleInverseRoundIn(double aXScale, double aYScale)
{
T right = static_cast<T>(floor(double(XMost()) / aXScale));
T bottom = static_cast<T>(floor(double(YMost()) / aYScale));
x = static_cast<T>(ceil(double(x) / aXScale));
y = static_cast<T>(ceil(double(y) / aYScale));
width = std::max<T>(0, right - x);
height = std::max<T>(0, bottom - y);
}
/**
* Clamp aPoint to this rectangle. It is allowed to end up on any
* edge of the rectangle.
*/
MOZ_MUST_USE Point ClampPoint(const Point& aPoint) const
{
return Point(std::max(x, std::min(XMost(), aPoint.x)),
std::max(y, std::min(YMost(), aPoint.y)));
}
/**
* Translate this rectangle to be inside aRect. If it doesn't fit inside
* aRect then the dimensions that don't fit will be shrunk so that they
* do fit. The resulting rect is returned.
*/
MOZ_MUST_USE Sub MoveInsideAndClamp(const Sub& aRect) const
{
Sub rect(std::max(aRect.x, x),
std::max(aRect.y, y),
std::min(aRect.width, width),
std::min(aRect.height, height));
rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width;
rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height;
return rect;
}
// Returns the largest rectangle that can be represented with 32-bit
// signed integers, centered around a point at 0,0. As BaseRect's represent
// the dimensions as a top-left point with a width and height, the width
// and height will be the largest positive 32-bit value. The top-left
// position coordinate is divided by two to center the rectangle around a
// point at 0,0.
static Sub MaxIntRect()
{
return Sub(
static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
static_cast<T>(std::numeric_limits<int32_t>::max()),
static_cast<T>(std::numeric_limits<int32_t>::max())
);
};
friend std::ostream& operator<<(std::ostream& stream,
const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) {
return stream << '(' << aRect.x << ',' << aRect.y << ','
<< aRect.width << ',' << aRect.height << ')';
}
private:
// Do not use the default operator== or operator!= !
// Use IsEqualEdges or IsEqualInterior explicitly.
bool operator==(const Sub& aRect) const { return false; }
bool operator!=(const Sub& aRect) const { return false; }
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_BASERECT_H_ */

100
gfx/2d/BaseSize.h Normal file
View file

@ -0,0 +1,100 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_BASESIZE_H_
#define MOZILLA_GFX_BASESIZE_H_
#include "mozilla/Attributes.h"
namespace mozilla {
namespace gfx {
/**
* Do not use this class directly. Subclass it, pass that subclass as the
* Sub parameter, and only use that subclass. This allows methods to safely
* cast 'this' to 'Sub*'.
*/
template <class T, class Sub>
struct BaseSize {
union {
struct {
T width, height;
};
T components[2];
};
// Constructors
constexpr BaseSize() : width(0), height(0) {}
constexpr BaseSize(T aWidth, T aHeight) : width(aWidth), height(aHeight) {}
void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; }
bool IsEmpty() const {
return width <= 0 || height <= 0;
}
bool IsSquare() const {
return width == height;
}
// Note that '=' isn't defined so we'll get the
// compiler generated default assignment operator
bool operator==(const Sub& aSize) const {
return width == aSize.width && height == aSize.height;
}
bool operator!=(const Sub& aSize) const {
return width != aSize.width || height != aSize.height;
}
bool operator<=(const Sub& aSize) const {
return width <= aSize.width && height <= aSize.height;
}
bool operator<(const Sub& aSize) const {
return *this <= aSize && *this != aSize;
}
Sub operator+(const Sub& aSize) const {
return Sub(width + aSize.width, height + aSize.height);
}
Sub operator-(const Sub& aSize) const {
return Sub(width - aSize.width, height - aSize.height);
}
Sub& operator+=(const Sub& aSize) {
width += aSize.width;
height += aSize.height;
return *static_cast<Sub*>(this);
}
Sub& operator-=(const Sub& aSize) {
width -= aSize.width;
height -= aSize.height;
return *static_cast<Sub*>(this);
}
Sub operator*(T aScale) const {
return Sub(width * aScale, height * aScale);
}
Sub operator/(T aScale) const {
return Sub(width / aScale, height / aScale);
}
friend Sub operator*(T aScale, const Sub& aSize) {
return Sub(aScale * aSize.width, aScale * aSize.height);
}
void Scale(T aXScale, T aYScale) {
width *= aXScale;
height *= aYScale;
}
Sub operator*(const Sub& aSize) const {
return Sub(width * aSize.width, height * aSize.height);
}
Sub operator/(const Sub& aSize) const {
return Sub(width / aSize.width, height / aSize.height);
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_BASESIZE_H_ */

339
gfx/2d/BezierUtils.cpp Normal file
View file

@ -0,0 +1,339 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:cindent:ts=2:et:sw=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 "BezierUtils.h"
#include "PathHelpers.h"
namespace mozilla {
namespace gfx {
Point
GetBezierPoint(const Bezier& aBezier, Float t)
{
Float s = 1.0f - t;
return Point(
aBezier.mPoints[0].x * s * s * s +
3.0f * aBezier.mPoints[1].x * t * s * s +
3.0f * aBezier.mPoints[2].x * t * t * s +
aBezier.mPoints[3].x * t * t * t,
aBezier.mPoints[0].y * s * s * s +
3.0f * aBezier.mPoints[1].y * t * s * s +
3.0f * aBezier.mPoints[2].y * t * t * s +
aBezier.mPoints[3].y * t * t * t
);
}
Point
GetBezierDifferential(const Bezier& aBezier, Float t)
{
// Return P'(t).
Float s = 1.0f - t;
return Point(
-3.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s * s +
2.0f * (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * t * s +
(aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t * t),
-3.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s * s +
2.0f * (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * t * s+
(aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t * t)
);
}
Point
GetBezierDifferential2(const Bezier& aBezier, Float t)
{
// Return P''(t).
Float s = 1.0f - t;
return Point(
6.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s -
(aBezier.mPoints[1].x - aBezier.mPoints[2].x) * (s - t) -
(aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t),
6.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s -
(aBezier.mPoints[1].y - aBezier.mPoints[2].y) * (s - t) -
(aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t)
);
}
Float
GetBezierLength(const Bezier& aBezier, Float a, Float b)
{
if (a < 0.5f && b > 0.5f) {
// To increase the accuracy, split into two parts.
return GetBezierLength(aBezier, a, 0.5f) +
GetBezierLength(aBezier, 0.5f, b);
}
// Calculate length of simple bezier curve with Simpson's rule.
// _
// / b
// length = | |P'(x)| dx
// _/ a
//
// b - a a + b
// = ----- [ |P'(a)| + 4 |P'(-----)| + |P'(b)| ]
// 6 2
Float fa = GetBezierDifferential(aBezier, a).Length();
Float fab = GetBezierDifferential(aBezier, (a + b) / 2.0f).Length();
Float fb = GetBezierDifferential(aBezier, b).Length();
return (b - a) / 6.0f * (fa + 4.0f * fab + fb);
}
static void
SplitBezierA(Bezier* aSubBezier, const Bezier& aBezier, Float t)
{
// Split bezier curve into [0,t] and [t,1] parts, and return [0,t] part.
Float s = 1.0f - t;
Point tmp1;
Point tmp2;
aSubBezier->mPoints[0] = aBezier.mPoints[0];
aSubBezier->mPoints[1] = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t;
tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t;
tmp2 = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t;
aSubBezier->mPoints[2] = aSubBezier->mPoints[1] * s + tmp1 * t;
tmp1 = tmp1 * s + tmp2 * t;
aSubBezier->mPoints[3] = aSubBezier->mPoints[2] * s + tmp1 * t;
}
static void
SplitBezierB(Bezier* aSubBezier, const Bezier& aBezier, Float t)
{
// Split bezier curve into [0,t] and [t,1] parts, and return [t,1] part.
Float s = 1.0f - t;
Point tmp1;
Point tmp2;
aSubBezier->mPoints[3] = aBezier.mPoints[3];
aSubBezier->mPoints[2] = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t;
tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t;
tmp2 = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t;
aSubBezier->mPoints[1] = tmp1 * s + aSubBezier->mPoints[2] * t;
tmp1 = tmp2 * s + tmp1 * t;
aSubBezier->mPoints[0] = tmp1 * s + aSubBezier->mPoints[1] * t;
}
void
GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1, Float t2)
{
Bezier tmp;
SplitBezierB(&tmp, aBezier, t1);
Float range = 1.0f - t1;
if (range == 0.0f) {
*aSubBezier = tmp;
} else {
SplitBezierA(aSubBezier, tmp, (t2 - t1) / range);
}
}
static Point
BisectBezierNearestPoint(const Bezier& aBezier, const Point& aTarget,
Float* aT)
{
// Find a nearest point on bezier curve with Binary search.
// Called from FindBezierNearestPoint.
Float lower = 0.0f;
Float upper = 1.0f;
Float t;
Point P, lastP;
const size_t MAX_LOOP = 32;
const Float DIST_MARGIN = 0.1f;
const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN;
const Float DIFF = 0.0001f;
for (size_t i = 0; i < MAX_LOOP; i++) {
t = (upper + lower) / 2.0f;
P = GetBezierPoint(aBezier, t);
// Check if it converged.
if (i > 0 && (lastP - P).LengthSquare() < DIST_MARGIN_SQUARE) {
break;
}
Float distSquare = (P - aTarget).LengthSquare();
if ((GetBezierPoint(aBezier, t + DIFF) - aTarget).LengthSquare() <
distSquare) {
lower = t;
} else if ((GetBezierPoint(aBezier, t - DIFF) - aTarget).LengthSquare() <
distSquare) {
upper = t;
} else {
break;
}
lastP = P;
}
if (aT) {
*aT = t;
}
return P;
}
Point
FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget,
Float aInitialT, Float* aT)
{
// Find a nearest point on bezier curve with Newton's method.
// It converges within 4 iterations in most cases.
//
// f(t_n)
// t_{n+1} = t_n - ---------
// f'(t_n)
//
// d 2
// f(t) = ---- | P(t) - aTarget |
// dt
Float t = aInitialT;
Point P;
Point lastP = GetBezierPoint(aBezier, t);
const size_t MAX_LOOP = 4;
const Float DIST_MARGIN = 0.1f;
const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN;
for (size_t i = 0; i <= MAX_LOOP; i++) {
Point dP = GetBezierDifferential(aBezier, t);
Point ddP = GetBezierDifferential2(aBezier, t);
Float f = 2.0f * (lastP.DotProduct(dP) - aTarget.DotProduct(dP));
Float df = 2.0f * (dP.DotProduct(dP) + lastP.DotProduct(ddP) -
aTarget.DotProduct(ddP));
t = t - f / df;
P = GetBezierPoint(aBezier, t);
if ((P - lastP).LengthSquare() < DIST_MARGIN_SQUARE) {
break;
}
lastP = P;
if (i == MAX_LOOP) {
// If aInitialT is too bad, it won't converge in a few iterations,
// fallback to binary search.
return BisectBezierNearestPoint(aBezier, aTarget, aT);
}
}
if (aT) {
*aT = t;
}
return P;
}
void
GetBezierPointsForCorner(Bezier* aBezier, mozilla::css::Corner aCorner,
const Point& aCornerPoint, const Size& aCornerSize)
{
// Calculate bezier control points for elliptic arc.
const Float signsList[4][2] = {
{ +1.0f, +1.0f },
{ -1.0f, +1.0f },
{ -1.0f, -1.0f },
{ +1.0f, -1.0f }
};
const Float (& signs)[2] = signsList[aCorner];
aBezier->mPoints[0] = aCornerPoint;
aBezier->mPoints[0].x += signs[0] * aCornerSize.width;
aBezier->mPoints[1] = aBezier->mPoints[0];
aBezier->mPoints[1].x -= signs[0] * aCornerSize.width * kKappaFactor;
aBezier->mPoints[3] = aCornerPoint;
aBezier->mPoints[3].y += signs[1] * aCornerSize.height;
aBezier->mPoints[2] = aBezier->mPoints[3];
aBezier->mPoints[2].y -= signs[1] * aCornerSize.height * kKappaFactor;
}
Float
GetQuarterEllipticArcLength(Float a, Float b)
{
// Calculate the approximate length of a quarter elliptic arc formed by radii
// (a, b), by Ramanujan's approximation of the perimeter p of an ellipse.
// _ _
// | 2 |
// | 3 * (a - b) |
// p = PI | (a + b) + ------------------------------------------- |
// | 2 2 |
// |_ 10 * (a + b) + sqrt(a + 14 * a * b + b ) _|
//
// _ _
// | 2 |
// | 3 * (a - b) |
// = PI | (a + b) + -------------------------------------------------- |
// | 2 2 |
// |_ 10 * (a + b) + sqrt(4 * (a + b) - 3 * (a - b) ) _|
//
// _ _
// | 2 |
// | 3 * S |
// = PI | A + -------------------------------------- |
// | 2 2 |
// |_ 10 * A + sqrt(4 * A - 3 * S ) _|
//
// where A = a + b, S = a - b
Float A = a + b, S = a - b;
Float A2 = A * A, S2 = S * S;
Float p = M_PI * (A + 3.0f * S2 / (10.0f * A + sqrt(4.0f * A2 - 3.0f * S2)));
return p / 4.0f;
}
Float
CalculateDistanceToEllipticArc(const Point& P, const Point& normal,
const Point& origin, Float width, Float height)
{
// Solve following equations with n and return smaller n.
//
// / (x, y) = P + n * normal
// |
// < _ _ 2 _ _ 2
// | | x - origin.x | | y - origin.y |
// | | ------------ | + | ------------ | = 1
// \ |_ width _| |_ height _|
Float a = (P.x - origin.x) / width;
Float b = normal.x / width;
Float c = (P.y - origin.y) / height;
Float d = normal.y / height;
Float A = b * b + d * d;
Float B = a * b + c * d;
Float C = a * a + c * c - 1;
Float S = sqrt(B * B - A * C);
Float n1 = (- B + S) / A;
Float n2 = (- B - S) / A;
MOZ_ASSERT(n1 >= 0);
MOZ_ASSERT(n2 >= 0);
return n1 < n2 ? n1 : n2;
}
} // namespace gfx
} // namespace mozilla

185
gfx/2d/BezierUtils.h Normal file
View file

@ -0,0 +1,185 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef mozilla_BezierUtils_h_
#define mozilla_BezierUtils_h_
#include "mozilla/gfx/2D.h"
#include "gfxRect.h"
namespace mozilla {
namespace gfx {
// Control points for bezier curve
//
// mPoints[2]
// +-----___---+ mPoints[3]
// __--
// _--
// /
// /
// mPoints[1] + |
// | |
// ||
// ||
// |
// |
// |
// |
// mPoints[0] +
struct Bezier {
Point mPoints[4];
};
// Calculate a point or it's differential of a bezier curve formed by
// aBezier and parameter t.
//
// GetBezierPoint = P(t)
// GetBezierDifferential = P'(t)
// GetBezierDifferential2 = P''(t)
//
// mPoints[2]
// +-----___---+ mPoints[3]
// __-- P(1)
// _--
// +
// / P(t)
// mPoints[1] + |
// | |
// ||
// ||
// |
// |
// |
// |
// mPoints[0] + P(0)
Point GetBezierPoint(const Bezier& aBezier, Float t);
Point GetBezierDifferential(const Bezier& aBezier, Float t);
Point GetBezierDifferential2(const Bezier& aBezier, Float t);
// Calculate length of a simple bezier curve formed by aBezier and range [a, b].
Float GetBezierLength(const Bezier& aBezier, Float a, Float b);
// Split bezier curve formed by aBezier into [0,t1], [t1,t2], [t2,1] parts, and
// stores control points for [t1,t2] to aSubBezier.
//
// ___---+
// __+- P(1)
// _-- P(t2)
// -
// / <-- aSubBezier
// |
// |
// +
// | P(t1)
// |
// |
// |
// |
// + P(0)
void GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier,
Float t1, Float t2);
// Find a nearest point on bezier curve formed by aBezier to a point aTarget.
// aInitialT is a hint to find the parameter t for the nearest point.
// If aT is non-null, parameter for the nearest point is stored to *aT.
// This function expects a bezier curve to be an approximation of elliptic arc.
// Otherwise it will return wrong point.
//
// aTarget
// + ___---+
// __--
// _--
// +
// / nearest point = P(t = *aT)
// |
// |
// |
// + P(aInitialT)
// |
// |
// |
// |
// +
Point FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget,
Float aInitialT, Float* aT=nullptr);
// Calculate control points for a bezier curve that is an approximation of
// an elliptic arc.
//
// aCornerSize.width
// |<----------------->|
// | |
// aCornerPoint| mPoints[2] |
// -------------+-------+-----___---+ mPoints[3]
// ^ | __--
// | | _--
// | | -
// | | /
// aCornerSize.height | mPoints[1] + |
// | | |
// | ||
// | ||
// | |
// | |
// | |
// v mPoints[0] |
// -------------+
void GetBezierPointsForCorner(Bezier* aBezier, mozilla::css::Corner aCorner,
const Point& aCornerPoint,
const Size& aCornerSize);
// Calculate the approximate length of a quarter elliptic arc formed by radii
// (a, b).
//
// a
// |<----------------->|
// | |
// ---+-------------___---+
// ^ | __--
// | | _--
// | | -
// | | /
// b | | |
// | | |
// | ||
// | ||
// | |
// | |
// | |
// v |
// ---+
Float GetQuarterEllipticArcLength(Float a, Float b);
// Calculate the distance between an elliptic arc formed by (origin, width,
// height), and a point P, along a line formed by |P + n * normal|.
// P should be outside of the ellipse, and the line should cross with the
// ellipse twice at n > 0 points.
//
// width
// |<----------------->|
// origin | |
// -----------+-------------___---+
// ^ normal | __--
// | P +->__ | _--
// | --__ -
// | | --+
// height | | |
// | | |
// | ||
// | ||
// | |
// | |
// | |
// v |
// -----------+
Float CalculateDistanceToEllipticArc(const Point& P, const Point& normal,
const Point& origin,
Float width, Float height);
} // namespace gfx
} // namespace mozilla
#endif /* mozilla_BezierUtils_h_ */

80
gfx/2d/BigEndianInts.h Normal file
View file

@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_BigEndianInts_h
#define mozilla_BigEndianInts_h
#include "mozilla/EndianUtils.h"
namespace mozilla {
#pragma pack(push, 1)
struct BigEndianUint16
{
#ifdef __SUNPRO_CC
BigEndianUint16& operator=(const uint16_t aValue)
{
value = NativeEndian::swapToBigEndian(aValue);
return *this;
}
#else
MOZ_IMPLICIT BigEndianUint16(const uint16_t aValue)
{
value = NativeEndian::swapToBigEndian(aValue);
}
#endif
operator uint16_t() const
{
return NativeEndian::swapFromBigEndian(value);
}
friend inline bool
operator==(const BigEndianUint16& lhs, const BigEndianUint16& rhs)
{
return lhs.value == rhs.value;
}
friend inline bool
operator!=(const BigEndianUint16& lhs, const BigEndianUint16& rhs)
{
return !(lhs == rhs);
}
private:
uint16_t value;
};
struct BigEndianUint32
{
#ifdef __SUNPRO_CC
BigEndianUint32& operator=(const uint32_t aValue)
{
value = NativeEndian::swapToBigEndian(aValue);
return *this;
}
#else
MOZ_IMPLICIT BigEndianUint32(const uint32_t aValue)
{
value = NativeEndian::swapToBigEndian(aValue);
}
#endif
operator uint32_t() const
{
return NativeEndian::swapFromBigEndian(value);
}
private:
uint32_t value;
};
#pragma pack(pop)
} // mozilla
#endif // mozilla_BigEndianInts_h

770
gfx/2d/Blur.cpp Normal file
View file

@ -0,0 +1,770 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "Blur.h"
#include <algorithm>
#include <math.h>
#include <string.h>
#include "mozilla/CheckedInt.h"
#include "2D.h"
#include "DataSurfaceHelpers.h"
#include "Tools.h"
#ifdef BUILD_ARM_NEON
#include "mozilla/arm.h"
#endif
using namespace std;
namespace mozilla {
namespace gfx {
/**
* Box blur involves looking at one pixel, and setting its value to the average
* of its neighbouring pixels.
* @param aInput The input buffer.
* @param aOutput The output buffer.
* @param aLeftLobe The number of pixels to blend on the left.
* @param aRightLobe The number of pixels to blend on the right.
* @param aWidth The number of columns in the buffers.
* @param aRows The number of rows in the buffers.
* @param aSkipRect An area to skip blurring in.
* XXX shouldn't we pass stride in separately here?
*/
static void
BoxBlurHorizontal(unsigned char* aInput,
unsigned char* aOutput,
int32_t aLeftLobe,
int32_t aRightLobe,
int32_t aWidth,
int32_t aRows,
const IntRect& aSkipRect)
{
MOZ_ASSERT(aWidth > 0);
int32_t boxSize = aLeftLobe + aRightLobe + 1;
bool skipRectCoversWholeRow = 0 >= aSkipRect.x &&
aWidth <= aSkipRect.XMost();
if (boxSize == 1) {
memcpy(aOutput, aInput, aWidth*aRows);
return;
}
uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize);
for (int32_t y = 0; y < aRows; y++) {
// Check whether the skip rect intersects this row. If the skip
// rect covers the whole surface in this row, we can avoid
// this row entirely (and any others along the skip rect).
bool inSkipRectY = y >= aSkipRect.y &&
y < aSkipRect.YMost();
if (inSkipRectY && skipRectCoversWholeRow) {
y = aSkipRect.YMost() - 1;
continue;
}
uint32_t alphaSum = 0;
for (int32_t i = 0; i < boxSize; i++) {
int32_t pos = i - aLeftLobe;
// See assertion above; if aWidth is zero, then we would have no
// valid position to clamp to.
pos = max(pos, 0);
pos = min(pos, aWidth - 1);
alphaSum += aInput[aWidth * y + pos];
}
for (int32_t x = 0; x < aWidth; x++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectY && x >= aSkipRect.x &&
x < aSkipRect.XMost()) {
x = aSkipRect.XMost();
if (x >= aWidth)
break;
// Recalculate the neighbouring alpha values for
// our new point on the surface.
alphaSum = 0;
for (int32_t i = 0; i < boxSize; i++) {
int32_t pos = x + i - aLeftLobe;
// See assertion above; if aWidth is zero, then we would have no
// valid position to clamp to.
pos = max(pos, 0);
pos = min(pos, aWidth - 1);
alphaSum += aInput[aWidth * y + pos];
}
}
int32_t tmp = x - aLeftLobe;
int32_t last = max(tmp, 0);
int32_t next = min(tmp + boxSize, aWidth - 1);
aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32;
alphaSum += aInput[aWidth * y + next] -
aInput[aWidth * y + last];
}
}
}
/**
* Identical to BoxBlurHorizontal, except it blurs top and bottom instead of
* left and right.
* XXX shouldn't we pass stride in separately here?
*/
static void
BoxBlurVertical(unsigned char* aInput,
unsigned char* aOutput,
int32_t aTopLobe,
int32_t aBottomLobe,
int32_t aWidth,
int32_t aRows,
const IntRect& aSkipRect)
{
MOZ_ASSERT(aRows > 0);
int32_t boxSize = aTopLobe + aBottomLobe + 1;
bool skipRectCoversWholeColumn = 0 >= aSkipRect.y &&
aRows <= aSkipRect.YMost();
if (boxSize == 1) {
memcpy(aOutput, aInput, aWidth*aRows);
return;
}
uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize);
for (int32_t x = 0; x < aWidth; x++) {
bool inSkipRectX = x >= aSkipRect.x &&
x < aSkipRect.XMost();
if (inSkipRectX && skipRectCoversWholeColumn) {
x = aSkipRect.XMost() - 1;
continue;
}
uint32_t alphaSum = 0;
for (int32_t i = 0; i < boxSize; i++) {
int32_t pos = i - aTopLobe;
// See assertion above; if aRows is zero, then we would have no
// valid position to clamp to.
pos = max(pos, 0);
pos = min(pos, aRows - 1);
alphaSum += aInput[aWidth * pos + x];
}
for (int32_t y = 0; y < aRows; y++) {
if (inSkipRectX && y >= aSkipRect.y &&
y < aSkipRect.YMost()) {
y = aSkipRect.YMost();
if (y >= aRows)
break;
alphaSum = 0;
for (int32_t i = 0; i < boxSize; i++) {
int32_t pos = y + i - aTopLobe;
// See assertion above; if aRows is zero, then we would have no
// valid position to clamp to.
pos = max(pos, 0);
pos = min(pos, aRows - 1);
alphaSum += aInput[aWidth * pos + x];
}
}
int32_t tmp = y - aTopLobe;
int32_t last = max(tmp, 0);
int32_t next = min(tmp + boxSize, aRows - 1);
aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32;
alphaSum += aInput[aWidth * next + x] -
aInput[aWidth * last + x];
}
}
}
static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2])
{
int32_t major, minor, final;
/* See http://www.w3.org/TR/SVG/filters.html#feGaussianBlur for
* some notes about approximating the Gaussian blur with box-blurs.
* The comments below are in the terminology of that page.
*/
int32_t z = aRadius / 3;
switch (aRadius % 3) {
case 0:
// aRadius = z*3; choose d = 2*z + 1
major = minor = final = z;
break;
case 1:
// aRadius = z*3 + 1
// This is a tricky case since there is no value of d which will
// yield a radius of exactly aRadius. If d is odd, i.e. d=2*k + 1
// for some integer k, then the radius will be 3*k. If d is even,
// i.e. d=2*k, then the radius will be 3*k - 1.
// So we have to choose values that don't match the standard
// algorithm.
major = z + 1;
minor = final = z;
break;
case 2:
// aRadius = z*3 + 2; choose d = 2*z + 2
major = final = z + 1;
minor = z;
break;
default:
// Mathematical impossibility!
MOZ_ASSERT(false);
major = minor = final = 0;
}
MOZ_ASSERT(major + minor + final == aRadius);
aLobes[0][0] = major;
aLobes[0][1] = minor;
aLobes[1][0] = minor;
aLobes[1][1] = major;
aLobes[2][0] = final;
aLobes[2][1] = final;
}
static void
SpreadHorizontal(unsigned char* aInput,
unsigned char* aOutput,
int32_t aRadius,
int32_t aWidth,
int32_t aRows,
int32_t aStride,
const IntRect& aSkipRect)
{
if (aRadius == 0) {
memcpy(aOutput, aInput, aStride * aRows);
return;
}
bool skipRectCoversWholeRow = 0 >= aSkipRect.x &&
aWidth <= aSkipRect.XMost();
for (int32_t y = 0; y < aRows; y++) {
// Check whether the skip rect intersects this row. If the skip
// rect covers the whole surface in this row, we can avoid
// this row entirely (and any others along the skip rect).
bool inSkipRectY = y >= aSkipRect.y &&
y < aSkipRect.YMost();
if (inSkipRectY && skipRectCoversWholeRow) {
y = aSkipRect.YMost() - 1;
continue;
}
for (int32_t x = 0; x < aWidth; x++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectY && x >= aSkipRect.x &&
x < aSkipRect.XMost()) {
x = aSkipRect.XMost();
if (x >= aWidth)
break;
}
int32_t sMin = max(x - aRadius, 0);
int32_t sMax = min(x + aRadius, aWidth - 1);
int32_t v = 0;
for (int32_t s = sMin; s <= sMax; ++s) {
v = max<int32_t>(v, aInput[aStride * y + s]);
}
aOutput[aStride * y + x] = v;
}
}
}
static void
SpreadVertical(unsigned char* aInput,
unsigned char* aOutput,
int32_t aRadius,
int32_t aWidth,
int32_t aRows,
int32_t aStride,
const IntRect& aSkipRect)
{
if (aRadius == 0) {
memcpy(aOutput, aInput, aStride * aRows);
return;
}
bool skipRectCoversWholeColumn = 0 >= aSkipRect.y &&
aRows <= aSkipRect.YMost();
for (int32_t x = 0; x < aWidth; x++) {
bool inSkipRectX = x >= aSkipRect.x &&
x < aSkipRect.XMost();
if (inSkipRectX && skipRectCoversWholeColumn) {
x = aSkipRect.XMost() - 1;
continue;
}
for (int32_t y = 0; y < aRows; y++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectX && y >= aSkipRect.y &&
y < aSkipRect.YMost()) {
y = aSkipRect.YMost();
if (y >= aRows)
break;
}
int32_t sMin = max(y - aRadius, 0);
int32_t sMax = min(y + aRadius, aRows - 1);
int32_t v = 0;
for (int32_t s = sMin; s <= sMax; ++s) {
v = max<int32_t>(v, aInput[aStride * s + x]);
}
aOutput[aStride * y + x] = v;
}
}
}
CheckedInt<int32_t>
AlphaBoxBlur::RoundUpToMultipleOf4(int32_t aVal)
{
CheckedInt<int32_t> val(aVal);
val += 3;
val /= 4;
val *= 4;
return val;
}
AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect,
const IntSize& aSpreadRadius,
const IntSize& aBlurRadius,
const Rect* aDirtyRect,
const Rect* aSkipRect)
: mSpreadRadius(aSpreadRadius),
mBlurRadius(aBlurRadius),
mSurfaceAllocationSize(0)
{
Rect rect(aRect);
rect.Inflate(Size(aBlurRadius + aSpreadRadius));
rect.RoundOut();
if (aDirtyRect) {
// If we get passed a dirty rect from layout, we can minimize the
// shadow size and make painting faster.
mHasDirtyRect = true;
mDirtyRect = *aDirtyRect;
Rect requiredBlurArea = mDirtyRect.Intersect(rect);
requiredBlurArea.Inflate(Size(aBlurRadius + aSpreadRadius));
rect = requiredBlurArea.Intersect(rect);
} else {
mHasDirtyRect = false;
}
mRect = IntRect(int32_t(rect.x), int32_t(rect.y),
int32_t(rect.width), int32_t(rect.height));
if (mRect.IsEmpty()) {
return;
}
if (aSkipRect) {
// If we get passed a skip rect, we can lower the amount of
// blurring/spreading we need to do. We convert it to IntRect to avoid
// expensive int<->float conversions if we were to use Rect instead.
Rect skipRect = *aSkipRect;
skipRect.RoundIn();
skipRect.Deflate(Size(aBlurRadius + aSpreadRadius));
mSkipRect = IntRect(int32_t(skipRect.x), int32_t(skipRect.y),
int32_t(skipRect.width), int32_t(skipRect.height));
mSkipRect = mSkipRect.Intersect(mRect);
if (mSkipRect.IsEqualInterior(mRect))
return;
mSkipRect -= mRect.TopLeft();
} else {
mSkipRect = IntRect(0, 0, 0, 0);
}
CheckedInt<int32_t> stride = RoundUpToMultipleOf4(mRect.width);
if (stride.isValid()) {
mStride = stride.value();
// We need to leave room for an additional 3 bytes for a potential overrun
// in our blurring code.
size_t size = BufferSizeFromStrideAndHeight(mStride, mRect.height, 3);
if (size != 0) {
mSurfaceAllocationSize = size;
}
}
}
AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect,
int32_t aStride,
float aSigmaX,
float aSigmaY)
: mRect(int32_t(aRect.x), int32_t(aRect.y),
int32_t(aRect.width), int32_t(aRect.height)),
mSpreadRadius(),
mBlurRadius(CalculateBlurRadius(Point(aSigmaX, aSigmaY))),
mStride(aStride),
mSurfaceAllocationSize(0)
{
IntRect intRect;
if (aRect.ToIntRect(&intRect)) {
size_t minDataSize = BufferSizeFromStrideAndHeight(intRect.width, intRect.height);
if (minDataSize != 0) {
mSurfaceAllocationSize = minDataSize;
}
}
}
AlphaBoxBlur::~AlphaBoxBlur()
{
}
IntSize
AlphaBoxBlur::GetSize()
{
IntSize size(mRect.width, mRect.height);
return size;
}
int32_t
AlphaBoxBlur::GetStride()
{
return mStride;
}
IntRect
AlphaBoxBlur::GetRect()
{
return mRect;
}
Rect*
AlphaBoxBlur::GetDirtyRect()
{
if (mHasDirtyRect) {
return &mDirtyRect;
}
return nullptr;
}
size_t
AlphaBoxBlur::GetSurfaceAllocationSize() const
{
return mSurfaceAllocationSize;
}
void
AlphaBoxBlur::Blur(uint8_t* aData)
{
if (!aData) {
return;
}
// no need to do all this if not blurring or spreading
if (mBlurRadius != IntSize(0,0) || mSpreadRadius != IntSize(0,0)) {
int32_t stride = GetStride();
IntSize size = GetSize();
if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) {
// No need to use CheckedInt here - we have validated it in the constructor.
size_t szB = stride * size.height;
unsigned char* tmpData = new (std::nothrow) uint8_t[szB];
if (!tmpData) {
return;
}
memset(tmpData, 0, szB);
SpreadHorizontal(aData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect);
SpreadVertical(tmpData, aData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect);
delete [] tmpData;
}
int32_t horizontalLobes[3][2];
ComputeLobes(mBlurRadius.width, horizontalLobes);
int32_t verticalLobes[3][2];
ComputeLobes(mBlurRadius.height, verticalLobes);
// We want to allow for some extra space on the left for alignment reasons.
int32_t maxLeftLobe = RoundUpToMultipleOf4(horizontalLobes[0][0] + 1).value();
IntSize integralImageSize(size.width + maxLeftLobe + horizontalLobes[1][1],
size.height + verticalLobes[0][0] + verticalLobes[1][1] + 1);
if ((integralImageSize.width * integralImageSize.height) > (1 << 24)) {
// Fallback to old blurring code when the surface is so large it may
// overflow our integral image!
// No need to use CheckedInt here - we have validated it in the constructor.
size_t szB = stride * size.height;
uint8_t* tmpData = new (std::nothrow) uint8_t[szB];
if (!tmpData) {
return;
}
memset(tmpData, 0, szB);
uint8_t* a = aData;
uint8_t* b = tmpData;
if (mBlurRadius.width > 0) {
BoxBlurHorizontal(a, b, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect);
BoxBlurHorizontal(b, a, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect);
BoxBlurHorizontal(a, b, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect);
} else {
a = tmpData;
b = aData;
}
// The result is in 'b' here.
if (mBlurRadius.height > 0) {
BoxBlurVertical(b, a, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect);
BoxBlurVertical(a, b, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect);
BoxBlurVertical(b, a, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect);
} else {
a = b;
}
// The result is in 'a' here.
if (a == tmpData) {
memcpy(aData, tmpData, szB);
}
delete [] tmpData;
} else {
size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width, 4);
if (integralImageStride == 0) {
return;
}
// We need to leave room for an additional 12 bytes for a maximum overrun
// of 3 pixels in the blurring code.
size_t bufLen = BufferSizeFromStrideAndHeight(integralImageStride, integralImageSize.height, 12);
if (bufLen == 0) {
return;
}
// bufLen is a byte count, but here we want a multiple of 32-bit ints, so
// we divide by 4.
AlignedArray<uint32_t> integralImage((bufLen / 4) + ((bufLen % 4) ? 1 : 0));
if (!integralImage) {
return;
}
#ifdef USE_SSE2
if (Factory::HasSSE2()) {
BoxBlur_SSE2(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0],
verticalLobes[0][1], integralImage, integralImageStride);
BoxBlur_SSE2(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0],
verticalLobes[1][1], integralImage, integralImageStride);
BoxBlur_SSE2(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0],
verticalLobes[2][1], integralImage, integralImageStride);
} else
#endif
#ifdef BUILD_ARM_NEON
if (mozilla::supports_neon()) {
BoxBlur_NEON(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0],
verticalLobes[0][1], integralImage, integralImageStride);
BoxBlur_NEON(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0],
verticalLobes[1][1], integralImage, integralImageStride);
BoxBlur_NEON(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0],
verticalLobes[2][1], integralImage, integralImageStride);
} else
#endif
{
#ifdef _MIPS_ARCH_LOONGSON3A
BoxBlur_LS3(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0],
verticalLobes[0][1], integralImage, integralImageStride);
BoxBlur_LS3(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0],
verticalLobes[1][1], integralImage, integralImageStride);
BoxBlur_LS3(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0],
verticalLobes[2][1], integralImage, integralImageStride);
#else
BoxBlur_C(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0],
verticalLobes[0][1], integralImage, integralImageStride);
BoxBlur_C(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0],
verticalLobes[1][1], integralImage, integralImageStride);
BoxBlur_C(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0],
verticalLobes[2][1], integralImage, integralImageStride);
#endif
}
}
}
}
MOZ_ALWAYS_INLINE void
GenerateIntegralRow(uint32_t *aDest, const uint8_t *aSource, uint32_t *aPreviousRow,
const uint32_t &aSourceWidth, const uint32_t &aLeftInflation, const uint32_t &aRightInflation)
{
uint32_t currentRowSum = 0;
uint32_t pixel = aSource[0];
for (uint32_t x = 0; x < aLeftInflation; x++) {
currentRowSum += pixel;
*aDest++ = currentRowSum + *aPreviousRow++;
}
for (uint32_t x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x += 4) {
uint32_t alphaValues = *(uint32_t*)(aSource + (x - aLeftInflation));
#if defined WORDS_BIGENDIAN || defined IS_BIG_ENDIAN || defined __BIG_ENDIAN__
currentRowSum += (alphaValues >> 24) & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
currentRowSum += (alphaValues >> 16) & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
currentRowSum += (alphaValues >> 8) & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
currentRowSum += alphaValues & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
#else
currentRowSum += alphaValues & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
alphaValues >>= 8;
currentRowSum += alphaValues & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
alphaValues >>= 8;
currentRowSum += alphaValues & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
alphaValues >>= 8;
currentRowSum += alphaValues & 0xff;
*aDest++ = *aPreviousRow++ + currentRowSum;
#endif
}
pixel = aSource[aSourceWidth - 1];
for (uint32_t x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) {
currentRowSum += pixel;
*aDest++ = currentRowSum + *aPreviousRow++;
}
}
MOZ_ALWAYS_INLINE void
GenerateIntegralImage_C(int32_t aLeftInflation, int32_t aRightInflation,
int32_t aTopInflation, int32_t aBottomInflation,
uint32_t *aIntegralImage, size_t aIntegralImageStride,
uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize)
{
uint32_t stride32bit = aIntegralImageStride / 4;
IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation,
aSize.height + aTopInflation + aBottomInflation);
memset(aIntegralImage, 0, aIntegralImageStride);
GenerateIntegralRow(aIntegralImage, aSource, aIntegralImage,
aSize.width, aLeftInflation, aRightInflation);
for (int y = 1; y < aTopInflation + 1; y++) {
GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource, aIntegralImage + (y - 1) * stride32bit,
aSize.width, aLeftInflation, aRightInflation);
}
for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) {
GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + aSourceStride * (y - aTopInflation),
aIntegralImage + (y - 1) * stride32bit, aSize.width, aLeftInflation, aRightInflation);
}
if (aBottomInflation) {
for (int y = (aSize.height + aTopInflation); y < integralImageSize.height; y++) {
GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + ((aSize.height - 1) * aSourceStride),
aIntegralImage + (y - 1) * stride32bit,
aSize.width, aLeftInflation, aRightInflation);
}
}
}
/**
* Attempt to do an in-place box blur using an integral image.
*/
void
AlphaBoxBlur::BoxBlur_C(uint8_t* aData,
int32_t aLeftLobe,
int32_t aRightLobe,
int32_t aTopLobe,
int32_t aBottomLobe,
uint32_t *aIntegralImage,
size_t aIntegralImageStride)
{
IntSize size = GetSize();
MOZ_ASSERT(size.width > 0);
// Our 'left' or 'top' lobe will include the current pixel. i.e. when
// looking at an integral image the value of a pixel at 'x,y' is calculated
// using the value of the integral image values above/below that.
aLeftLobe++;
aTopLobe++;
int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe);
MOZ_ASSERT(boxSize > 0);
if (boxSize == 1) {
return;
}
int32_t stride32bit = aIntegralImageStride / 4;
int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value();
GenerateIntegralImage_C(leftInflation, aRightLobe, aTopLobe, aBottomLobe,
aIntegralImage, aIntegralImageStride, aData,
mStride, size);
uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize);
uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation;
// Storing these locally makes this about 30% faster! Presumably the compiler
// can't be sure we're not altering the member variables in this loop.
IntRect skipRect = mSkipRect;
uint8_t *data = aData;
int32_t stride = mStride;
for (int32_t y = 0; y < size.height; y++) {
bool inSkipRectY = y > skipRect.y && y < skipRect.YMost();
uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * stride32bit - aLeftLobe);
uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * stride32bit + aRightLobe);
uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * stride32bit + aRightLobe);
uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * stride32bit - aLeftLobe);
for (int32_t x = 0; x < size.width; x++) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 1;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
int32_t topLeft = topLeftBase[x];
int32_t topRight = topRightBase[x];
int32_t bottomRight = bottomRightBase[x];
int32_t bottomLeft = bottomLeftBase[x];
uint32_t value = bottomRight - topRight - bottomLeft;
value += topLeft;
data[stride * y + x] = (uint64_t(reciprocal) * value + (uint64_t(1) << 31)) >> 32;
}
}
}
/**
* Compute the box blur size (which we're calling the blur radius) from
* the standard deviation.
*
* Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
* approximating a Gaussian using box blurs. This yields quite a good
* approximation for a Gaussian. Then we multiply this by 1.5 since our
* code wants the radius of the entire triple-box-blur kernel instead of
* the diameter of an individual box blur. For more details, see:
* http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
* https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19
*/
static const Float GAUSSIAN_SCALE_FACTOR = Float((3 * sqrt(2 * M_PI) / 4) * 1.5);
IntSize
AlphaBoxBlur::CalculateBlurRadius(const Point& aStd)
{
IntSize size(static_cast<int32_t>(floor(aStd.x * GAUSSIAN_SCALE_FACTOR + 0.5f)),
static_cast<int32_t>(floor(aStd.y * GAUSSIAN_SCALE_FACTOR + 0.5f)));
return size;
}
} // namespace gfx
} // namespace mozilla

186
gfx/2d/Blur.h Normal file
View file

@ -0,0 +1,186 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef MOZILLA_GFX_BLUR_H_
#define MOZILLA_GFX_BLUR_H_
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/CheckedInt.h"
namespace mozilla {
namespace gfx {
#ifdef _MSC_VER
#pragma warning( disable : 4251 )
#endif
/**
* Implementation of a triple box blur approximation of a Gaussian blur.
*
* A Gaussian blur is good for blurring because, when done independently
* in the horizontal and vertical directions, it matches the result that
* would be obtained using a different (rotated) set of axes. A triple
* box blur is a very close approximation of a Gaussian.
*
* This is a "service" class; the constructors set up all the information
* based on the values and compute the minimum size for an 8-bit alpha
* channel context.
* The callers are responsible for creating and managing the backing surface
* and passing the pointer to the data to the Blur() method. This class does
* not retain the pointer to the data outside of the Blur() call.
*
* A spread N makes each output pixel the maximum value of all source
* pixels within a square of side length 2N+1 centered on the output pixel.
*/
class GFX2D_API AlphaBoxBlur
{
public:
/** Constructs a box blur and computes the backing surface size.
*
* @param aRect The coordinates of the surface to create in device units.
*
* @param aBlurRadius The blur radius in pixels. This is the radius of the
* entire (triple) kernel function. Each individual box blur has radius
* approximately 1/3 this value, or diameter approximately 2/3 this value.
* This parameter should nearly always be computed using CalculateBlurRadius,
* below.
*
* @param aDirtyRect A pointer to a dirty rect, measured in device units, if
* available. This will be used for optimizing the blur operation. It is
* safe to pass nullptr here.
*
* @param aSkipRect A pointer to a rect, measured in device units, that
* represents an area where blurring is unnecessary and shouldn't be done for
* speed reasons. It is safe to pass nullptr here.
*/
AlphaBoxBlur(const Rect& aRect,
const IntSize& aSpreadRadius,
const IntSize& aBlurRadius,
const Rect* aDirtyRect,
const Rect* aSkipRect);
AlphaBoxBlur(const Rect& aRect,
int32_t aStride,
float aSigmaX,
float aSigmaY);
~AlphaBoxBlur();
/**
* Return the size, in pixels, of the 8-bit alpha surface we'd use.
*/
IntSize GetSize();
/**
* Return the stride, in bytes, of the 8-bit alpha surface we'd use.
*/
int32_t GetStride();
/**
* Returns the device-space rectangle the 8-bit alpha surface covers.
*/
IntRect GetRect();
/**
* Return a pointer to a dirty rect, as passed in to the constructor, or nullptr
* if none was passed in.
*/
Rect* GetDirtyRect();
/**
* Return the minimum buffer size that should be given to Blur() method. If
* zero, the class is not properly setup for blurring. Note that this
* includes the extra three bytes on top of the stride*width, where something
* like gfxImageSurface::GetDataSize() would report without it, even if it
* happens to have the extra bytes.
*/
size_t GetSurfaceAllocationSize() const;
/**
* Perform the blur in-place on the surface backed by specified 8-bit
* alpha surface data. The size must be at least that returned by
* GetSurfaceAllocationSize() or bad things will happen.
*/
void Blur(uint8_t* aData);
/**
* Calculates a blur radius that, when used with box blur, approximates a
* Gaussian blur with the given standard deviation. The result of this
* function should be used as the aBlurRadius parameter to AlphaBoxBlur's
* constructor, above.
*/
static IntSize CalculateBlurRadius(const Point& aStandardDeviation);
private:
void BoxBlur_C(uint8_t* aData,
int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe,
int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride);
void BoxBlur_SSE2(uint8_t* aData,
int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe,
int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride);
#ifdef BUILD_ARM_NEON
void BoxBlur_NEON(uint8_t* aData,
int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe,
int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride);
#endif
#ifdef _MIPS_ARCH_LOONGSON3A
void BoxBlur_LS3(uint8_t* aData,
int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe,
int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride);
#endif
static CheckedInt<int32_t> RoundUpToMultipleOf4(int32_t aVal);
/**
* A rect indicating the area where blurring is unnecessary, and the blur
* algorithm should skip over it.
*/
IntRect mSkipRect;
/**
* The device-space rectangle the the backing 8-bit alpha surface covers.
*/
IntRect mRect;
/**
* A copy of the dirty rect passed to the constructor. This will only be valid if
* mHasDirtyRect is true.
*/
Rect mDirtyRect;
/**
* The spread radius, in pixels.
*/
IntSize mSpreadRadius;
/**
* The blur radius, in pixels.
*/
IntSize mBlurRadius;
/**
* The stride of the data passed to Blur()
*/
int32_t mStride;
/**
* The minimum size of the buffer needed for the Blur() operation.
*/
size_t mSurfaceAllocationSize;
/**
* Whether mDirtyRect contains valid data.
*/
bool mHasDirtyRect;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_BLUR_H_ */

588
gfx/2d/BlurLS3.cpp Normal file
View file

@ -0,0 +1,588 @@
/* 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 "Blur.h"
#include <string.h>
#ifdef _MIPS_ARCH_LOONGSON3A
#include "MMIHelpers.h"
namespace mozilla {
namespace gfx {
typedef struct { double l; double h; } __m128i;
MOZ_ALWAYS_INLINE
__m128i loadUnaligned128(__m128i *p)
{
__m128i v;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"gsldlc1 %[vh], 0xf(%[p]) \n\t"
"gsldrc1 %[vh], 0x8(%[p]) \n\t"
"gsldlc1 %[vl], 0x7(%[p]) \n\t"
"gsldrc1 %[vl], 0x0(%[p]) \n\t"
".set pop \n\t"
:[vh]"=f"(v.h), [vl]"=f"(v.l)
:[p]"r"(p)
:"memory"
);
return v;
}
MOZ_ALWAYS_INLINE
__m128i Divide(__m128i aValues, __m128i aDivisor)
{
uint64_t tmp;
double srl32;
__m128i mask, ra, p4321, t1, t2;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"li %[tmp], 0x80000000 \n\t"
"mtc1 %[tmp], %[ral] \n\t"
"xor %[maskl], %[maskl], %[maskl] \n\t"
"mov.d %[rah], %[ral] \n\t"
"li %[tmp], 0xffffffff \n\t"
"mthc1 %[tmp], %[maskl] \n\t"
"mov.d %[maskh], %[maskl] \n\t"
".set pop \n\t"
:[rah]"=f"(ra.h), [ral]"=f"(ra.l),
[maskh]"=f"(mask.h), [maskl]"=f"(mask.l),
[tmp]"=&r"(tmp)
);
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"ori %[tmp], $0, 32 \n\t"
"mtc1 %[tmp], %[srl32] \n\t"
_mm_pmuluw(t1, av, ad)
_mm_psrld(t2, av, srl32)
_mm_pmuluw(t2, t2, ad)
// Add 1 << 31 before shifting or masking the lower 32 bits away, so that the
// result is rounded.
_mm_paddd(t1, t1, ra)
_mm_psrld(t1, t1, srl32)
_mm_paddd(t2, t2, ra)
_mm_and(t2, t2, mask)
_mm_or(p4321, t1, t2)
".set pop \n\t"
:[p4321h]"=&f"(p4321.h), [p4321l]"=&f"(p4321.l),
[t1h]"=&f"(t1.h), [t1l]"=&f"(t1.l),
[t2h]"=&f"(t2.h), [t2l]"=&f"(t2.l),
[srl32]"=&f"(srl32), [tmp]"=&r"(tmp)
:[rah]"f"(ra.h), [ral]"f"(ra.l),
[maskh]"f"(mask.h), [maskl]"f"(mask.l),
[avh]"f"(aValues.h), [avl]"f"(aValues.l),
[adh]"f"(aDivisor.h), [adl]"f"(aDivisor.l)
);
return p4321;
}
MOZ_ALWAYS_INLINE
__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight,
const __m128i& aBottomRight, const __m128i& aBottomLeft,
const __m128i& aDivisor)
{
__m128i values;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_psubw(val, abr, atr)
_mm_psubw(val, val, abl)
_mm_paddw(val, val, atl)
".set pop \n\t"
:[valh]"=&f"(values.h), [vall]"=&f"(values.l)
:[abrh]"f"(aBottomRight.h), [abrl]"f"(aBottomRight.l),
[atrh]"f"(aTopRight.h), [atrl]"f"(aTopRight.l),
[ablh]"f"(aBottomLeft.h), [abll]"f"(aBottomLeft.l),
[atlh]"f"(aTopLeft.h), [atll]"f"(aTopLeft.l)
);
return Divide(values, aDivisor);
}
MOZ_ALWAYS_INLINE
void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource,
int32_t aSourceWidth, int32_t aLeftInflation,
int32_t aRightInflation)
{
int32_t currentRowSum = 0;
for (int x = 0; x < aLeftInflation; x++) {
currentRowSum += aSource[0];
aDest[x] = currentRowSum;
}
for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) {
currentRowSum += aSource[(x - aLeftInflation)];
aDest[x] = currentRowSum;
}
for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) {
currentRowSum += aSource[aSourceWidth - 1];
aDest[x] = currentRowSum;
}
}
// This function calculates an integral of four pixels stored in the 4
// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns
// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after
// much testing.
MOZ_ALWAYS_INLINE
__m128i AccumulatePixelSums(__m128i aPixels)
{
uint64_t tr;
double tmp, s4, s64;
__m128i sumPixels, currentPixels, zero;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_xor(z, z, z)
"li %[tr], 64 \n\t"
"mtc1 %[tr], %[s64] \n\t"
"li %[tr], 32 \n\t"
"mtc1 %[tr], %[s4] \n\t"
_mm_psllq(cp, ap, s4, s64, t)
_mm_paddw(sp, ap, cp)
_mm_punpckldq(cp, z, sp)
_mm_paddw(sp, sp, cp)
".set pop \n\t"
:[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l),
[cph]"=&f"(currentPixels.h), [cpl]"=&f"(currentPixels.l),
[zh]"=&f"(zero.h), [zl]"=&f"(zero.l),
[s4]"=&f"(s4), [s64]"=&f"(s64), [t]"=&f"(tmp), [tr]"=&r"(tr)
:[aph]"f"(aPixels.h), [apl]"f"(aPixels.l)
);
return sumPixels;
}
MOZ_ALWAYS_INLINE
void GenerateIntegralImage_LS3(int32_t aLeftInflation, int32_t aRightInflation,
int32_t aTopInflation, int32_t aBottomInflation,
uint32_t *aIntegralImage, size_t aIntegralImageStride,
uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize)
{
MOZ_ASSERT(!(aLeftInflation & 3));
uint32_t stride32bit = aIntegralImageStride / 4;
IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation,
aSize.height + aTopInflation + aBottomInflation);
LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation);
for (int y = 1; y < aTopInflation + 1; y++) {
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint32_t *intFirstRow = aIntegralImage;
for (int x = 0; x < integralImageSize.width; x += 4) {
__m128i firstRow, previousRow;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"gslqc1 %[frh], %[frl], (%[fr]) \n\t"
"gslqc1 %[prh], %[prl], (%[pr]) \n\t"
_mm_paddw(fr, fr, pr)
"gssqc1 %[frh], %[frl], (%[r]) \n\t"
".set pop \n\t"
:[frh]"=&f"(firstRow.h), [frl]"=&f"(firstRow.l),
[prh]"=&f"(previousRow.h), [prl]"=&f"(previousRow.l)
:[fr]"r"(intFirstRow + x), [pr]"r"(intPrevRow + x),
[r]"r"(intRow + x)
:"memory"
);
}
}
uint64_t tmp;
double s44, see;
__m128i zero;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"li %[tmp], 0xee \n\t"
"mtc1 %[tmp], %[see] \n\t"
"li %[tmp], 0x44 \n\t"
"mtc1 %[tmp], %[s44] \n\t"
_mm_xor(zero, zero, zero)
".set pop \n\t"
:[tmp]"=&r"(tmp), [s44]"=f"(s44), [see]"=f"(see),
[zeroh]"=f"(zero.h), [zerol]"=f"(zero.l)
);
for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) {
__m128i currentRowSum;
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation);
uint32_t pixel = sourceRow[0];
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_xor(cr, cr, cr)
".set pop \n\t"
:[crh]"=f"(currentRowSum.h), [crl]"=f"(currentRowSum.l)
);
for (int x = 0; x < aLeftInflation; x += 4) {
__m128i sumPixels, t;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"mtc1 %[pix], %[spl] \n\t"
"punpcklwd %[spl], %[spl], %[spl] \n\t"
"mov.d %[sph], %[spl] \n\t"
"pshufh %[sph], %[spl], %[s44] \n\t"
"pshufh %[spl], %[spl], %[s44] \n\t"
".set pop \n\t"
:[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l)
:[pix]"r"(pixel), [s44]"f"(s44)
);
sumPixels = AccumulatePixelSums(sumPixels);
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_paddw(sp, sp, cr)
"pshufh %[crh], %[sph], %[see] \n\t"
"pshufh %[crl], %[sph], %[see] \n\t"
"gslqc1 %[th], %[tl], (%[pr]) \n\t"
_mm_paddw(t, sp, t)
"gssqc1 %[th], %[tl], (%[r]) \n\t"
".set pop \n\t"
:[th]"=&f"(t.h), [tl]"=&f"(t.l),
[sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l),
[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l)
:[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see)
:"memory"
);
}
for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) {
uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation));
__m128i sumPixels, t;
// It's important to shuffle here. When we exit this loop currentRowSum
// has to be set to sumPixels, so that the following loop can get the
// correct pixel for the currentRowSum. The highest order pixel in
// currentRowSum could've originated from accumulation in the stride.
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"pshufh %[crl], %[crh], %[see] \n\t"
"pshufh %[crh], %[crh], %[see] \n\t"
"mtc1 %[pix], %[spl] \n\t"
"punpcklwd %[spl], %[spl], %[spl] \n\t"
"mov.d %[sph], %[spl] \n\t"
_mm_punpcklbh(sp, sp, zero)
_mm_punpcklhw(sp, sp, zero)
".set pop \n\t"
:[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l),
[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l)
:[pix]"r"(pixels), [see]"f"(see),
[zeroh]"f"(zero.h), [zerol]"f"(zero.l)
);
sumPixels = AccumulatePixelSums(sumPixels);
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_paddw(sp, sp, cr)
"mov.d %[crh], %[sph] \n\t"
"mov.d %[crl], %[spl] \n\t"
"gslqc1 %[th], %[tl], (%[pr]) \n\t"
_mm_paddw(t, sp, t)
"gssqc1 %[th], %[tl], (%[r]) \n\t"
".set pop \n\t"
:[th]"=&f"(t.h), [tl]"=&f"(t.l),
[sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l),
[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l)
:[r]"r"(intRow + x), [pr]"r"(intPrevRow + x)
:"memory"
);
}
pixel = sourceRow[aSize.width - 1];
int x = (aSize.width + aLeftInflation);
if ((aSize.width & 3)) {
// Deal with unaligned portion. Get the correct pixel from currentRowSum,
// see explanation above.
uint32_t intCurrentRowSum = ((uint32_t*)&currentRowSum)[(aSize.width % 4) - 1];
for (; x < integralImageSize.width; x++) {
// We could be unaligned here!
if (!(x & 3)) {
// aligned!
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"mtc1 %[cr], %[crl] \n\t"
"punpcklwd %[crl], %[crl], %[crl] \n\t"
"mov.d %[crh], %[crl] \n\t"
".set pop \n\t"
:[crh]"=f"(currentRowSum.h), [crl]"=f"(currentRowSum.l)
:[cr]"r"(intCurrentRowSum)
);
break;
}
intCurrentRowSum += pixel;
intRow[x] = intPrevRow[x] + intCurrentRowSum;
}
} else {
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"pshufh %[crl], %[crh], %[see] \n\t"
"pshufh %[crh], %[crh], %[see] \n\t"
".set pop \n\t"
:[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l)
:[see]"f"(see)
);
}
for (; x < integralImageSize.width; x += 4) {
__m128i sumPixels, t;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"mtc1 %[pix], %[spl] \n\t"
"punpcklwd %[spl], %[spl], %[spl] \n\t"
"mov.d %[sph], %[spl] \n\t"
".set pop \n\t"
:[sph]"=f"(sumPixels.h), [spl]"=f"(sumPixels.l)
:[pix]"r"(pixel)
);
sumPixels = AccumulatePixelSums(sumPixels);
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_paddw(sp, sp, cr)
"pshufh %[crh], %[sph], %[see] \n\t"
"pshufh %[crl], %[sph], %[see] \n\t"
"gslqc1 %[th], %[tl], (%[pr]) \n\t"
_mm_paddw(t, sp, t)
"gssqc1 %[th], %[tl], (%[r]) \n\t"
".set pop \n\t"
:[th]"=&f"(t.h), [tl]"=&f"(t.l),
[sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l),
[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l)
:[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see)
:"memory"
);
}
}
if (aBottomInflation) {
// Store the last valid row of our source image in the last row of
// our integral image. This will be overwritten with the correct values
// in the upcoming loop.
LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit,
aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation);
for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) {
__m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit));
__m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit);
__m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit);
for (int x = 0; x < integralImageSize.width; x += 4) {
__m128i t1, t2;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"gslqc1 %[t1h], %[t1l], (%[lr]) \n\t"
"gslqc1 %[t2h], %[t2l], (%[pr]) \n\t"
_mm_paddw(t1, t1, t2)
"gssqc1 %[t1h], %[t1l], (%[r]) \n\t"
".set pop \n\t"
:[t1h]"=&f"(t1.h), [t1l]"=&f"(t1.l),
[t2h]"=&f"(t2.h), [t2l]"=&f"(t2.l)
:[r]"r"(intRow + (x / 4)),
[lr]"r"(intLastRow + (x / 4)),
[pr]"r"(intPrevRow + (x / 4))
:"memory"
);
}
}
}
}
/**
* Attempt to do an in-place box blur using an integral image.
*/
void
AlphaBoxBlur::BoxBlur_LS3(uint8_t* aData,
int32_t aLeftLobe,
int32_t aRightLobe,
int32_t aTopLobe,
int32_t aBottomLobe,
uint32_t *aIntegralImage,
size_t aIntegralImageStride)
{
IntSize size = GetSize();
MOZ_ASSERT(size.height > 0);
// Our 'left' or 'top' lobe will include the current pixel. i.e. when
// looking at an integral image the value of a pixel at 'x,y' is calculated
// using the value of the integral image values above/below that.
aLeftLobe++;
aTopLobe++;
int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe);
MOZ_ASSERT(boxSize > 0);
if (boxSize == 1) {
return;
}
uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize);
uint32_t stride32bit = aIntegralImageStride / 4;
int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value();
GenerateIntegralImage_LS3(leftInflation, aRightLobe, aTopLobe, aBottomLobe,
aIntegralImage, aIntegralImageStride, aData,
mStride, size);
__m128i divisor, zero;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
"mtc1 %[rec], %[divl] \n\t"
"punpcklwd %[divl], %[divl], %[divl] \n\t"
"mov.d %[divh], %[divl] \n\t"
_mm_xor(zero, zero, zero)
".set pop \n\t"
:[divh]"=f"(divisor.h), [divl]"=f"(divisor.l),
[zeroh]"=f"(zero.h), [zerol]"=f"(zero.l)
:[rec]"r"(reciprocal)
);
// This points to the start of the rectangle within the IntegralImage that overlaps
// the surface being blurred.
uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation;
IntRect skipRect = mSkipRect;
int32_t stride = mStride;
uint8_t *data = aData;
for (int32_t y = 0; y < size.height; y++) {
bool inSkipRectY = y > skipRect.y && y < skipRect.YMost();
uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe);
uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe);
uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe);
uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe);
int32_t x = 0;
// Process 16 pixels at a time for as long as possible.
for (; x <= size.width - 16; x += 16) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 16;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
__m128i topLeft;
__m128i topRight;
__m128i bottomRight;
__m128i bottomLeft;
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x));
topRight = loadUnaligned128((__m128i*)(topRightBase + x));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x));
__m128i result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4));
topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4));
__m128i result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8));
topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8));
__m128i result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12));
topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12));
__m128i result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
double t;
__m128i final;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_packsswh(r3, r3, r4, t)
_mm_packsswh(f, r1, r2, t)
_mm_packushb(f, f, r3, t)
"gssdlc1 %[fh], 0xf(%[d]) \n\t"
"gssdrc1 %[fh], 0x8(%[d]) \n\t"
"gssdlc1 %[fl], 0x7(%[d]) \n\t"
"gssdrc1 %[fl], 0x0(%[d]) \n\t"
".set pop \n\t"
:[fh]"=&f"(final.h), [fl]"=&f"(final.l),
[r3h]"+f"(result3.h), [r3l]"+f"(result3.l),
[t]"=&f"(t)
:[r1h]"f"(result1.h), [r1l]"f"(result1.l),
[r2h]"f"(result2.h), [r2l]"f"(result2.l),
[r4h]"f"(result4.h), [r4l]"f"(result4.l),
[d]"r"(data + stride * y + x)
:"memory"
);
}
// Process the remaining pixels 4 bytes at a time.
for (; x < size.width; x += 4) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 4;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
__m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x));
__m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x));
__m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x));
__m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x));
__m128i result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
double t;
__m128i final;
asm volatile (
".set push \n\t"
".set arch=loongson3a \n\t"
_mm_packsswh(f, r, zero, t)
_mm_packushb(f, f, zero, t)
"swc1 %[fl], (%[d]) \n\t"
".set pop \n\t"
:[fh]"=&f"(final.h), [fl]"=&f"(final.l),
[t]"=&f"(t)
:[d]"r"(data + stride * y + x),
[rh]"f"(result.h), [rl]"f"(result.l),
[zeroh]"f"(zero.h), [zerol]"f"(zero.l)
:"memory"
);
}
}
}
}
}
#endif /* _MIPS_ARCH_LOONGSON3A */

288
gfx/2d/BlurNEON.cpp Normal file
View file

@ -0,0 +1,288 @@
/* 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 "Blur.h"
#include <arm_neon.h>
namespace mozilla {
namespace gfx {
MOZ_ALWAYS_INLINE
uint16x4_t Divide(uint32x4_t aValues, uint32x2_t aDivisor)
{
uint64x2_t roundingAddition = vdupq_n_u64(int64_t(1) << 31);
uint64x2_t multiplied21 = vmull_u32(vget_low_u32(aValues), aDivisor);
uint64x2_t multiplied43 = vmull_u32(vget_high_u32(aValues), aDivisor);
return vqmovn_u32(vcombine_u32(vshrn_n_u64(vaddq_u64(multiplied21, roundingAddition), 32),
vshrn_n_u64(vaddq_u64(multiplied43, roundingAddition), 32)));
}
MOZ_ALWAYS_INLINE
uint16x4_t BlurFourPixels(const uint32x4_t& aTopLeft, const uint32x4_t& aTopRight,
const uint32x4_t& aBottomRight, const uint32x4_t& aBottomLeft,
const uint32x2_t& aDivisor)
{
uint32x4_t values = vaddq_u32(vsubq_u32(vsubq_u32(aBottomRight, aTopRight), aBottomLeft), aTopLeft);
return Divide(values, aDivisor);
}
MOZ_ALWAYS_INLINE
void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource,
int32_t aSourceWidth, int32_t aLeftInflation,
int32_t aRightInflation)
{
int32_t currentRowSum = 0;
for (int x = 0; x < aLeftInflation; x++) {
currentRowSum += aSource[0];
aDest[x] = currentRowSum;
}
for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) {
currentRowSum += aSource[(x - aLeftInflation)];
aDest[x] = currentRowSum;
}
for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) {
currentRowSum += aSource[aSourceWidth - 1];
aDest[x] = currentRowSum;
}
}
MOZ_ALWAYS_INLINE void
GenerateIntegralImage_NEON(int32_t aLeftInflation, int32_t aRightInflation,
int32_t aTopInflation, int32_t aBottomInflation,
uint32_t *aIntegralImage, size_t aIntegralImageStride,
uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize)
{
MOZ_ASSERT(!(aLeftInflation & 3));
uint32_t stride32bit = aIntegralImageStride / 4;
IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation,
aSize.height + aTopInflation + aBottomInflation);
LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation);
for (int y = 1; y < aTopInflation + 1; y++) {
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint32_t *intFirstRow = aIntegralImage;
for (int x = 0; x < integralImageSize.width; x += 4) {
uint32x4_t firstRow = vld1q_u32(intFirstRow + x);
uint32x4_t previousRow = vld1q_u32(intPrevRow + x);
vst1q_u32(intRow + x, vaddq_u32(firstRow, previousRow));
}
}
for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) {
uint32x4_t currentRowSum = vdupq_n_u32(0);
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation);
uint32_t pixel = sourceRow[0];
for (int x = 0; x < aLeftInflation; x += 4) {
uint32_t temp[4];
temp[0] = pixel;
temp[1] = temp[0] + pixel;
temp[2] = temp[1] + pixel;
temp[3] = temp[2] + pixel;
uint32x4_t sumPixels = vld1q_u32(temp);
sumPixels = vaddq_u32(sumPixels, currentRowSum);
currentRowSum = vdupq_n_u32(vgetq_lane_u32(sumPixels, 3));
vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x)));
}
for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) {
// It's important to shuffle here. When we exit this loop currentRowSum
// has to be set to sumPixels, so that the following loop can get the
// correct pixel for the currentRowSum. The highest order pixel in
// currentRowSum could've originated from accumulation in the stride.
currentRowSum = vdupq_n_u32(vgetq_lane_u32(currentRowSum, 3));
uint32_t temp[4];
temp[0] = *(sourceRow + (x - aLeftInflation));
temp[1] = temp[0] + *(sourceRow + (x - aLeftInflation) + 1);
temp[2] = temp[1] + *(sourceRow + (x - aLeftInflation) + 2);
temp[3] = temp[2] + *(sourceRow + (x - aLeftInflation) + 3);
uint32x4_t sumPixels = vld1q_u32(temp);
sumPixels = vaddq_u32(sumPixels, currentRowSum);
currentRowSum = sumPixels;
vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x)));
}
pixel = sourceRow[aSize.width - 1];
int x = (aSize.width + aLeftInflation);
if ((aSize.width & 3)) {
// Deal with unaligned portion. Get the correct pixel from currentRowSum,
// see explanation above.
uint32_t intCurrentRowSum = ((uint32_t*)&currentRowSum)[(aSize.width % 4) - 1];
for (; x < integralImageSize.width; x++) {
// We could be unaligned here!
if (!(x & 3)) {
// aligned!
currentRowSum = vdupq_n_u32(intCurrentRowSum);
break;
}
intCurrentRowSum += pixel;
intRow[x] = intPrevRow[x] + intCurrentRowSum;
}
} else {
currentRowSum = vdupq_n_u32(vgetq_lane_u32(currentRowSum, 3));
}
for (; x < integralImageSize.width; x += 4) {
uint32_t temp[4];
temp[0] = pixel;
temp[1] = temp[0] + pixel;
temp[2] = temp[1] + pixel;
temp[3] = temp[2] + pixel;
uint32x4_t sumPixels = vld1q_u32(temp);
sumPixels = vaddq_u32(sumPixels, currentRowSum);
currentRowSum = vdupq_n_u32(vgetq_lane_u32(sumPixels, 3));
vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x)));
}
}
if (aBottomInflation) {
// Store the last valid row of our source image in the last row of
// our integral image. This will be overwritten with the correct values
// in the upcoming loop.
LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit,
aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation);
for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) {
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint32_t *intLastRow = aIntegralImage + (integralImageSize.height - 1) * stride32bit;
for (int x = 0; x < integralImageSize.width; x += 4) {
vst1q_u32(intRow + x,
vaddq_u32(vld1q_u32(intLastRow + x),
vld1q_u32(intPrevRow + x)));
}
}
}
}
/**
* Attempt to do an in-place box blur using an integral image.
*/
void
AlphaBoxBlur::BoxBlur_NEON(uint8_t* aData,
int32_t aLeftLobe,
int32_t aRightLobe,
int32_t aTopLobe,
int32_t aBottomLobe,
uint32_t *aIntegralImage,
size_t aIntegralImageStride)
{
IntSize size = GetSize();
MOZ_ASSERT(size.height > 0);
// Our 'left' or 'top' lobe will include the current pixel. i.e. when
// looking at an integral image the value of a pixel at 'x,y' is calculated
// using the value of the integral image values above/below that.
aLeftLobe++;
aTopLobe++;
int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe);
MOZ_ASSERT(boxSize > 0);
if (boxSize == 1) {
return;
}
uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize);
uint32_t stride32bit = aIntegralImageStride / 4;
int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value();
GenerateIntegralImage_NEON(leftInflation, aRightLobe, aTopLobe, aBottomLobe,
aIntegralImage, aIntegralImageStride, aData,
mStride, size);
uint32x2_t divisor = vdup_n_u32(reciprocal);
// This points to the start of the rectangle within the IntegralImage that overlaps
// the surface being blurred.
uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation;
IntRect skipRect = mSkipRect;
int32_t stride = mStride;
uint8_t *data = aData;
for (int32_t y = 0; y < size.height; y++) {
bool inSkipRectY = y > skipRect.y && y < skipRect.YMost();
uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe);
uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe);
uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe);
uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe);
int32_t x = 0;
// Process 16 pixels at a time for as long as possible.
for (; x <= size.width - 16; x += 16) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 16;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
uint32x4_t topLeft;
uint32x4_t topRight;
uint32x4_t bottomRight;
uint32x4_t bottomLeft;
topLeft = vld1q_u32(topLeftBase + x);
topRight = vld1q_u32(topRightBase + x);
bottomRight = vld1q_u32(bottomRightBase + x);
bottomLeft = vld1q_u32(bottomLeftBase + x);
uint16x4_t result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = vld1q_u32(topLeftBase + x + 4);
topRight = vld1q_u32(topRightBase + x + 4);
bottomRight = vld1q_u32(bottomRightBase + x + 4);
bottomLeft = vld1q_u32(bottomLeftBase + x + 4);
uint16x4_t result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = vld1q_u32(topLeftBase + x + 8);
topRight = vld1q_u32(topRightBase + x + 8);
bottomRight = vld1q_u32(bottomRightBase + x + 8);
bottomLeft = vld1q_u32(bottomLeftBase + x + 8);
uint16x4_t result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = vld1q_u32(topLeftBase + x + 12);
topRight = vld1q_u32(topRightBase + x + 12);
bottomRight = vld1q_u32(bottomRightBase + x + 12);
bottomLeft = vld1q_u32(bottomLeftBase + x + 12);
uint16x4_t result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
uint8x8_t combine1 = vqmovn_u16(vcombine_u16(result1, result2));
uint8x8_t combine2 = vqmovn_u16(vcombine_u16(result3, result4));
uint8x16_t final = vcombine_u8(combine1, combine2);
vst1q_u8(data + stride * y + x, final);
}
// Process the remaining pixels 4 bytes at a time.
for (; x < size.width; x += 4) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 4;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
uint32x4_t topLeft = vld1q_u32(topLeftBase + x);
uint32x4_t topRight = vld1q_u32(topRightBase + x);
uint32x4_t bottomRight = vld1q_u32(bottomRightBase + x);
uint32x4_t bottomLeft = vld1q_u32(bottomLeftBase + x);
uint16x4_t result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
uint32x2_t final = vreinterpret_u32_u8(vmovn_u16(vcombine_u16(result, vdup_n_u16(0))));
*(uint32_t*)(data + stride * y + x) = vget_lane_u32(final, 0);
}
}
}
}
}

315
gfx/2d/BlurSSE2.cpp Normal file
View file

@ -0,0 +1,315 @@
/* 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 "Blur.h"
#include "SSEHelpers.h"
#include <string.h>
namespace mozilla {
namespace gfx {
MOZ_ALWAYS_INLINE
__m128i Divide(__m128i aValues, __m128i aDivisor)
{
const __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff);
static const union {
int64_t i64[2];
__m128i m;
} roundingAddition = { { int64_t(1) << 31, int64_t(1) << 31 } };
__m128i multiplied31 = _mm_mul_epu32(aValues, aDivisor);
__m128i multiplied42 = _mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor);
// Add 1 << 31 before shifting or masking the lower 32 bits away, so that the
// result is rounded.
__m128i p_3_1 = _mm_srli_epi64(_mm_add_epi64(multiplied31, roundingAddition.m), 32);
__m128i p4_2_ = _mm_and_si128(_mm_add_epi64(multiplied42, roundingAddition.m), mask);
__m128i p4321 = _mm_or_si128(p_3_1, p4_2_);
return p4321;
}
MOZ_ALWAYS_INLINE
__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight,
const __m128i& aBottomRight, const __m128i& aBottomLeft,
const __m128i& aDivisor)
{
__m128i values = _mm_add_epi32(_mm_sub_epi32(_mm_sub_epi32(aBottomRight, aTopRight), aBottomLeft), aTopLeft);
return Divide(values, aDivisor);
}
MOZ_ALWAYS_INLINE
void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource,
int32_t aSourceWidth, int32_t aLeftInflation,
int32_t aRightInflation)
{
int32_t currentRowSum = 0;
for (int x = 0; x < aLeftInflation; x++) {
currentRowSum += aSource[0];
aDest[x] = currentRowSum;
}
for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) {
currentRowSum += aSource[(x - aLeftInflation)];
aDest[x] = currentRowSum;
}
for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) {
currentRowSum += aSource[aSourceWidth - 1];
aDest[x] = currentRowSum;
}
}
// This function calculates an integral of four pixels stored in the 4
// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns
// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after
// much testing.
MOZ_ALWAYS_INLINE
__m128i AccumulatePixelSums(__m128i aPixels)
{
__m128i sumPixels = aPixels;
__m128i currentPixels = _mm_slli_si128(aPixels, 4);
sumPixels = _mm_add_epi32(sumPixels, currentPixels);
currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels);
return _mm_add_epi32(sumPixels, currentPixels);
}
MOZ_ALWAYS_INLINE void
GenerateIntegralImage_SSE2(int32_t aLeftInflation, int32_t aRightInflation,
int32_t aTopInflation, int32_t aBottomInflation,
uint32_t *aIntegralImage, size_t aIntegralImageStride,
uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize)
{
MOZ_ASSERT(!(aLeftInflation & 3));
uint32_t stride32bit = aIntegralImageStride / 4;
IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation,
aSize.height + aTopInflation + aBottomInflation);
LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation);
for (int y = 1; y < aTopInflation + 1; y++) {
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint32_t *intFirstRow = aIntegralImage;
for (int x = 0; x < integralImageSize.width; x += 4) {
__m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x));
__m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x));
_mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(firstRow, previousRow));
}
}
for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) {
__m128i currentRowSum = _mm_setzero_si128();
uint32_t *intRow = aIntegralImage + (y * stride32bit);
uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit;
uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation);
uint32_t pixel = sourceRow[0];
for (int x = 0; x < aLeftInflation; x += 4) {
__m128i sumPixels = AccumulatePixelSums(_mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0)));
sumPixels = _mm_add_epi32(sumPixels, currentRowSum);
currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3));
_mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x))));
}
for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) {
uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation));
// It's important to shuffle here. When we exit this loop currentRowSum
// has to be set to sumPixels, so that the following loop can get the
// correct pixel for the currentRowSum. The highest order pixel in
// currentRowSum could've originated from accumulation in the stride.
currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3));
__m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16(_mm_unpacklo_epi8( _mm_set1_epi32(pixels), _mm_setzero_si128()), _mm_setzero_si128()));
sumPixels = _mm_add_epi32(sumPixels, currentRowSum);
currentRowSum = sumPixels;
_mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x))));
}
pixel = sourceRow[aSize.width - 1];
int x = (aSize.width + aLeftInflation);
if ((aSize.width & 3)) {
// Deal with unaligned portion. Get the correct pixel from currentRowSum,
// see explanation above.
uint32_t intCurrentRowSum = ((uint32_t*)&currentRowSum)[(aSize.width % 4) - 1];
for (; x < integralImageSize.width; x++) {
// We could be unaligned here!
if (!(x & 3)) {
// aligned!
currentRowSum = _mm_set1_epi32(intCurrentRowSum);
break;
}
intCurrentRowSum += pixel;
intRow[x] = intPrevRow[x] + intCurrentRowSum;
}
} else {
currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3));
}
for (; x < integralImageSize.width; x += 4) {
__m128i sumPixels = AccumulatePixelSums(_mm_set1_epi32(pixel));
sumPixels = _mm_add_epi32(sumPixels, currentRowSum);
currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3));
_mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x))));
}
}
if (aBottomInflation) {
// Store the last valid row of our source image in the last row of
// our integral image. This will be overwritten with the correct values
// in the upcoming loop.
LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit,
aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation);
for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) {
__m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit));
__m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit);
__m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit);
for (int x = 0; x < integralImageSize.width; x += 4) {
_mm_store_si128(intRow + (x / 4),
_mm_add_epi32(_mm_load_si128(intLastRow + (x / 4)),
_mm_load_si128(intPrevRow + (x / 4))));
}
}
}
}
/**
* Attempt to do an in-place box blur using an integral image.
*/
void
AlphaBoxBlur::BoxBlur_SSE2(uint8_t* aData,
int32_t aLeftLobe,
int32_t aRightLobe,
int32_t aTopLobe,
int32_t aBottomLobe,
uint32_t *aIntegralImage,
size_t aIntegralImageStride)
{
IntSize size = GetSize();
MOZ_ASSERT(size.height > 0);
// Our 'left' or 'top' lobe will include the current pixel. i.e. when
// looking at an integral image the value of a pixel at 'x,y' is calculated
// using the value of the integral image values above/below that.
aLeftLobe++;
aTopLobe++;
int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe);
MOZ_ASSERT(boxSize > 0);
if (boxSize == 1) {
return;
}
uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize);
uint32_t stride32bit = aIntegralImageStride / 4;
int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value();
GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe,
aIntegralImage, aIntegralImageStride, aData,
mStride, size);
__m128i divisor = _mm_set1_epi32(reciprocal);
// This points to the start of the rectangle within the IntegralImage that overlaps
// the surface being blurred.
uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation;
IntRect skipRect = mSkipRect;
int32_t stride = mStride;
uint8_t *data = aData;
for (int32_t y = 0; y < size.height; y++) {
bool inSkipRectY = y > skipRect.y && y < skipRect.YMost();
uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe);
uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe);
uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe);
uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe);
int32_t x = 0;
// Process 16 pixels at a time for as long as possible.
for (; x <= size.width - 16; x += 16) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 16;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
__m128i topLeft;
__m128i topRight;
__m128i bottomRight;
__m128i bottomLeft;
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x));
topRight = loadUnaligned128((__m128i*)(topRightBase + x));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x));
__m128i result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4));
topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4));
__m128i result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8));
topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8));
__m128i result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12));
topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12));
bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12));
bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12));
__m128i result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
__m128i final = _mm_packus_epi16(_mm_packs_epi32(result1, result2), _mm_packs_epi32(result3, result4));
_mm_storeu_si128((__m128i*)(data + stride * y + x), final);
}
// Process the remaining pixels 4 bytes at a time.
for (; x < size.width; x += 4) {
if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) {
x = skipRect.XMost() - 4;
// Trigger early jump on coming loop iterations, this will be reset
// next line anyway.
inSkipRectY = false;
continue;
}
__m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x));
__m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x));
__m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x));
__m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x));
__m128i result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor);
__m128i final = _mm_packus_epi16(_mm_packs_epi32(result, _mm_setzero_si128()), _mm_setzero_si128());
*(uint32_t*)(data + stride * y + x) = _mm_cvtsi128_si32(final);
}
}
}
} // namespace gfx
} // namespace mozilla

217
gfx/2d/BorrowedContext.h Normal file
View file

@ -0,0 +1,217 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_BORROWED_CONTEXT_H
#define _MOZILLA_GFX_BORROWED_CONTEXT_H
#include "2D.h"
#ifdef MOZ_X11
#include <X11/extensions/Xrender.h>
#include <X11/Xlib.h>
#include "X11UndefineNone.h"
#endif
struct _cairo;
typedef struct _cairo cairo_t;
namespace mozilla {
namespace gfx {
/* This is a helper class that let's you borrow a cairo_t from a
* DrawTargetCairo. This is used for drawing themed widgets.
*
* Callers should check the cr member after constructing the object
* to see if it succeeded. The DrawTarget should not be used while
* the context is borrowed. */
class BorrowedCairoContext
{
public:
BorrowedCairoContext()
: mCairo(nullptr)
, mDT(nullptr)
{ }
explicit BorrowedCairoContext(DrawTarget *aDT)
: mDT(aDT)
{
mCairo = BorrowCairoContextFromDrawTarget(aDT);
}
// We can optionally Init after construction in
// case we don't know what the DT will be at construction
// time.
cairo_t *Init(DrawTarget *aDT)
{
MOZ_ASSERT(!mDT, "Can't initialize twice!");
mDT = aDT;
return mCairo = BorrowCairoContextFromDrawTarget(aDT);
}
// The caller needs to call Finish if cr is non-null when
// they are done with the context. This is currently explicit
// instead of happening implicitly in the destructor to make
// what's happening in the caller more clear. It also
// let's you resume using the DrawTarget in the same scope.
void Finish()
{
if (mCairo) {
ReturnCairoContextToDrawTarget(mDT, mCairo);
mCairo = nullptr;
}
}
~BorrowedCairoContext() {
MOZ_ASSERT(!mCairo);
}
cairo_t *mCairo;
private:
static cairo_t* BorrowCairoContextFromDrawTarget(DrawTarget *aDT);
static void ReturnCairoContextToDrawTarget(DrawTarget *aDT, cairo_t *aCairo);
DrawTarget *mDT;
};
#ifdef MOZ_X11
/* This is a helper class that let's you borrow an Xlib drawable from
* a DrawTarget. This is used for drawing themed widgets.
*
* Callers should check the Xlib drawable after constructing the object
* to see if it succeeded. The DrawTarget should not be used while
* the drawable is borrowed. */
class BorrowedXlibDrawable
{
public:
BorrowedXlibDrawable()
: mDT(nullptr),
mDisplay(nullptr),
mDrawable(X11None),
mScreen(nullptr),
mVisual(nullptr),
mXRenderFormat(nullptr)
{}
explicit BorrowedXlibDrawable(DrawTarget *aDT)
: mDT(nullptr),
mDisplay(nullptr),
mDrawable(X11None),
mScreen(nullptr),
mVisual(nullptr),
mXRenderFormat(nullptr)
{
Init(aDT);
}
// We can optionally Init after construction in
// case we don't know what the DT will be at construction
// time.
bool Init(DrawTarget *aDT);
// The caller needs to call Finish if drawable is non-zero when
// they are done with the context. This is currently explicit
// instead of happening implicitly in the destructor to make
// what's happening in the caller more clear. It also
// let's you resume using the DrawTarget in the same scope.
void Finish();
~BorrowedXlibDrawable() {
MOZ_ASSERT(!mDrawable);
}
Display *GetDisplay() const { return mDisplay; }
Drawable GetDrawable() const { return mDrawable; }
Screen *GetScreen() const { return mScreen; }
Visual *GetVisual() const { return mVisual; }
IntSize GetSize() const { return mSize; }
Point GetOffset() const { return mOffset; }
XRenderPictFormat* GetXRenderFormat() const { return mXRenderFormat; }
private:
DrawTarget *mDT;
Display *mDisplay;
Drawable mDrawable;
Screen *mScreen;
Visual *mVisual;
XRenderPictFormat *mXRenderFormat;
IntSize mSize;
Point mOffset;
};
#endif
#ifdef XP_DARWIN
/* This is a helper class that let's you borrow a CGContextRef from a
* DrawTargetCG. This is used for drawing themed widgets.
*
* Callers should check the cg member after constructing the object
* to see if it succeeded. The DrawTarget should not be used while
* the context is borrowed. */
class BorrowedCGContext
{
public:
BorrowedCGContext()
: cg(nullptr)
, mDT(nullptr)
{ }
explicit BorrowedCGContext(DrawTarget *aDT)
: mDT(aDT)
{
MOZ_ASSERT(aDT, "Caller should check for nullptr");
cg = BorrowCGContextFromDrawTarget(aDT);
}
// We can optionally Init after construction in
// case we don't know what the DT will be at construction
// time.
CGContextRef Init(DrawTarget *aDT)
{
MOZ_ASSERT(aDT, "Caller should check for nullptr");
MOZ_ASSERT(!mDT, "Can't initialize twice!");
mDT = aDT;
cg = BorrowCGContextFromDrawTarget(aDT);
return cg;
}
// The caller needs to call Finish if cg is non-null when
// they are done with the context. This is currently explicit
// instead of happening implicitly in the destructor to make
// what's happening in the caller more clear. It also
// let's you resume using the DrawTarget in the same scope.
void Finish()
{
if (cg) {
ReturnCGContextToDrawTarget(mDT, cg);
cg = nullptr;
}
}
~BorrowedCGContext() {
MOZ_ASSERT(!cg);
}
CGContextRef cg;
private:
#ifdef USE_SKIA
static CGContextRef BorrowCGContextFromDrawTarget(DrawTarget *aDT);
static void ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg);
#else
static CGContextRef BorrowCGContextFromDrawTarget(DrawTarget *aDT) {
MOZ_CRASH("Not supported without Skia");
}
static void ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg) {
MOZ_CRASH("not supported without Skia");
}
#endif
DrawTarget *mDT;
};
#endif
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_BORROWED_CONTEXT_H

144
gfx/2d/CGTextDrawing.h Normal file
View file

@ -0,0 +1,144 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_SKIACGPOPUPDRAWER_H
#define _MOZILLA_GFX_SKIACGPOPUPDRAWER_H
#include <ApplicationServices/ApplicationServices.h>
#include "nsDebug.h"
#include "mozilla/Vector.h"
#include "ScaledFontMac.h"
#include "PathCG.h"
#include <dlfcn.h>
// This is used when we explicitly need CG to draw text to support things such
// as vibrancy and subpixel AA on transparent backgrounds. The current use cases
// are really only to enable Skia to support drawing text in those situations.
namespace mozilla {
namespace gfx {
typedef void (*CGContextSetFontSmoothingBackgroundColorFunc) (CGContextRef cgContext, CGColorRef color);
static CGContextSetFontSmoothingBackgroundColorFunc
GetCGContextSetFontSmoothingBackgroundColorFunc()
{
static CGContextSetFontSmoothingBackgroundColorFunc func = nullptr;
static bool lookedUpFunc = false;
if (!lookedUpFunc) {
func = (CGContextSetFontSmoothingBackgroundColorFunc)dlsym(
RTLD_DEFAULT, "CGContextSetFontSmoothingBackgroundColor");
lookedUpFunc = true;
}
return func;
}
static CGColorRef
ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor)
{
CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a};
return CGColorCreate(aColorSpace, components);
}
static bool
SetFontSmoothingBackgroundColor(CGContextRef aCGContext, CGColorSpaceRef aColorSpace,
const GlyphRenderingOptions* aRenderingOptions)
{
if (aRenderingOptions) {
Color fontSmoothingBackgroundColor =
static_cast<const GlyphRenderingOptionsCG*>(aRenderingOptions)->FontSmoothingBackgroundColor();
if (fontSmoothingBackgroundColor.a > 0) {
CGContextSetFontSmoothingBackgroundColorFunc setFontSmoothingBGColorFunc =
GetCGContextSetFontSmoothingBackgroundColorFunc();
if (setFontSmoothingBGColorFunc) {
CGColorRef color = ColorToCGColor(aColorSpace, fontSmoothingBackgroundColor);
setFontSmoothingBGColorFunc(aCGContext, color);
CGColorRelease(color);
return true;
}
}
}
return false;
}
// Font rendering with a non-transparent font smoothing background color
// can leave pixels in our buffer where the rgb components exceed the alpha
// component. When this happens we need to clean up the data afterwards.
// The purpose of this is probably the following: Correct compositing of
// subpixel anti-aliased fonts on transparent backgrounds requires
// different alpha values per RGB component. Usually, premultiplied color
// values are derived by multiplying all components with the same per-pixel
// alpha value. However, if you multiply each component with a *different*
// alpha, and set the alpha component of the pixel to, say, the average
// of the alpha values that you used during the premultiplication of the
// RGB components, you can trick OVER compositing into doing a simplified
// form of component alpha compositing. (You just need to make sure to
// clamp the components of the result pixel to [0,255] afterwards.)
static void
EnsureValidPremultipliedData(CGContextRef aContext,
CGRect aTextBounds = CGRectInfinite)
{
if (CGBitmapContextGetBitsPerPixel(aContext) != 32 ||
CGBitmapContextGetAlphaInfo(aContext) != kCGImageAlphaPremultipliedFirst) {
return;
}
uint8_t* bitmapData = (uint8_t*)CGBitmapContextGetData(aContext);
CGRect bitmapBounds = CGRectMake(0, 0, CGBitmapContextGetWidth(aContext), CGBitmapContextGetHeight(aContext));
int stride = CGBitmapContextGetBytesPerRow(aContext);
CGRect bounds = CGRectIntersection(bitmapBounds, aTextBounds);
int startX = bounds.origin.x;
int endX = startX + bounds.size.width;
MOZ_ASSERT(endX <= bitmapBounds.size.width);
// CGRect assume that our origin is the bottom left.
// The data assumes that the origin is the top left.
// Have to switch the Y axis so that our coordinates are correct
int startY = bitmapBounds.size.height - (bounds.origin.y + bounds.size.height);
int endY = startY + bounds.size.height;
MOZ_ASSERT(endY <= (int)CGBitmapContextGetHeight(aContext));
for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) {
int i = y * stride + x * 4;
uint8_t a = bitmapData[i + 3];
bitmapData[i + 0] = std::min(a, bitmapData[i+0]);
bitmapData[i + 1] = std::min(a, bitmapData[i+1]);
bitmapData[i + 2] = std::min(a, bitmapData[i+2]);
}
}
}
static CGRect
ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale)
{
CGFloat x1, x2, y1, y2;
if (count < 1)
return CGRectZero;
x1 = bboxes[0].origin.x + positions[0].x;
x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width;
y1 = bboxes[0].origin.y + positions[0].y;
y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height;
// accumulate max and minimum coordinates
for (int i = 1; i < count; i++) {
x1 = std::min(x1, bboxes[i].origin.x + positions[i].x);
y1 = std::min(y1, bboxes[i].origin.y + positions[i].y);
x2 = std::max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width);
y2 = std::max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height);
}
CGRect extents = {{x1, y1}, {x2-x1, y2-y1}};
return extents;
}
} // namespace gfx
} // namespace mozilla
#endif

151
gfx/2d/Coord.h Normal file
View file

@ -0,0 +1,151 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_COORD_H_
#define MOZILLA_GFX_COORD_H_
#include "mozilla/Attributes.h"
#include "mozilla/TypeTraits.h" // For IsSame
#include "Types.h"
#include "BaseCoord.h"
#include <cmath>
namespace mozilla {
template <typename> struct IsPixel;
namespace gfx {
template <class units> struct IntCoordTyped;
template <class units, class F = Float> struct CoordTyped;
// CommonType<coord, primitive> is a metafunction that returns the type of the
// result of an arithmetic operation on the underlying type of a strongly-typed
// coordinate type 'coord', and a primitive type 'primitive'. C++ rules for
// arithmetic conversions are designed to avoid losing information - for
// example, the result of adding an int and a float is a float - and we want
// the same behaviour when mixing our coordinate types with primitive types.
// We get C++ to compute the desired result type using 'decltype'.
template <class coord, class primitive>
struct CommonType;
template <class units, class primitive>
struct CommonType<IntCoordTyped<units>, primitive> {
typedef decltype(int32_t() + primitive()) type;
};
template <class units, class F, class primitive>
struct CommonType<CoordTyped<units, F>, primitive> {
typedef decltype(F() + primitive()) type;
};
// This is a base class that provides mixed-type operator overloads between
// a strongly-typed Coord and a primitive value. It is needed to avoid
// ambiguities at mixed-type call sites, because Coord classes are implicitly
// convertible to their underlying value type. As we transition more of our code
// to strongly-typed classes, we may be able to remove some or all of these
// overloads.
template <bool B, class coord, class primitive>
struct CoordOperatorsHelper {
// Using SFINAE (Substitution Failure Is Not An Error) to suppress redundant
// operators
};
template <class coord, class primitive>
struct CoordOperatorsHelper<true, coord, primitive> {
friend bool operator==(coord aA, primitive aB) {
return aA.value == aB;
}
friend bool operator==(primitive aA, coord aB) {
return aA == aB.value;
}
friend bool operator!=(coord aA, primitive aB) {
return aA.value != aB;
}
friend bool operator!=(primitive aA, coord aB) {
return aA != aB.value;
}
typedef typename CommonType<coord, primitive>::type result_type;
friend result_type operator+(coord aA, primitive aB) {
return aA.value + aB;
}
friend result_type operator+(primitive aA, coord aB) {
return aA + aB.value;
}
friend result_type operator-(coord aA, primitive aB) {
return aA.value - aB;
}
friend result_type operator-(primitive aA, coord aB) {
return aA - aB.value;
}
friend result_type operator*(coord aCoord, primitive aScale) {
return aCoord.value * aScale;
}
friend result_type operator*(primitive aScale, coord aCoord) {
return aScale * aCoord.value;
}
friend result_type operator/(coord aCoord, primitive aScale) {
return aCoord.value / aScale;
}
// 'scale / coord' is intentionally omitted because it doesn't make sense.
};
// Note: 'IntCoordTyped<units>' and 'CoordTyped<units>' do not derive from
// 'units' to work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61959.
template<class units>
struct IntCoordTyped :
public BaseCoord< int32_t, IntCoordTyped<units> >,
public CoordOperatorsHelper< true, IntCoordTyped<units>, float >,
public CoordOperatorsHelper< true, IntCoordTyped<units>, double > {
static_assert(IsPixel<units>::value,
"'units' must be a coordinate system tag");
typedef BaseCoord< int32_t, IntCoordTyped<units> > Super;
constexpr IntCoordTyped() : Super() {}
constexpr MOZ_IMPLICIT IntCoordTyped(int32_t aValue) : Super(aValue) {}
};
template<class units, class F>
struct CoordTyped :
public BaseCoord< F, CoordTyped<units, F> >,
public CoordOperatorsHelper< !IsSame<F, int32_t>::value, CoordTyped<units, F>, int32_t >,
public CoordOperatorsHelper< !IsSame<F, uint32_t>::value, CoordTyped<units, F>, uint32_t >,
public CoordOperatorsHelper< !IsSame<F, double>::value, CoordTyped<units, F>, double >,
public CoordOperatorsHelper< !IsSame<F, float>::value, CoordTyped<units, F>, float > {
static_assert(IsPixel<units>::value,
"'units' must be a coordinate system tag");
typedef BaseCoord< F, CoordTyped<units, F> > Super;
constexpr CoordTyped() : Super() {}
constexpr MOZ_IMPLICIT CoordTyped(F aValue) : Super(aValue) {}
explicit constexpr CoordTyped(const IntCoordTyped<units>& aCoord) : Super(F(aCoord.value)) {}
void Round() {
this->value = floor(this->value + 0.5);
}
void Truncate() {
this->value = int32_t(this->value);
}
IntCoordTyped<units> Rounded() const {
return IntCoordTyped<units>(int32_t(floor(this->value + 0.5)));
}
IntCoordTyped<units> Truncated() const {
return IntCoordTyped<units>(int32_t(this->value));
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_COORD_H_ */

80
gfx/2d/CriticalSection.h Normal file
View file

@ -0,0 +1,80 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_CRITICALSECTION_H_
#define MOZILLA_GFX_CRITICALSECTION_H_
#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#include "mozilla/DebugOnly.h"
#endif
namespace mozilla {
namespace gfx {
#ifdef WIN32
class CriticalSection {
public:
CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); }
~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); }
void Enter() { ::EnterCriticalSection(&mCriticalSection); }
void Leave() { ::LeaveCriticalSection(&mCriticalSection); }
protected:
CRITICAL_SECTION mCriticalSection;
};
#else
// posix
class PosixCondvar;
class CriticalSection {
public:
CriticalSection() {
DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr);
MOZ_ASSERT(!err);
}
~CriticalSection() {
DebugOnly<int> err = pthread_mutex_destroy(&mMutex);
MOZ_ASSERT(!err);
}
void Enter() {
DebugOnly<int> err = pthread_mutex_lock(&mMutex);
MOZ_ASSERT(!err);
}
void Leave() {
DebugOnly<int> err = pthread_mutex_unlock(&mMutex);
MOZ_ASSERT(!err);
}
protected:
pthread_mutex_t mMutex;
friend class PosixCondVar;
};
#endif
/// RAII helper.
struct CriticalSectionAutoEnter {
explicit CriticalSectionAutoEnter(CriticalSection* aSection) : mSection(aSection) { mSection->Enter(); }
~CriticalSectionAutoEnter() { mSection->Leave(); }
protected:
CriticalSection* mSection;
};
} // namespace
} // namespace
#endif

View file

@ -0,0 +1,21 @@
/* -*- 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 "2D.h"
#include "DataSourceSurfaceWrapper.h"
namespace mozilla {
namespace gfx {
already_AddRefed<DataSourceSurface>
DataSourceSurface::GetDataSurface()
{
RefPtr<DataSourceSurface> surface =
(GetType() == SurfaceType::DATA) ? this : new DataSourceSurfaceWrapper(this);
return surface.forget();
}
} // namespace gfx
} // namespace mozilla

View file

@ -0,0 +1,39 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_
#define MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_
#include "2D.h"
namespace mozilla {
namespace gfx {
// Wraps a DataSourceSurface and forwards all methods except for GetType(),
// from which it always returns SurfaceType::DATA.
class DataSourceSurfaceWrapper : public DataSourceSurface
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceWrapper, override)
explicit DataSourceSurfaceWrapper(DataSourceSurface *aSurface)
: mSurface(aSurface)
{}
virtual SurfaceType GetType() const override { return SurfaceType::DATA; }
virtual uint8_t *GetData() override { return mSurface->GetData(); }
virtual int32_t Stride() override { return mSurface->Stride(); }
virtual IntSize GetSize() const override { return mSurface->GetSize(); }
virtual SurfaceFormat GetFormat() const override { return mSurface->GetFormat(); }
virtual bool IsValid() const override { return mSurface->IsValid(); }
private:
RefPtr<DataSourceSurface> mSurface;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ */

View file

@ -0,0 +1,374 @@
/* -*- 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 <cstring>
#include "2D.h"
#include "DataSurfaceHelpers.h"
#include "Logging.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PodOperations.h"
#include "Tools.h"
namespace mozilla {
namespace gfx {
already_AddRefed<DataSourceSurface>
CreateDataSourceSurfaceFromData(const IntSize& aSize,
SurfaceFormat aFormat,
const uint8_t* aData,
int32_t aDataStride)
{
RefPtr<DataSourceSurface> srcSurface =
Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData),
aDataStride,
aSize,
aFormat);
RefPtr<DataSourceSurface> destSurface =
Factory::CreateDataSourceSurface(aSize, aFormat, false);
if (!srcSurface || !destSurface) {
return nullptr;
}
if (CopyRect(srcSurface,
destSurface,
IntRect(IntPoint(), srcSurface->GetSize()),
IntPoint())) {
return destSurface.forget();
}
return nullptr;
}
already_AddRefed<DataSourceSurface>
CreateDataSourceSurfaceWithStrideFromData(const IntSize &aSize,
SurfaceFormat aFormat,
int32_t aStride,
const uint8_t* aData,
int32_t aDataStride)
{
RefPtr<DataSourceSurface> srcSurface =
Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData),
aDataStride,
aSize,
aFormat);
RefPtr<DataSourceSurface> destSurface =
Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, aStride, false);
if (!srcSurface || !destSurface) {
return nullptr;
}
if (CopyRect(srcSurface,
destSurface,
IntRect(IntPoint(), srcSurface->GetSize()),
IntPoint())) {
return destSurface.forget();
}
return nullptr;
}
uint8_t*
DataAtOffset(DataSourceSurface* aSurface,
const DataSourceSurface::MappedSurface* aMap,
IntPoint aPoint)
{
if (!SurfaceContainsPoint(aSurface, aPoint)) {
MOZ_CRASH("GFX: sample position needs to be inside surface!");
}
MOZ_ASSERT(Factory::CheckSurfaceSize(aSurface->GetSize()),
"surface size overflows - this should have been prevented when the surface was created");
uint8_t* data = aMap->mData + aPoint.y * aMap->mStride +
aPoint.x * BytesPerPixel(aSurface->GetFormat());
if (data < aMap->mData) {
MOZ_CRASH("GFX: out-of-range data access");
}
return data;
}
// This check is safe against integer overflow.
bool
SurfaceContainsPoint(SourceSurface* aSurface, const IntPoint& aPoint)
{
IntSize size = aSurface->GetSize();
return aPoint.x >= 0 && aPoint.x < size.width &&
aPoint.y >= 0 && aPoint.y < size.height;
}
void
ConvertBGRXToBGRA(uint8_t* aData, const IntSize &aSize, const int32_t aStride)
{
int height = aSize.height, width = aSize.width * 4;
for (int row = 0; row < height; ++row) {
for (int column = 0; column < width; column += 4) {
#ifdef IS_BIG_ENDIAN
aData[column] = 0xFF;
#else
aData[column + 3] = 0xFF;
#endif
}
aData += aStride;
}
}
void
CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst, IntSize aSrcSize,
int32_t aSrcStride, int32_t aBytesPerPixel)
{
MOZ_ASSERT(aBytesPerPixel > 0,
"Negative stride for aDst not currently supported");
MOZ_ASSERT(BufferSizeFromStrideAndHeight(aSrcStride, aSrcSize.height) > 0,
"How did we end up with a surface with such a big buffer?");
int packedStride = aSrcSize.width * aBytesPerPixel;
if (aSrcStride == packedStride) {
// aSrc is already packed, so we can copy with a single memcpy.
memcpy(aDst, aSrc, packedStride * aSrcSize.height);
} else {
// memcpy one row at a time.
for (int row = 0; row < aSrcSize.height; ++row) {
memcpy(aDst, aSrc, packedStride);
aSrc += aSrcStride;
aDst += packedStride;
}
}
}
void
CopyBGRXSurfaceDataToPackedBGRArray(uint8_t* aSrc, uint8_t* aDst,
IntSize aSrcSize, int32_t aSrcStride)
{
int packedStride = aSrcSize.width * 3;
uint8_t* srcPx = aSrc;
uint8_t* dstPx = aDst;
for (int row = 0; row < aSrcSize.height; ++row) {
for (int col = 0; col < aSrcSize.width; ++col) {
dstPx[0] = srcPx[0];
dstPx[1] = srcPx[1];
dstPx[2] = srcPx[2];
// srcPx[3] (unused or alpha component) dropped on floor
srcPx += 4;
dstPx += 3;
}
srcPx = aSrc += aSrcStride;
dstPx = aDst += packedStride;
}
}
UniquePtr<uint8_t[]>
SurfaceToPackedBGRA(DataSourceSurface *aSurface)
{
SurfaceFormat format = aSurface->GetFormat();
if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) {
return nullptr;
}
IntSize size = aSurface->GetSize();
UniquePtr<uint8_t[]> imageBuffer(
new (std::nothrow) uint8_t[size.width * size.height * sizeof(uint32_t)]);
if (!imageBuffer) {
return nullptr;
}
DataSourceSurface::MappedSurface map;
if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
return nullptr;
}
CopySurfaceDataToPackedArray(map.mData, imageBuffer.get(), size,
map.mStride, 4 * sizeof(uint8_t));
aSurface->Unmap();
if (format == SurfaceFormat::B8G8R8X8) {
// Convert BGRX to BGRA by setting a to 255.
ConvertBGRXToBGRA(imageBuffer.get(), size, size.width * sizeof(uint32_t));
}
return imageBuffer;
}
uint8_t*
SurfaceToPackedBGR(DataSourceSurface *aSurface)
{
SurfaceFormat format = aSurface->GetFormat();
MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8, "Format not supported");
if (format != SurfaceFormat::B8G8R8X8) {
// To support B8G8R8A8 we'd need to un-pre-multiply alpha
return nullptr;
}
IntSize size = aSurface->GetSize();
uint8_t* imageBuffer = new (std::nothrow) uint8_t[size.width * size.height * 3 * sizeof(uint8_t)];
if (!imageBuffer) {
return nullptr;
}
DataSourceSurface::MappedSurface map;
if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
delete [] imageBuffer;
return nullptr;
}
CopyBGRXSurfaceDataToPackedBGRArray(map.mData, imageBuffer, size,
map.mStride);
aSurface->Unmap();
return imageBuffer;
}
void
ClearDataSourceSurface(DataSourceSurface *aSurface)
{
DataSourceSurface::MappedSurface map;
if (!aSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
MOZ_ASSERT(false, "Failed to map DataSourceSurface");
return;
}
// We avoid writing into the gaps between the rows here since we can't be
// sure that some drivers don't use those bytes.
uint32_t width = aSurface->GetSize().width;
uint32_t bytesPerRow = width * BytesPerPixel(aSurface->GetFormat());
uint8_t* row = map.mData;
// converting to size_t here because otherwise the temporaries can overflow
// and we can end up with |end| being a bad address!
uint8_t* end = row + size_t(map.mStride) * size_t(aSurface->GetSize().height);
while (row != end) {
memset(row, 0, bytesPerRow);
row += map.mStride;
}
aSurface->Unmap();
}
size_t
BufferSizeFromStrideAndHeight(int32_t aStride,
int32_t aHeight,
int32_t aExtraBytes)
{
if (MOZ_UNLIKELY(aHeight <= 0) || MOZ_UNLIKELY(aStride <= 0)) {
return 0;
}
// We limit the length returned to values that can be represented by int32_t
// because we don't want to allocate buffers any bigger than that. This
// allows for a buffer size of over 2 GiB which is already rediculously
// large and will make the process janky. (Note the choice of the signed type
// is deliberate because we specifically don't want the returned value to
// overflow if someone stores the buffer length in an int32_t variable.)
CheckedInt32 requiredBytes =
CheckedInt32(aStride) * CheckedInt32(aHeight) + CheckedInt32(aExtraBytes);
if (MOZ_UNLIKELY(!requiredBytes.isValid())) {
gfxWarning() << "Buffer size too big; returning zero " << aStride << ", " << aHeight << ", " << aExtraBytes;
return 0;
}
return requiredBytes.value();
}
size_t
BufferSizeFromDimensions(int32_t aWidth,
int32_t aHeight,
int32_t aDepth,
int32_t aExtraBytes)
{
if (MOZ_UNLIKELY(aHeight <= 0) ||
MOZ_UNLIKELY(aWidth <= 0) ||
MOZ_UNLIKELY(aDepth <= 0)) {
return 0;
}
// Similar to BufferSizeFromStrideAndHeight, but with an extra parameter.
CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * CheckedInt32(aDepth) + CheckedInt32(aExtraBytes);
if (MOZ_UNLIKELY(!requiredBytes.isValid())) {
gfxWarning() << "Buffer size too big; returning zero " << aWidth << ", " << aHeight << ", " << aDepth << ", " << aExtraBytes;
return 0;
}
return requiredBytes.value();
}
/**
* aSrcRect: Rect relative to the aSrc surface
* aDestPoint: Point inside aDest surface
*/
bool
CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest,
IntRect aSrcRect, IntPoint aDestPoint)
{
if (aSrcRect.Overflows() ||
IntRect(aDestPoint, aSrcRect.Size()).Overflows()) {
MOZ_CRASH("GFX: we should never be getting invalid rects at this point");
}
MOZ_RELEASE_ASSERT(aSrc->GetFormat() == aDest->GetFormat(),
"GFX: different surface formats");
MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aSrc->GetSize()).Contains(aSrcRect),
"GFX: source rect too big for source surface");
MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aDest->GetSize()).Contains(IntRect(aDestPoint, aSrcRect.Size())),
"GFX: dest surface too small");
if (aSrcRect.IsEmpty()) {
return false;
}
DataSourceSurface::ScopedMap srcMap(aSrc, DataSourceSurface::READ);
DataSourceSurface::ScopedMap destMap(aDest, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!srcMap.IsMapped() || !destMap.IsMapped())) {
return false;
}
uint8_t* sourceData = DataAtOffset(aSrc, srcMap.GetMappedSurface(), aSrcRect.TopLeft());
uint32_t sourceStride = srcMap.GetStride();
uint8_t* destData = DataAtOffset(aDest, destMap.GetMappedSurface(), aDestPoint);
uint32_t destStride = destMap.GetStride();
if (BytesPerPixel(aSrc->GetFormat()) == 4) {
for (int32_t y = 0; y < aSrcRect.height; y++) {
PodCopy((int32_t*)destData, (int32_t*)sourceData, aSrcRect.width);
sourceData += sourceStride;
destData += destStride;
}
} else if (BytesPerPixel(aSrc->GetFormat()) == 1) {
for (int32_t y = 0; y < aSrcRect.height; y++) {
PodCopy(destData, sourceData, aSrcRect.width);
sourceData += sourceStride;
destData += destStride;
}
}
return true;
}
already_AddRefed<DataSourceSurface>
CreateDataSourceSurfaceByCloning(DataSourceSurface* aSource)
{
RefPtr<DataSourceSurface> copy =
Factory::CreateDataSourceSurface(aSource->GetSize(), aSource->GetFormat(), true);
if (copy) {
CopyRect(aSource, copy, IntRect(IntPoint(), aSource->GetSize()), IntPoint());
}
return copy.forget();
}
} // namespace gfx
} // namespace mozilla

147
gfx/2d/DataSurfaceHelpers.h Normal file
View file

@ -0,0 +1,147 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_DATASURFACEHELPERS_H
#define _MOZILLA_GFX_DATASURFACEHELPERS_H
#include "2D.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
namespace gfx {
/**
* Create a DataSourceSurface and init the surface with the |aData|. The stride
* of this source surface might be different from the input data's |aDataStride|.
* System will try to use the optimal one.
*/
already_AddRefed<DataSourceSurface>
CreateDataSourceSurfaceFromData(const IntSize& aSize,
SurfaceFormat aFormat,
const uint8_t* aData,
int32_t aDataStride);
/**
* Similar to CreateDataSourceSurfaceFromData(), but could setup the stride for
* this surface.
*/
already_AddRefed<DataSourceSurface>
CreateDataSourceSurfaceWithStrideFromData(const IntSize &aSize,
SurfaceFormat aFormat,
int32_t aStride,
const uint8_t* aData,
int32_t aDataStride);
void
ConvertBGRXToBGRA(uint8_t* aData, const IntSize &aSize, const int32_t aStride);
/**
* Copy the pixel data from aSrc and pack it into aDst. aSrcSize, aSrcStride
* and aBytesPerPixel give the size, stride and bytes per pixel for aSrc's
* surface. Callers are responsible for making sure that aDst is big enough to
* contain |aSrcSize.width * aSrcSize.height * aBytesPerPixel| bytes.
*/
void
CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst, IntSize aSrcSize,
int32_t aSrcStride, int32_t aBytesPerPixel);
/**
* Convert aSurface to a packed buffer in BGRA format.
*/
UniquePtr<uint8_t[]>
SurfaceToPackedBGRA(DataSourceSurface *aSurface);
/**
* Convert aSurface to a packed buffer in BGR format. The pixel data is
* returned in a buffer allocated with new uint8_t[]. The caller then has
* ownership of the buffer and is responsible for delete[]'ing it.
*
* This function is currently only intended for use with surfaces of format
* SurfaceFormat::B8G8R8X8 since the X components of the pixel data (if any)
* are simply dropped (no attempt is made to un-pre-multiply alpha from the
* color components).
*/
uint8_t*
SurfaceToPackedBGR(DataSourceSurface *aSurface);
/**
* Clears all the bytes in a DataSourceSurface's data array to zero (so to
* transparent black for SurfaceFormat::B8G8R8A8, for example).
* Note that DataSourceSurfaces can be initialized to zero, which is
* more efficient than zeroing the surface after initialization.
*/
void
ClearDataSourceSurface(DataSourceSurface *aSurface);
/**
* Multiplies aStride and aHeight and makes sure the result is limited to
* something sane. To keep things consistent, this should always be used
* wherever we allocate a buffer based on surface stride and height.
*
* @param aExtra Optional argument to specify an additional number of trailing
* bytes (useful for creating intermediate surfaces for filters, for
* example).
*
* @return The result of the multiplication if it is acceptable, or else zero.
*/
size_t
BufferSizeFromStrideAndHeight(int32_t aStride,
int32_t aHeight,
int32_t aExtraBytes = 0);
/**
* Multiplies aWidth, aHeight, aDepth and makes sure the result is limited to
* something sane. To keep things consistent, this should always be used
* wherever we allocate a buffer based on surface dimensions.
*
* @param aExtra Optional argument to specify an additional number of trailing
* bytes (useful for creating intermediate surfaces for filters, for
* example).
*
* @return The result of the multiplication if it is acceptable, or else zero.
*/
size_t
BufferSizeFromDimensions(int32_t aWidth,
int32_t aHeight,
int32_t aDepth,
int32_t aExtraBytes = 0);
/**
* Copy aSrcRect from aSrc to aDest starting at aDestPoint.
* @returns false if the copy is not successful or the aSrc's size is empty.
*/
bool
CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest,
IntRect aSrcRect, IntPoint aDestPoint);
/**
* Create a non aliasing copy of aSource. This creates a new DataSourceSurface
* using the factory and copies the bits.
*
* @return a dss allocated by Factory that contains a copy a aSource.
*/
already_AddRefed<DataSourceSurface>
CreateDataSourceSurfaceByCloning(DataSourceSurface* aSource);
/**
* Return the byte at aPoint.
*/
uint8_t*
DataAtOffset(DataSourceSurface* aSurface,
const DataSourceSurface::MappedSurface* aMap,
IntPoint aPoint);
/**
* Check if aPoint is contained by the surface.
*
* @returns true if and only if aPoint is inside the surface.
*/
bool
SurfaceContainsPoint(SourceSurface* aSurface, const IntPoint& aPoint);
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_DATASURFACEHELPERS_H

594
gfx/2d/DrawCommand.h Normal file
View file

@ -0,0 +1,594 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_DRAWCOMMAND_H_
#define MOZILLA_GFX_DRAWCOMMAND_H_
#include <math.h>
#include "2D.h"
#include "Filters.h"
#include <vector>
namespace mozilla {
namespace gfx {
enum class CommandType : int8_t {
DRAWSURFACE = 0,
DRAWFILTER,
DRAWSURFACEWITHSHADOW,
CLEARRECT,
COPYSURFACE,
COPYRECT,
FILLRECT,
STROKERECT,
STROKELINE,
STROKE,
FILL,
FILLGLYPHS,
MASK,
MASKSURFACE,
PUSHCLIP,
PUSHCLIPRECT,
POPCLIP,
SETTRANSFORM,
FLUSH
};
class DrawingCommand
{
public:
virtual ~DrawingCommand() {}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform = nullptr) const = 0;
virtual bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const { return false; }
protected:
explicit DrawingCommand(CommandType aType)
: mType(aType)
{
}
CommandType GetType() { return mType; }
private:
CommandType mType;
};
class StoredPattern
{
public:
explicit StoredPattern(const Pattern& aPattern)
{
Assign(aPattern);
}
void Assign(const Pattern& aPattern)
{
switch (aPattern.GetType()) {
case PatternType::COLOR:
new (mColor)ColorPattern(*static_cast<const ColorPattern*>(&aPattern));
return;
case PatternType::SURFACE:
{
SurfacePattern* surfPat = new (mSurface)SurfacePattern(*static_cast<const SurfacePattern*>(&aPattern));
surfPat->mSurface->GuaranteePersistance();
return;
}
case PatternType::LINEAR_GRADIENT:
new (mLinear)LinearGradientPattern(*static_cast<const LinearGradientPattern*>(&aPattern));
return;
case PatternType::RADIAL_GRADIENT:
new (mRadial)RadialGradientPattern(*static_cast<const RadialGradientPattern*>(&aPattern));
return;
}
}
~StoredPattern()
{
reinterpret_cast<Pattern*>(mPattern)->~Pattern();
}
operator Pattern&()
{
return *reinterpret_cast<Pattern*>(mPattern);
}
operator const Pattern&() const
{
return *reinterpret_cast<const Pattern*>(mPattern);
}
StoredPattern(const StoredPattern& aPattern)
{
Assign(aPattern);
}
private:
StoredPattern operator=(const StoredPattern& aOther)
{
// Block this so that we notice if someone's doing excessive assigning.
return *this;
}
union {
char mPattern[sizeof(Pattern)];
char mColor[sizeof(ColorPattern)];
char mLinear[sizeof(LinearGradientPattern)];
char mRadial[sizeof(RadialGradientPattern)];
char mSurface[sizeof(SurfacePattern)];
};
};
class DrawSurfaceCommand : public DrawingCommand
{
public:
DrawSurfaceCommand(SourceSurface *aSurface, const Rect& aDest,
const Rect& aSource, const DrawSurfaceOptions& aSurfOptions,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::DRAWSURFACE)
, mSurface(aSurface), mDest(aDest)
, mSource(aSource), mSurfOptions(aSurfOptions)
, mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->DrawSurface(mSurface, mDest, mSource, mSurfOptions, mOptions);
}
private:
RefPtr<SourceSurface> mSurface;
Rect mDest;
Rect mSource;
DrawSurfaceOptions mSurfOptions;
DrawOptions mOptions;
};
class DrawFilterCommand : public DrawingCommand
{
public:
DrawFilterCommand(FilterNode* aFilter, const Rect& aSourceRect,
const Point& aDestPoint, const DrawOptions& aOptions)
: DrawingCommand(CommandType::DRAWSURFACE)
, mFilter(aFilter), mSourceRect(aSourceRect)
, mDestPoint(aDestPoint), mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions);
}
private:
RefPtr<FilterNode> mFilter;
Rect mSourceRect;
Point mDestPoint;
DrawOptions mOptions;
};
class ClearRectCommand : public DrawingCommand
{
public:
explicit ClearRectCommand(const Rect& aRect)
: DrawingCommand(CommandType::CLEARRECT)
, mRect(aRect)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->ClearRect(mRect);
}
private:
Rect mRect;
};
class CopySurfaceCommand : public DrawingCommand
{
public:
CopySurfaceCommand(SourceSurface* aSurface,
const IntRect& aSourceRect,
const IntPoint& aDestination)
: DrawingCommand(CommandType::COPYSURFACE)
, mSurface(aSurface)
, mSourceRect(aSourceRect)
, mDestination(aDestination)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform) const
{
MOZ_ASSERT(!aTransform || !aTransform->HasNonIntegerTranslation());
Point dest(Float(mDestination.x), Float(mDestination.y));
if (aTransform) {
dest = aTransform->TransformPoint(dest);
}
aDT->CopySurface(mSurface, mSourceRect, IntPoint(uint32_t(dest.x), uint32_t(dest.y)));
}
private:
RefPtr<SourceSurface> mSurface;
IntRect mSourceRect;
IntPoint mDestination;
};
class FillRectCommand : public DrawingCommand
{
public:
FillRectCommand(const Rect& aRect,
const Pattern& aPattern,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::FILLRECT)
, mRect(aRect)
, mPattern(aPattern)
, mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->FillRect(mRect, mPattern, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = aTransform.TransformBounds(mRect);
return true;
}
private:
Rect mRect;
StoredPattern mPattern;
DrawOptions mOptions;
};
class StrokeRectCommand : public DrawingCommand
{
public:
StrokeRectCommand(const Rect& aRect,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::STROKERECT)
, mRect(aRect)
, mPattern(aPattern)
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
if (aStrokeOptions.mDashLength) {
mDashes.resize(aStrokeOptions.mDashLength);
mStrokeOptions.mDashPattern = &mDashes.front();
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
}
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->StrokeRect(mRect, mPattern, mStrokeOptions, mOptions);
}
private:
Rect mRect;
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
std::vector<Float> mDashes;
};
class StrokeLineCommand : public DrawingCommand
{
public:
StrokeLineCommand(const Point& aStart,
const Point& aEnd,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::STROKELINE)
, mStart(aStart)
, mEnd(aEnd)
, mPattern(aPattern)
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->StrokeLine(mStart, mEnd, mPattern, mStrokeOptions, mOptions);
}
private:
Point mStart;
Point mEnd;
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
};
class FillCommand : public DrawingCommand
{
public:
FillCommand(const Path* aPath,
const Pattern& aPattern,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::FILL)
, mPath(const_cast<Path*>(aPath))
, mPattern(aPattern)
, mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Fill(mPath, mPattern, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = mPath->GetBounds(aTransform);
return true;
}
private:
RefPtr<Path> mPath;
StoredPattern mPattern;
DrawOptions mOptions;
};
#ifndef M_SQRT2
#define M_SQRT2 1.41421356237309504880
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039
#endif
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
static Rect
PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions,
const Rect &aRect,
const Matrix &aTransform)
{
double styleExpansionFactor = 0.5f;
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
styleExpansionFactor = M_SQRT1_2;
}
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
}
styleExpansionFactor *= aStrokeOptions.mLineWidth;
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
Rect result = aRect;
result.Inflate(dx, dy);
return result;
}
class StrokeCommand : public DrawingCommand
{
public:
StrokeCommand(const Path* aPath,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::STROKE)
, mPath(const_cast<Path*>(aPath))
, mPattern(aPattern)
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
if (aStrokeOptions.mDashLength) {
mDashes.resize(aStrokeOptions.mDashLength);
mStrokeOptions.mDashPattern = &mDashes.front();
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
}
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Stroke(mPath, mPattern, mStrokeOptions, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = PathExtentsToMaxStrokeExtents(mStrokeOptions, mPath->GetBounds(aTransform), aTransform);
return true;
}
private:
RefPtr<Path> mPath;
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
std::vector<Float> mDashes;
};
class FillGlyphsCommand : public DrawingCommand
{
public:
FillGlyphsCommand(ScaledFont* aFont,
const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const DrawOptions& aOptions,
const GlyphRenderingOptions* aRenderingOptions)
: DrawingCommand(CommandType::FILLGLYPHS)
, mFont(aFont)
, mPattern(aPattern)
, mOptions(aOptions)
, mRenderingOptions(const_cast<GlyphRenderingOptions*>(aRenderingOptions))
{
mGlyphs.resize(aBuffer.mNumGlyphs);
memcpy(&mGlyphs.front(), aBuffer.mGlyphs, sizeof(Glyph) * aBuffer.mNumGlyphs);
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
GlyphBuffer buf;
buf.mNumGlyphs = mGlyphs.size();
buf.mGlyphs = &mGlyphs.front();
aDT->FillGlyphs(mFont, buf, mPattern, mOptions, mRenderingOptions);
}
private:
RefPtr<ScaledFont> mFont;
std::vector<Glyph> mGlyphs;
StoredPattern mPattern;
DrawOptions mOptions;
RefPtr<GlyphRenderingOptions> mRenderingOptions;
};
class MaskCommand : public DrawingCommand
{
public:
MaskCommand(const Pattern& aSource,
const Pattern& aMask,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::MASK)
, mSource(aSource)
, mMask(aMask)
, mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Mask(mSource, mMask, mOptions);
}
private:
StoredPattern mSource;
StoredPattern mMask;
DrawOptions mOptions;
};
class MaskSurfaceCommand : public DrawingCommand
{
public:
MaskSurfaceCommand(const Pattern& aSource,
const SourceSurface* aMask,
const Point& aOffset,
const DrawOptions& aOptions)
: DrawingCommand(CommandType::MASKSURFACE)
, mSource(aSource)
, mMask(const_cast<SourceSurface*>(aMask))
, mOffset(aOffset)
, mOptions(aOptions)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->MaskSurface(mSource, mMask, mOffset, mOptions);
}
private:
StoredPattern mSource;
RefPtr<SourceSurface> mMask;
Point mOffset;
DrawOptions mOptions;
};
class PushClipCommand : public DrawingCommand
{
public:
explicit PushClipCommand(const Path* aPath)
: DrawingCommand(CommandType::PUSHCLIP)
, mPath(const_cast<Path*>(aPath))
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->PushClip(mPath);
}
private:
RefPtr<Path> mPath;
};
class PushClipRectCommand : public DrawingCommand
{
public:
explicit PushClipRectCommand(const Rect& aRect)
: DrawingCommand(CommandType::PUSHCLIPRECT)
, mRect(aRect)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->PushClipRect(mRect);
}
private:
Rect mRect;
};
class PopClipCommand : public DrawingCommand
{
public:
PopClipCommand()
: DrawingCommand(CommandType::POPCLIP)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->PopClip();
}
};
class SetTransformCommand : public DrawingCommand
{
public:
explicit SetTransformCommand(const Matrix& aTransform)
: DrawingCommand(CommandType::SETTRANSFORM)
, mTransform(aTransform)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aMatrix) const
{
if (aMatrix) {
aDT->SetTransform(mTransform * (*aMatrix));
} else {
aDT->SetTransform(mTransform);
}
}
private:
Matrix mTransform;
};
class FlushCommand : public DrawingCommand
{
public:
explicit FlushCommand()
: DrawingCommand(CommandType::FLUSH)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Flush();
}
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_DRAWCOMMAND_H_ */

View file

@ -0,0 +1,117 @@
/* -*- 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 "DrawEventRecorder.h"
#include "PathRecording.h"
#include "RecordingTypes.h"
namespace mozilla {
namespace gfx {
using namespace std;
DrawEventRecorderPrivate::DrawEventRecorderPrivate(std::ostream *aStream)
: mOutputStream(aStream)
{
}
void
DrawEventRecorderPrivate::WriteHeader()
{
WriteElement(*mOutputStream, kMagicInt);
WriteElement(*mOutputStream, kMajorRevision);
WriteElement(*mOutputStream, kMinorRevision);
}
void
DrawEventRecorderPrivate::RecordEvent(const RecordedEvent &aEvent)
{
WriteElement(*mOutputStream, aEvent.mType);
aEvent.RecordToStream(*mOutputStream);
Flush();
}
DrawEventRecorderFile::DrawEventRecorderFile(const char *aFilename)
: DrawEventRecorderPrivate(nullptr)
, mOutputFile(aFilename, ofstream::binary)
{
mOutputStream = &mOutputFile;
WriteHeader();
}
DrawEventRecorderFile::~DrawEventRecorderFile()
{
mOutputFile.close();
}
void
DrawEventRecorderFile::Flush()
{
mOutputFile.flush();
}
bool
DrawEventRecorderFile::IsOpen()
{
return mOutputFile.is_open();
}
void
DrawEventRecorderFile::OpenNew(const char *aFilename)
{
MOZ_ASSERT(!mOutputFile.is_open());
mOutputFile.open(aFilename, ofstream::binary);
WriteHeader();
}
void
DrawEventRecorderFile::Close()
{
MOZ_ASSERT(mOutputFile.is_open());
mOutputFile.close();
}
DrawEventRecorderMemory::DrawEventRecorderMemory()
: DrawEventRecorderPrivate(nullptr)
{
mOutputStream = &mMemoryStream;
WriteHeader();
}
void
DrawEventRecorderMemory::Flush()
{
mOutputStream->flush();
}
size_t
DrawEventRecorderMemory::RecordingSize()
{
return mMemoryStream.tellp();
}
bool
DrawEventRecorderMemory::CopyRecording(char* aBuffer, size_t aBufferLen)
{
return !!mMemoryStream.read(aBuffer, aBufferLen);
}
void
DrawEventRecorderMemory::WipeRecording()
{
mMemoryStream.str(std::string());
mMemoryStream.clear();
WriteHeader();
}
} // namespace gfx
} // namespace mozilla

148
gfx/2d/DrawEventRecorder.h Normal file
View file

@ -0,0 +1,148 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_DRAWEVENTRECORDER_H_
#define MOZILLA_GFX_DRAWEVENTRECORDER_H_
#include "2D.h"
#include "RecordedEvent.h"
#include <ostream>
#include <fstream>
#if defined(_MSC_VER)
#include <unordered_set>
#else
#include <set>
#endif
namespace mozilla {
namespace gfx {
class PathRecording;
class DrawEventRecorderPrivate : public DrawEventRecorder
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPrivate)
explicit DrawEventRecorderPrivate(std::ostream *aStream);
virtual ~DrawEventRecorderPrivate() { }
void WriteHeader();
void RecordEvent(const RecordedEvent &aEvent);
void WritePath(const PathRecording *aPath);
void AddStoredObject(const ReferencePtr aObject) {
mStoredObjects.insert(aObject);
}
void RemoveStoredObject(const ReferencePtr aObject) {
mStoredObjects.erase(aObject);
}
bool HasStoredObject(const ReferencePtr aObject) {
return mStoredObjects.find(aObject) != mStoredObjects.end();
}
void AddStoredFontData(const uint64_t aFontDataKey) {
mStoredFontData.insert(aFontDataKey);
}
bool HasStoredFontData(const uint64_t aFontDataKey) {
return mStoredFontData.find(aFontDataKey) != mStoredFontData.end();
}
protected:
std::ostream *mOutputStream;
virtual void Flush() = 0;
#if defined(_MSC_VER)
typedef std::unordered_set<const void*> ObjectSet;
typedef std::unordered_set<uint64_t> Uint64Set;
#else
typedef std::set<const void*> ObjectSet;
typedef std::set<uint64_t> Uint64Set;
#endif
ObjectSet mStoredObjects;
Uint64Set mStoredFontData;
};
class DrawEventRecorderFile : public DrawEventRecorderPrivate
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderFile)
explicit DrawEventRecorderFile(const char *aFilename);
~DrawEventRecorderFile();
/**
* Returns whether a recording file is currently open.
*/
bool IsOpen();
/**
* Opens new file with the provided name. The recorder does NOT forget which
* objects it has recorded. This can be used with Close, so that a recording
* can be processed in chunks. The file must not already be open.
*/
void OpenNew(const char *aFilename);
/**
* Closes the file so that it can be processed. The recorder does NOT forget
* which objects it has recorded. This can be used with OpenNew, so that a
* recording can be processed in chunks. The file must be open.
*/
void Close();
private:
virtual void Flush();
std::ofstream mOutputFile;
};
class DrawEventRecorderMemory final : public DrawEventRecorderPrivate
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderMemory)
/**
* Constructs a DrawEventRecorder that stores the recording in memory.
*/
DrawEventRecorderMemory();
/**
* @return the current size of the recording (in chars).
*/
size_t RecordingSize();
/**
* Copies at most aBufferLen chars of the recording into aBuffer.
*
* @param aBuffer buffer to receive the recording chars
* @param aBufferLen length of aBuffer
* @return true if copied successfully
*/
bool CopyRecording(char* aBuffer, size_t aBufferLen);
/**
* Wipes the internal recording buffer, but the recorder does NOT forget which
* objects it has recorded. This can be used so that a recording can be copied
* and processed in chunks, releasing memory as it goes.
*/
void WipeRecording();
private:
~DrawEventRecorderMemory() {};
void Flush() final;
std::stringstream mMemoryStream;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_DRAWEVENTRECORDER_H_ */

56
gfx/2d/DrawTarget.cpp Normal file
View file

@ -0,0 +1,56 @@
/* -*- 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 "2D.h"
#include "Logging.h"
#include "PathHelpers.h"
#include "DrawTargetCapture.h"
namespace mozilla {
namespace gfx {
already_AddRefed<DrawTargetCapture>
DrawTarget::CreateCaptureDT(const IntSize& aSize)
{
RefPtr<DrawTargetCaptureImpl> dt = new DrawTargetCaptureImpl();
if (!dt->Init(aSize, this)) {
gfxWarning() << "Failed to initialize Capture DrawTarget!";
return nullptr;
}
return dt.forget();
}
void
DrawTarget::DrawCapturedDT(DrawTargetCapture *aCaptureDT,
const Matrix& aTransform)
{
if (aTransform.HasNonIntegerTranslation()) {
gfxWarning() << "Non integer translations are not supported for DrawCaptureDT at this time!";
return;
}
static_cast<DrawTargetCaptureImpl*>(aCaptureDT)->ReplayToDrawTarget(this, aTransform);
}
void
DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount)
{
Matrix oldTransform = GetTransform();
SetTransform(Matrix());
RefPtr<PathBuilder> pathBuilder = CreatePathBuilder();
for (uint32_t i = 0; i < aCount; i++) {
AppendRectToPath(pathBuilder, Rect(aRects[i]));
}
RefPtr<Path> path = pathBuilder->Finish();
PushClip(path);
SetTransform(oldTransform);
}
} // namespace gfx
} // namespace mozilla

2374
gfx/2d/DrawTargetCairo.cpp Normal file

File diff suppressed because it is too large Load diff

262
gfx/2d/DrawTargetCairo.h Normal file
View file

@ -0,0 +1,262 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
#define _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
#include "2D.h"
#include "cairo.h"
#include "PathCairo.h"
#include <vector>
namespace mozilla {
namespace gfx {
class SourceSurfaceCairo;
class GradientStopsCairo : public GradientStops
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCairo)
GradientStopsCairo(GradientStop* aStops, uint32_t aNumStops,
ExtendMode aExtendMode)
: mExtendMode(aExtendMode)
{
for (uint32_t i = 0; i < aNumStops; ++i) {
mStops.push_back(aStops[i]);
}
}
virtual ~GradientStopsCairo() {}
const std::vector<GradientStop>& GetStops() const
{
return mStops;
}
ExtendMode GetExtendMode() const
{
return mExtendMode;
}
virtual BackendType GetBackendType() const { return BackendType::CAIRO; }
private:
std::vector<GradientStop> mStops;
ExtendMode mExtendMode;
};
class DrawTargetCairo final : public DrawTarget
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetCairo, override)
friend class BorrowedCairoContext;
friend class BorrowedXlibDrawable;
DrawTargetCairo();
virtual ~DrawTargetCairo();
virtual bool IsValid() const override;
virtual DrawTargetType GetType() const override;
virtual BackendType GetBackendType() const override { return BackendType::CAIRO; }
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual IntSize GetSize() override;
virtual bool IsCurrentGroupOpaque() override;
virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override;
virtual bool LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat,
IntPoint* aOrigin = nullptr) override;
virtual void ReleaseBits(uint8_t* aData) override;
virtual void Flush() override;
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator) override;
virtual void ClearRect(const Rect &aRect) override;
virtual void CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination) override;
virtual void CopyRect(const IntRect &aSourceRect,
const IntPoint &aDestination) override;
virtual void FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions,
const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
virtual void Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions()) override;
virtual bool Draw3DTransformedSurface(SourceSurface* aSurface,
const Matrix4x4& aMatrix) override;
virtual void PushClip(const Path *aPath) override;
virtual void PushClipRect(const Rect &aRect) override;
virtual void PopClip() override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
virtual void PopLayer() override;
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const override;
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override;
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
virtual already_AddRefed<DrawTarget>
CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFormat,
float aSigma) const override;
virtual already_AddRefed<GradientStops>
CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
virtual void GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices,
uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) override;
virtual void *GetNativeSurface(NativeSurfaceType aType) override;
bool Init(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat = nullptr);
bool Init(const IntSize& aSize, SurfaceFormat aFormat);
bool Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat);
virtual void SetTransform(const Matrix& aTransform) override;
virtual void DetachAllSnapshots() override { MarkSnapshotIndependent(); }
// Call to set up aContext for drawing (with the current transform, etc).
// Pass the path you're going to be using if you have one.
// Implicitly calls WillChange(aPath).
void PrepareForDrawing(cairo_t* aContext, const Path* aPath = nullptr);
static cairo_surface_t *GetDummySurface();
// Cairo hardcodes this as its maximum surface size.
static size_t GetMaxSurfaceSize() {
return 32767;
}
private: // methods
// Init cairo surface without doing a cairo_surface_reference() call.
bool InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat = nullptr);
enum DrawPatternType { DRAW_FILL, DRAW_STROKE };
void DrawPattern(const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions,
DrawPatternType aDrawType,
bool aPathBoundsClip = false);
void CopySurfaceInternal(cairo_surface_t* aSurface,
const IntRect& aSource,
const IntPoint& aDest);
Rect GetUserSpaceClip();
// Call before you make any changes to the backing surface with which this
// context is associated. Pass the path you're going to be using if you have
// one.
void WillChange(const Path* aPath = nullptr);
// Call if there is any reason to disassociate the snapshot from this draw
// target; for example, because we're going to be destroyed.
void MarkSnapshotIndependent();
// If the current operator is "source" then clear the destination before we
// draw into it, to simulate the effect of an unbounded source operator.
void ClearSurfaceForUnboundedSource(const CompositionOp &aOperator);
// Set the Cairo context font options according to the current draw target
// font state.
void SetFontOptions();
private: // data
cairo_t* mContext;
cairo_surface_t* mSurface;
IntSize mSize;
bool mTransformSingular;
uint8_t* mLockedBits;
cairo_font_options_t* mFontOptions;
struct PushedLayer
{
PushedLayer(Float aOpacity, bool aWasPermittingSubpixelAA)
: mOpacity(aOpacity)
, mMaskPattern(nullptr)
, mWasPermittingSubpixelAA(aWasPermittingSubpixelAA)
{}
Float mOpacity;
cairo_pattern_t* mMaskPattern;
bool mWasPermittingSubpixelAA;
};
std::vector<PushedLayer> mPushedLayers;
// The latest snapshot of this surface. This needs to be told when this
// target is modified. We keep it alive as a cache.
RefPtr<SourceSurfaceCairo> mSnapshot;
static cairo_surface_t *mDummySurface;
};
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_DRAWTARGET_CAIRO_H_

View file

@ -0,0 +1,201 @@
/* -*- 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 "DrawTargetCapture.h"
#include "DrawCommand.h"
namespace mozilla {
namespace gfx {
DrawTargetCaptureImpl::~DrawTargetCaptureImpl()
{
uint8_t* start = &mDrawCommandStorage.front();
uint8_t* current = start;
while (current < start + mDrawCommandStorage.size()) {
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->~DrawingCommand();
current += *(uint32_t*)current;
}
}
bool
DrawTargetCaptureImpl::Init(const IntSize& aSize, DrawTarget* aRefDT)
{
if (!aRefDT) {
return false;
}
mRefDT = aRefDT;
mSize = aSize;
return true;
}
already_AddRefed<SourceSurface>
DrawTargetCaptureImpl::Snapshot()
{
RefPtr<DrawTarget> dt = mRefDT->CreateSimilarDrawTarget(mSize, mRefDT->GetFormat());
ReplayToDrawTarget(dt, Matrix());
return dt->Snapshot();
}
void
DrawTargetCaptureImpl::DetachAllSnapshots()
{}
#define AppendCommand(arg) new (AppendToCommandList<arg>()) arg
void
DrawTargetCaptureImpl::DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aOptions)
{
aSurface->GuaranteePersistance();
AppendCommand(DrawSurfaceCommand)(aSurface, aDest, aSource, aSurfOptions, aOptions);
}
void
DrawTargetCaptureImpl::DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions)
{
// @todo XXX - this won't work properly long term yet due to filternodes not
// being immutable.
AppendCommand(DrawFilterCommand)(aNode, aSourceRect, aDestPoint, aOptions);
}
void
DrawTargetCaptureImpl::ClearRect(const Rect &aRect)
{
AppendCommand(ClearRectCommand)(aRect);
}
void
DrawTargetCaptureImpl::MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions)
{
aMask->GuaranteePersistance();
AppendCommand(MaskSurfaceCommand)(aSource, aMask, aOffset, aOptions);
}
void
DrawTargetCaptureImpl::CopySurface(SourceSurface* aSurface,
const IntRect& aSourceRect,
const IntPoint& aDestination)
{
aSurface->GuaranteePersistance();
AppendCommand(CopySurfaceCommand)(aSurface, aSourceRect, aDestination);
}
void
DrawTargetCaptureImpl::FillRect(const Rect& aRect,
const Pattern& aPattern,
const DrawOptions& aOptions)
{
AppendCommand(FillRectCommand)(aRect, aPattern, aOptions);
}
void
DrawTargetCaptureImpl::StrokeRect(const Rect& aRect,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions)
{
AppendCommand(StrokeRectCommand)(aRect, aPattern, aStrokeOptions, aOptions);
}
void
DrawTargetCaptureImpl::StrokeLine(const Point& aStart,
const Point& aEnd,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions)
{
AppendCommand(StrokeLineCommand)(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
}
void
DrawTargetCaptureImpl::Stroke(const Path* aPath,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions)
{
AppendCommand(StrokeCommand)(aPath, aPattern, aStrokeOptions, aOptions);
}
void
DrawTargetCaptureImpl::Fill(const Path* aPath,
const Pattern& aPattern,
const DrawOptions& aOptions)
{
AppendCommand(FillCommand)(aPath, aPattern, aOptions);
}
void
DrawTargetCaptureImpl::FillGlyphs(ScaledFont* aFont,
const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const DrawOptions& aOptions,
const GlyphRenderingOptions* aRenderingOptions)
{
AppendCommand(FillGlyphsCommand)(aFont, aBuffer, aPattern, aOptions, aRenderingOptions);
}
void
DrawTargetCaptureImpl::Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions)
{
AppendCommand(MaskCommand)(aSource, aMask, aOptions);
}
void
DrawTargetCaptureImpl::PushClip(const Path* aPath)
{
AppendCommand(PushClipCommand)(aPath);
}
void
DrawTargetCaptureImpl::PushClipRect(const Rect& aRect)
{
AppendCommand(PushClipRectCommand)(aRect);
}
void
DrawTargetCaptureImpl::PopClip()
{
AppendCommand(PopClipCommand)();
}
void
DrawTargetCaptureImpl::SetTransform(const Matrix& aTransform)
{
AppendCommand(SetTransformCommand)(aTransform);
}
void
DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransform)
{
uint8_t* start = &mDrawCommandStorage.front();
uint8_t* current = start;
while (current < start + mDrawCommandStorage.size()) {
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, &aTransform);
current += *(uint32_t*)current;
}
}
} // namespace gfx
} // namespace mozilla

168
gfx/2d/DrawTargetCapture.h Normal file
View file

@ -0,0 +1,168 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef MOZILLA_GFX_DRAWTARGETCAPTURE_H_
#define MOZILLA_GFX_DRAWTARGETCAPTURE_H_
#include "2D.h"
#include <vector>
#include "Filters.h"
namespace mozilla {
namespace gfx {
class DrawingCommand;
class DrawTargetCaptureImpl : public DrawTargetCapture
{
public:
DrawTargetCaptureImpl()
{}
bool Init(const IntSize& aSize, DrawTarget* aRefDT);
virtual BackendType GetBackendType() const { return mRefDT->GetBackendType(); }
virtual DrawTargetType GetType() const { return mRefDT->GetType(); }
virtual already_AddRefed<SourceSurface> Snapshot();
virtual void DetachAllSnapshots();
virtual IntSize GetSize() { return mSize; }
virtual void Flush() {}
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aOptions);
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions());
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator) { /* Not implemented */ }
virtual void ClearRect(const Rect &aRect);
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions());
virtual void CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination);
virtual void FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions());
virtual void StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions());
virtual void StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions());
virtual void Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions());
virtual void Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions());
virtual void FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions(),
const GlyphRenderingOptions *aRenderingOptions = nullptr);
virtual void Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions = DrawOptions());
virtual void PushClip(const Path *aPath);
virtual void PushClipRect(const Rect &aRect);
virtual void PopClip();
virtual void SetTransform(const Matrix &aTransform);
virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const
{
return mRefDT->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
}
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const
{
return mRefDT->OptimizeSourceSurface(aSurface);
}
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const
{
return mRefDT->CreateSourceSurfaceFromNativeSurface(aSurface);
}
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
{
return mRefDT->CreateSimilarDrawTarget(aSize, aFormat);
}
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const
{
return mRefDT->CreatePathBuilder(aFillRule);
}
virtual already_AddRefed<GradientStops>
CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const
{
return mRefDT->CreateGradientStops(aStops, aNumStops, aExtendMode);
}
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType)
{
return mRefDT->CreateFilter(aType);
}
void ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransform);
protected:
~DrawTargetCaptureImpl();
private:
// This storage system was used to minimize the amount of heap allocations
// that are required while recording. It should be noted there's no
// guarantees on the alignments of DrawingCommands allocated in this array.
template<typename T>
T* AppendToCommandList()
{
size_t oldSize = mDrawCommandStorage.size();
mDrawCommandStorage.resize(mDrawCommandStorage.size() + sizeof(T) + sizeof(uint32_t));
uint8_t* nextDrawLocation = &mDrawCommandStorage.front() + oldSize;
*(uint32_t*)(nextDrawLocation) = sizeof(T) + sizeof(uint32_t);
return reinterpret_cast<T*>(nextDrawLocation + sizeof(uint32_t));
}
RefPtr<DrawTarget> mRefDT;
IntSize mSize;
std::vector<uint8_t> mDrawCommandStorage;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_DRAWTARGETCAPTURE_H_ */

1931
gfx/2d/DrawTargetD2D1.cpp Normal file

File diff suppressed because it is too large Load diff

297
gfx/2d/DrawTargetD2D1.h Normal file
View file

@ -0,0 +1,297 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_DRAWTARGETD2D1_H_
#define MOZILLA_GFX_DRAWTARGETD2D1_H_
#include "2D.h"
#include <d3d11.h>
#include <d2d1_1.h>
#include "PathD2D.h"
#include "HelpersD2D.h"
#include <vector>
#include <sstream>
#include <unordered_set>
struct IDWriteFactory;
namespace mozilla {
namespace gfx {
class SourceSurfaceD2D1;
const int32_t kLayerCacheSize1 = 5;
class DrawTargetD2D1 : public DrawTarget
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D1, override)
DrawTargetD2D1();
virtual ~DrawTargetD2D1();
virtual DrawTargetType GetType() const override { return DrawTargetType::HARDWARE_RASTER; }
virtual BackendType GetBackendType() const override { return BackendType::DIRECT2D1_1; }
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual IntSize GetSize() override { return mSize; }
virtual void Flush() override;
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aOptions) override;
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator) override;
virtual void ClearRect(const Rect &aRect) override;
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination) override;
virtual void FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions(),
const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
virtual void Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void PushClip(const Path *aPath) override;
virtual void PushClipRect(const Rect &aRect) override;
virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override;
virtual void PopClip() override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
virtual void PopLayer() override;
virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const override;
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override { return nullptr; }
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
virtual already_AddRefed<GradientStops>
CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
virtual bool SupportsRegionClipping() const override { return false; }
virtual bool IsCurrentGroupOpaque() override { return CurrentLayer().mIsOpaque; }
virtual void *GetNativeSurface(NativeSurfaceType aType) override { return nullptr; }
virtual void DetachAllSnapshots() override { MarkChanged(); }
virtual void GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices,
uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) override;
bool Init(const IntSize &aSize, SurfaceFormat aFormat);
bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat);
uint32_t GetByteSize() const;
// This function will get an image for a surface, it may adjust the source
// transform for any transformation of the resulting image relative to the
// oritingal SourceSurface.
already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform,
ExtendMode aExtendMode, const IntRect* aSourceRect = nullptr);
already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface *aSurface, ExtendMode aExtendMode) {
Matrix mat;
return GetImageForSurface(aSurface, mat, aExtendMode, nullptr);
}
static ID2D1Factory1 *factory();
static void CleanupD2D();
static IDWriteFactory *GetDWriteFactory();
operator std::string() const {
std::stringstream stream;
stream << "DrawTargetD2D 1.1 (" << this << ")";
return stream.str();
}
static uint32_t GetMaxSurfaceSize() {
return D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
}
static uint64_t mVRAMUsageDT;
static uint64_t mVRAMUsageSS;
private:
friend class SourceSurfaceD2D1;
typedef std::unordered_set<DrawTargetD2D1*> TargetSet;
// This function will mark the surface as changing, and make sure any
// copy-on-write snapshots are notified.
void MarkChanged();
bool ShouldClipTemporarySurfaceDrawing(CompositionOp aOp, const Pattern& aPattern, bool aClipIsComplex);
void PrepareForDrawing(CompositionOp aOp, const Pattern &aPattern);
void FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern);
void FlushTransformToDC() {
if (mTransformDirty) {
mDC->SetTransform(D2DMatrix(mTransform));
mTransformDirty = false;
}
}
void AddDependencyOnSource(SourceSurfaceD2D1* aSource);
// Must be called with all clips popped and an identity matrix set.
already_AddRefed<ID2D1Image> GetImageForLayerContent(bool aShouldPreserveContent = true);
ID2D1Image* CurrentTarget()
{
if (CurrentLayer().mCurrentList) {
return CurrentLayer().mCurrentList;
}
return mBitmap;
}
// This returns the clipped geometry, in addition it returns aClipBounds which
// represents the intersection of all pixel-aligned rectangular clips that
// are currently set. The returned clipped geometry must be clipped by these
// bounds to correctly reflect the total clip. This is in device space and
// only for clips applied to the -current layer-.
already_AddRefed<ID2D1Geometry> GetClippedGeometry(IntRect *aClipBounds);
already_AddRefed<ID2D1Geometry> GetInverseClippedGeometry();
// This gives the device space clip rect applied to the -current layer-.
bool GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned);
void PopAllClips();
void PushAllClips();
void PushClipsToDC(ID2D1DeviceContext *aDC, bool aForceIgnoreAlpha = false, const D2D1_RECT_F& aMaxRect = D2D1::InfiniteRect());
void PopClipsFromDC(ID2D1DeviceContext *aDC);
already_AddRefed<ID2D1Brush> CreateTransparentBlackBrush();
already_AddRefed<ID2D1SolidColorBrush> GetSolidColorBrush(const D2D_COLOR_F& aColor);
already_AddRefed<ID2D1Brush> CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f);
void PushClipGeometry(ID2D1Geometry* aGeometry, const D2D1_MATRIX_3X2_F& aTransform, bool aPixelAligned = false);
void PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform,
bool aPixelAligned = false, bool aForceIgnoreAlpha = false,
const D2D1_RECT_F& aLayerRect = D2D1::InfiniteRect());
IntSize mSize;
RefPtr<ID3D11Device> mDevice;
RefPtr<ID3D11Texture2D> mTexture;
RefPtr<ID2D1Geometry> mCurrentClippedGeometry;
// This is only valid if mCurrentClippedGeometry is non-null. And will
// only be the intersection of all pixel-aligned retangular clips. This is in
// device space.
IntRect mCurrentClipBounds;
mutable RefPtr<ID2D1DeviceContext> mDC;
RefPtr<ID2D1Bitmap1> mBitmap;
RefPtr<ID2D1CommandList> mCommandList;
RefPtr<ID2D1SolidColorBrush> mSolidColorBrush;
// We store this to prevent excessive SetTextRenderingParams calls.
RefPtr<IDWriteRenderingParams> mTextRenderingParams;
// List of pushed clips.
struct PushedClip
{
D2D1_RECT_F mBounds;
// If mGeometry is non-null, the mTransform member will be used.
D2D1_MATRIX_3X2_F mTransform;
RefPtr<ID2D1Geometry> mGeometry;
// Indicates if mBounds, and when non-null, mGeometry with mTransform
// applied, are pixel-aligned.
bool mIsPixelAligned;
};
// List of pushed layers.
struct PushedLayer
{
PushedLayer() : mClipsArePushed(false), mIsOpaque(false), mOldPermitSubpixelAA(false) {}
std::vector<PushedClip> mPushedClips;
RefPtr<ID2D1CommandList> mCurrentList;
// True if the current clip stack is pushed to the CurrentTarget().
bool mClipsArePushed;
bool mIsOpaque;
bool mOldPermitSubpixelAA;
};
std::vector<PushedLayer> mPushedLayers;
PushedLayer& CurrentLayer()
{
return mPushedLayers.back();
}
// The latest snapshot of this surface. This needs to be told when this
// target is modified. We keep it alive as a cache.
RefPtr<SourceSurfaceD2D1> mSnapshot;
// A list of targets we need to flush when we're modified.
TargetSet mDependentTargets;
// A list of targets which have this object in their mDependentTargets set
TargetSet mDependingOnTargets;
uint32_t mUsedCommandListsSincePurge;
// When a BlendEffect has been drawn to a command list, and that command list is
// subsequently used -again- as an input to a blend effect for a command list,
// this causes an infinite recursion inside D2D as it tries to resolve the bounds.
// If we resolve the current command list before this happens
// we can avoid the subsequent hang. (See bug 1293586)
bool mDidComplexBlendWithListInList;
static ID2D1Factory1 *mFactory;
static IDWriteFactory *mDWriteFactory;
};
}
}
#endif /* MOZILLA_GFX_DRAWTARGETD2D_H_ */

223
gfx/2d/DrawTargetDual.cpp Normal file
View file

@ -0,0 +1,223 @@
/* -*- 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 "DrawTargetDual.h"
#include "Tools.h"
#include "Logging.h"
namespace mozilla {
namespace gfx {
class DualSurface
{
public:
inline explicit DualSurface(SourceSurface *aSurface)
{
if (!aSurface) {
mA = mB = nullptr;
return;
}
if (aSurface->GetType() != SurfaceType::DUAL_DT) {
mA = mB = aSurface;
return;
}
SourceSurfaceDual *ssDual =
static_cast<SourceSurfaceDual*>(aSurface);
mA = ssDual->mA;
mB = ssDual->mB;
}
SourceSurface *mA;
SourceSurface *mB;
};
/* This only needs to split patterns up for SurfacePatterns. Only in that
* case can we be dealing with a 'dual' source (SourceSurfaceDual) and do
* we need to pass separate patterns into our destination DrawTargets.
*/
class DualPattern
{
public:
inline explicit DualPattern(const Pattern &aPattern)
: mPatternsInitialized(false)
{
if (aPattern.GetType() != PatternType::SURFACE) {
mA = mB = &aPattern;
return;
}
const SurfacePattern *surfPat =
static_cast<const SurfacePattern*>(&aPattern);
if (surfPat->mSurface->GetType() != SurfaceType::DUAL_DT) {
mA = mB = &aPattern;
return;
}
const SourceSurfaceDual *ssDual =
static_cast<const SourceSurfaceDual*>(surfPat->mSurface.get());
mA = new (mSurfPatA.addr()) SurfacePattern(ssDual->mA, surfPat->mExtendMode,
surfPat->mMatrix,
surfPat->mSamplingFilter);
mB = new (mSurfPatB.addr()) SurfacePattern(ssDual->mB, surfPat->mExtendMode,
surfPat->mMatrix,
surfPat->mSamplingFilter);
mPatternsInitialized = true;
}
inline ~DualPattern()
{
if (mPatternsInitialized) {
mA->~Pattern();
mB->~Pattern();
}
}
ClassStorage<SurfacePattern> mSurfPatA;
ClassStorage<SurfacePattern> mSurfPatB;
const Pattern *mA;
const Pattern *mB;
bool mPatternsInitialized;
};
void
DrawTargetDual::DetachAllSnapshots()
{
mA->DetachAllSnapshots();
mB->DetachAllSnapshots();
}
void
DrawTargetDual::DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions)
{
DualSurface surface(aSurface);
mA->DrawSurface(surface.mA, aDest, aSource, aSurfOptions, aOptions);
mB->DrawSurface(surface.mB, aDest, aSource, aSurfOptions, aOptions);
}
void
DrawTargetDual::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest,
const Color &aColor, const Point &aOffset,
Float aSigma, CompositionOp aOp)
{
DualSurface surface(aSurface);
mA->DrawSurfaceWithShadow(surface.mA, aDest, aColor, aOffset, aSigma, aOp);
mB->DrawSurfaceWithShadow(surface.mB, aDest, aColor, aOffset, aSigma, aOp);
}
void
DrawTargetDual::MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions)
{
DualPattern source(aSource);
DualSurface mask(aMask);
mA->MaskSurface(*source.mA, mask.mA, aOffset, aOptions);
mB->MaskSurface(*source.mB, mask.mB, aOffset, aOptions);
}
void
DrawTargetDual::CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect,
const IntPoint &aDestination)
{
DualSurface surface(aSurface);
mA->CopySurface(surface.mA, aSourceRect, aDestination);
mB->CopySurface(surface.mB, aSourceRect, aDestination);
}
void
DrawTargetDual::FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions)
{
DualPattern pattern(aPattern);
mA->FillRect(aRect, *pattern.mA, aOptions);
mB->FillRect(aRect, *pattern.mB, aOptions);
}
void
DrawTargetDual::StrokeRect(const Rect &aRect, const Pattern &aPattern,
const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions)
{
DualPattern pattern(aPattern);
mA->StrokeRect(aRect, *pattern.mA, aStrokeOptions, aOptions);
mB->StrokeRect(aRect, *pattern.mB, aStrokeOptions, aOptions);
}
void
DrawTargetDual::StrokeLine(const Point &aStart, const Point &aEnd, const Pattern &aPattern,
const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions)
{
DualPattern pattern(aPattern);
mA->StrokeLine(aStart, aEnd, *pattern.mA, aStrokeOptions, aOptions);
mB->StrokeLine(aStart, aEnd, *pattern.mB, aStrokeOptions, aOptions);
}
void
DrawTargetDual::Stroke(const Path *aPath, const Pattern &aPattern,
const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions)
{
DualPattern pattern(aPattern);
mA->Stroke(aPath, *pattern.mA, aStrokeOptions, aOptions);
mB->Stroke(aPath, *pattern.mB, aStrokeOptions, aOptions);
}
void
DrawTargetDual::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aOptions)
{
DualPattern pattern(aPattern);
mA->Fill(aPath, *pattern.mA, aOptions);
mB->Fill(aPath, *pattern.mB, aOptions);
}
void
DrawTargetDual::FillGlyphs(ScaledFont *aScaledFont, const GlyphBuffer &aBuffer,
const Pattern &aPattern, const DrawOptions &aOptions,
const GlyphRenderingOptions *aRenderingOptions)
{
DualPattern pattern(aPattern);
mA->FillGlyphs(aScaledFont, aBuffer, *pattern.mA, aOptions, aRenderingOptions);
mB->FillGlyphs(aScaledFont, aBuffer, *pattern.mB, aOptions, aRenderingOptions);
}
void
DrawTargetDual::Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions)
{
DualPattern source(aSource);
DualPattern mask(aMask);
mA->Mask(*source.mA, *mask.mA, aOptions);
mB->Mask(*source.mB, *mask.mB, aOptions);
}
void
DrawTargetDual::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
const Matrix& aMaskTransform, const IntRect& aBounds,
bool aCopyBackground)
{
DualSurface mask(aMask);
mA->PushLayer(aOpaque, aOpacity, mask.mA, aMaskTransform, aBounds, aCopyBackground);
mB->PushLayer(aOpaque, aOpacity, mask.mB, aMaskTransform, aBounds, aCopyBackground);
}
already_AddRefed<DrawTarget>
DrawTargetDual::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
{
RefPtr<DrawTarget> dtA = mA->CreateSimilarDrawTarget(aSize, aFormat);
RefPtr<DrawTarget> dtB = mB->CreateSimilarDrawTarget(aSize, aFormat);
if (!dtA || !dtB) {
gfxWarning() << "Failure to allocate a similar DrawTargetDual. Size: " << aSize;
return nullptr;
}
return MakeAndAddRef<DrawTargetDual>(dtA, dtB);
}
} // namespace gfx
} // namespace mozilla

178
gfx/2d/DrawTargetDual.h Normal file
View file

@ -0,0 +1,178 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_DRAWTARGETDUAL_H_
#define MOZILLA_GFX_DRAWTARGETDUAL_H_
#include <vector>
#include <sstream>
#include "SourceSurfaceDual.h"
#include "2D.h"
#include "Filters.h"
namespace mozilla {
namespace gfx {
#define FORWARD_FUNCTION(funcName) \
virtual void funcName() override { mA->funcName(); mB->funcName(); }
#define FORWARD_FUNCTION1(funcName, var1Type, var1Name) \
virtual void funcName(var1Type var1Name) override { mA->funcName(var1Name); mB->funcName(var1Name); }
/* This is a special type of DrawTarget. It duplicates all drawing calls
* accross two drawtargets. An exception to this is when a snapshot of another
* dual DrawTarget is used as the source for any surface data. In this case
* the snapshot of the first source DrawTarget is used as a source for the call
* to the first destination DrawTarget (mA) and the snapshot of the second
* source DrawTarget is used at the source for the second destination
* DrawTarget (mB). This class facilitates black-background/white-background
* drawing for per-component alpha extraction for backends which do not support
* native component alpha.
*/
class DrawTargetDual : public DrawTarget
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetDual, override)
DrawTargetDual(DrawTarget *aA, DrawTarget *aB)
: mA(aA)
, mB(aB)
{
mFormat = aA->GetFormat();
}
virtual DrawTargetType GetType() const override { return mA->GetType(); }
virtual BackendType GetBackendType() const override { return mA->GetBackendType(); }
virtual already_AddRefed<SourceSurface> Snapshot() override {
return MakeAndAddRef<SourceSurfaceDual>(mA, mB);
}
virtual IntSize GetSize() override { return mA->GetSize(); }
virtual void DetachAllSnapshots() override;
FORWARD_FUNCTION(Flush)
FORWARD_FUNCTION1(PushClip, const Path *, aPath)
FORWARD_FUNCTION1(PushClipRect, const Rect &, aRect)
FORWARD_FUNCTION(PopClip)
FORWARD_FUNCTION(PopLayer)
FORWARD_FUNCTION1(ClearRect, const Rect &, aRect)
virtual void SetTransform(const Matrix &aTransform) override {
mTransform = aTransform;
mA->SetTransform(aTransform);
mB->SetTransform(aTransform);
}
virtual void DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect & aSource,
const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions) override;
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions()) override
{
mA->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
mB->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
}
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest,
const Color &aColor, const Point &aOffset,
Float aSigma, CompositionOp aOp) override;
virtual void CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect,
const IntPoint &aDestination) override;
virtual void FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) override;
virtual void StrokeRect(const Rect &aRect, const Pattern &aPattern,
const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) override;
virtual void StrokeLine(const Point &aStart, const Point &aEnd, const Pattern &aPattern,
const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) override;
virtual void Stroke(const Path *aPath, const Pattern &aPattern,
const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) override;
virtual void Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aOptions) override;
virtual void FillGlyphs(ScaledFont *aScaledFont, const GlyphBuffer &aBuffer,
const Pattern &aPattern, const DrawOptions &aOptions,
const GlyphRenderingOptions *aRenderingOptions) override;
virtual void Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions) override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const override
{
return mA->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
}
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override
{
return mA->OptimizeSourceSurface(aSurface);
}
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override
{
return mA->CreateSourceSurfaceFromNativeSurface(aSurface);
}
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override
{
return mA->CreatePathBuilder(aFillRule);
}
virtual already_AddRefed<GradientStops>
CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const override
{
return mA->CreateGradientStops(aStops, aNumStops, aExtendMode);
}
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override
{
return mA->CreateFilter(aType);
}
virtual void *GetNativeSurface(NativeSurfaceType aType) override
{
return nullptr;
}
virtual bool IsDualDrawTarget() const override
{
return true;
}
virtual bool IsCurrentGroupOpaque() override { return mA->IsCurrentGroupOpaque(); }
private:
RefPtr<DrawTarget> mA;
RefPtr<DrawTarget> mB;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_DRAWTARGETDUAL_H_ */

View file

@ -0,0 +1,737 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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 "DrawTargetRecording.h"
#include "PathRecording.h"
#include <stdio.h>
#include "Logging.h"
#include "Tools.h"
#include "Filters.h"
#include "mozilla/UniquePtr.h"
#include "RecordingTypes.h"
namespace mozilla {
namespace gfx {
struct RecordingSourceSurfaceUserData
{
void *refPtr;
RefPtr<DrawEventRecorderPrivate> recorder;
};
void RecordingSourceSurfaceUserDataFunc(void *aUserData)
{
RecordingSourceSurfaceUserData *userData =
static_cast<RecordingSourceSurfaceUserData*>(aUserData);
userData->recorder->RemoveStoredObject(userData->refPtr);
userData->recorder->RecordEvent(
RecordedSourceSurfaceDestruction(userData->refPtr));
delete userData;
}
static void
StoreSourceSurface(DrawEventRecorderPrivate *aRecorder, SourceSurface *aSurface,
DataSourceSurface *aDataSurf, const char *reason)
{
if (!aDataSurf) {
gfxWarning() << "Recording failed to record SourceSurface for " << reason;
// Insert a bogus source surface.
int32_t stride = aSurface->GetSize().width * BytesPerPixel(aSurface->GetFormat());
UniquePtr<uint8_t[]> sourceData(new uint8_t[stride * aSurface->GetSize().height]());
aRecorder->RecordEvent(
RecordedSourceSurfaceCreation(aSurface, sourceData.get(), stride,
aSurface->GetSize(), aSurface->GetFormat()));
} else {
DataSourceSurface::ScopedMap map(aDataSurf, DataSourceSurface::READ);
aRecorder->RecordEvent(
RecordedSourceSurfaceCreation(aSurface, map.GetData(), map.GetStride(),
aDataSurf->GetSize(), aDataSurf->GetFormat()));
}
}
static void
EnsureSurfaceStored(DrawEventRecorderPrivate *aRecorder, SourceSurface *aSurface,
const char *reason)
{
if (aRecorder->HasStoredObject(aSurface)) {
return;
}
RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
StoreSourceSurface(aRecorder, aSurface, dataSurf, reason);
aRecorder->AddStoredObject(aSurface);
RecordingSourceSurfaceUserData *userData = new RecordingSourceSurfaceUserData;
userData->refPtr = aSurface;
userData->recorder = aRecorder;
aSurface->AddUserData(reinterpret_cast<UserDataKey*>(aRecorder),
userData, &RecordingSourceSurfaceUserDataFunc);
return;
}
class SourceSurfaceRecording : public SourceSurface
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceRecording)
SourceSurfaceRecording(SourceSurface *aFinalSurface, DrawEventRecorderPrivate *aRecorder)
: mFinalSurface(aFinalSurface), mRecorder(aRecorder)
{
mRecorder->AddStoredObject(this);
}
~SourceSurfaceRecording()
{
mRecorder->RemoveStoredObject(this);
mRecorder->RecordEvent(RecordedSourceSurfaceDestruction(this));
}
virtual SurfaceType GetType() const { return SurfaceType::RECORDING; }
virtual IntSize GetSize() const { return mFinalSurface->GetSize(); }
virtual SurfaceFormat GetFormat() const { return mFinalSurface->GetFormat(); }
virtual already_AddRefed<DataSourceSurface> GetDataSurface() { return mFinalSurface->GetDataSurface(); }
RefPtr<SourceSurface> mFinalSurface;
RefPtr<DrawEventRecorderPrivate> mRecorder;
};
class GradientStopsRecording : public GradientStops
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsRecording)
GradientStopsRecording(GradientStops *aFinalGradientStops, DrawEventRecorderPrivate *aRecorder)
: mFinalGradientStops(aFinalGradientStops), mRecorder(aRecorder)
{
mRecorder->AddStoredObject(this);
}
~GradientStopsRecording()
{
mRecorder->RemoveStoredObject(this);
mRecorder->RecordEvent(RecordedGradientStopsDestruction(this));
}
virtual BackendType GetBackendType() const { return BackendType::RECORDING; }
RefPtr<GradientStops> mFinalGradientStops;
RefPtr<DrawEventRecorderPrivate> mRecorder;
};
static SourceSurface *
GetSourceSurface(SourceSurface *aSurface)
{
if (aSurface->GetType() != SurfaceType::RECORDING) {
return aSurface;
}
return static_cast<SourceSurfaceRecording*>(aSurface)->mFinalSurface;
}
static GradientStops *
GetGradientStops(GradientStops *aStops)
{
if (aStops->GetBackendType() != BackendType::RECORDING) {
return aStops;
}
return static_cast<GradientStopsRecording*>(aStops)->mFinalGradientStops;
}
class FilterNodeRecording : public FilterNode
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeRecording, override)
using FilterNode::SetAttribute;
FilterNodeRecording(FilterNode *aFinalFilterNode, DrawEventRecorderPrivate *aRecorder)
: mFinalFilterNode(aFinalFilterNode), mRecorder(aRecorder)
{
mRecorder->AddStoredObject(this);
}
~FilterNodeRecording()
{
mRecorder->RemoveStoredObject(this);
mRecorder->RecordEvent(RecordedFilterNodeDestruction(this));
}
static FilterNode*
GetFilterNode(FilterNode* aNode)
{
if (aNode->GetBackendType() != FILTER_BACKEND_RECORDING) {
gfxWarning() << "Non recording filter node used with recording DrawTarget!";
return aNode;
}
return static_cast<FilterNodeRecording*>(aNode)->mFinalFilterNode;
}
virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) override
{
EnsureSurfaceStored(mRecorder, aSurface, "SetInput");
mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aSurface));
mFinalFilterNode->SetInput(aIndex, GetSourceSurface(aSurface));
}
virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override
{
MOZ_ASSERT(mRecorder->HasStoredObject(aFilter));
mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aFilter));
mFinalFilterNode->SetInput(aIndex, GetFilterNode(aFilter));
}
#define FORWARD_SET_ATTRIBUTE(type, argtype) \
virtual void SetAttribute(uint32_t aIndex, type aValue) override { \
mRecorder->RecordEvent(RecordedFilterNodeSetAttribute(this, aIndex, aValue, RecordedFilterNodeSetAttribute::ARGTYPE_##argtype)); \
mFinalFilterNode->SetAttribute(aIndex, aValue); \
}
FORWARD_SET_ATTRIBUTE(bool, BOOL);
FORWARD_SET_ATTRIBUTE(uint32_t, UINT32);
FORWARD_SET_ATTRIBUTE(Float, FLOAT);
FORWARD_SET_ATTRIBUTE(const Size&, SIZE);
FORWARD_SET_ATTRIBUTE(const IntSize&, INTSIZE);
FORWARD_SET_ATTRIBUTE(const IntPoint&, INTPOINT);
FORWARD_SET_ATTRIBUTE(const Rect&, RECT);
FORWARD_SET_ATTRIBUTE(const IntRect&, INTRECT);
FORWARD_SET_ATTRIBUTE(const Point&, POINT);
FORWARD_SET_ATTRIBUTE(const Matrix&, MATRIX);
FORWARD_SET_ATTRIBUTE(const Matrix5x4&, MATRIX5X4);
FORWARD_SET_ATTRIBUTE(const Point3D&, POINT3D);
FORWARD_SET_ATTRIBUTE(const Color&, COLOR);
#undef FORWARD_SET_ATTRIBUTE
virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override {
mRecorder->RecordEvent(RecordedFilterNodeSetAttribute(this, aIndex, aFloat, aSize));
mFinalFilterNode->SetAttribute(aIndex, aFloat, aSize);
}
virtual FilterBackend GetBackendType() override { return FILTER_BACKEND_RECORDING; }
RefPtr<FilterNode> mFinalFilterNode;
RefPtr<DrawEventRecorderPrivate> mRecorder;
};
struct AdjustedPattern
{
explicit AdjustedPattern(const Pattern &aPattern)
: mPattern(nullptr)
{
mOrigPattern = const_cast<Pattern*>(&aPattern);
}
~AdjustedPattern() {
if (mPattern) {
mPattern->~Pattern();
}
}
operator Pattern*()
{
switch(mOrigPattern->GetType()) {
case PatternType::COLOR:
return mOrigPattern;
case PatternType::SURFACE:
{
SurfacePattern *surfPat = static_cast<SurfacePattern*>(mOrigPattern);
mPattern =
new (mSurfPat) SurfacePattern(GetSourceSurface(surfPat->mSurface),
surfPat->mExtendMode, surfPat->mMatrix,
surfPat->mSamplingFilter,
surfPat->mSamplingRect);
return mPattern;
}
case PatternType::LINEAR_GRADIENT:
{
LinearGradientPattern *linGradPat = static_cast<LinearGradientPattern*>(mOrigPattern);
mPattern =
new (mLinGradPat) LinearGradientPattern(linGradPat->mBegin, linGradPat->mEnd,
GetGradientStops(linGradPat->mStops),
linGradPat->mMatrix);
return mPattern;
}
case PatternType::RADIAL_GRADIENT:
{
RadialGradientPattern *radGradPat = static_cast<RadialGradientPattern*>(mOrigPattern);
mPattern =
new (mRadGradPat) RadialGradientPattern(radGradPat->mCenter1, radGradPat->mCenter2,
radGradPat->mRadius1, radGradPat->mRadius2,
GetGradientStops(radGradPat->mStops),
radGradPat->mMatrix);
return mPattern;
}
default:
return new (mColPat) ColorPattern(Color());
}
return mPattern;
}
union {
char mColPat[sizeof(ColorPattern)];
char mLinGradPat[sizeof(LinearGradientPattern)];
char mRadGradPat[sizeof(RadialGradientPattern)];
char mSurfPat[sizeof(SurfacePattern)];
};
Pattern *mOrigPattern;
Pattern *mPattern;
};
DrawTargetRecording::DrawTargetRecording(DrawEventRecorder *aRecorder, DrawTarget *aDT, bool aHasData)
: mRecorder(static_cast<DrawEventRecorderPrivate*>(aRecorder))
, mFinalDT(aDT)
{
RefPtr<SourceSurface> snapshot = aHasData ? mFinalDT->Snapshot() : nullptr;
mRecorder->RecordEvent(RecordedDrawTargetCreation(this,
mFinalDT->GetBackendType(),
mFinalDT->GetSize(),
mFinalDT->GetFormat(),
aHasData, snapshot));
mFormat = mFinalDT->GetFormat();
}
DrawTargetRecording::DrawTargetRecording(const DrawTargetRecording *aDT,
DrawTarget *aSimilarDT)
: mRecorder(aDT->mRecorder)
, mFinalDT(aSimilarDT)
{
mRecorder->RecordEvent(RecordedCreateSimilarDrawTarget(this,
mFinalDT->GetSize(),
mFinalDT->GetFormat()));
mFormat = mFinalDT->GetFormat();
}
DrawTargetRecording::~DrawTargetRecording()
{
mRecorder->RecordEvent(RecordedDrawTargetDestruction(this));
}
void
DrawTargetRecording::FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions)
{
EnsurePatternDependenciesStored(aPattern);
mRecorder->RecordEvent(RecordedFillRect(this, aRect, aPattern, aOptions));
mFinalDT->FillRect(aRect, *AdjustedPattern(aPattern), aOptions);
}
void
DrawTargetRecording::StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aOptions)
{
EnsurePatternDependenciesStored(aPattern);
mRecorder->RecordEvent(RecordedStrokeRect(this, aRect, aPattern, aStrokeOptions, aOptions));
mFinalDT->StrokeRect(aRect, *AdjustedPattern(aPattern), aStrokeOptions, aOptions);
}
void
DrawTargetRecording::StrokeLine(const Point &aBegin,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aOptions)
{
EnsurePatternDependenciesStored(aPattern);
mRecorder->RecordEvent(RecordedStrokeLine(this, aBegin, aEnd, aPattern, aStrokeOptions, aOptions));
mFinalDT->StrokeLine(aBegin, aEnd, *AdjustedPattern(aPattern), aStrokeOptions, aOptions);
}
void
DrawTargetRecording::Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions)
{
RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath);
EnsurePatternDependenciesStored(aPattern);
mRecorder->RecordEvent(RecordedFill(this, pathRecording, aPattern, aOptions));
mFinalDT->Fill(pathRecording->mPath, *AdjustedPattern(aPattern), aOptions);
}
struct RecordingFontUserData
{
void *refPtr;
RefPtr<DrawEventRecorderPrivate> recorder;
};
void RecordingFontUserDataDestroyFunc(void *aUserData)
{
RecordingFontUserData *userData =
static_cast<RecordingFontUserData*>(aUserData);
userData->recorder->RecordEvent(RecordedScaledFontDestruction(userData->refPtr));
delete userData;
}
void
DrawTargetRecording::FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions,
const GlyphRenderingOptions *aRenderingOptions)
{
EnsurePatternDependenciesStored(aPattern);
if (!aFont->GetUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()))) {
RecordedFontData fontData(aFont);
RecordedFontDetails fontDetails;
if (fontData.GetFontDetails(fontDetails)) {
// Try to serialise the whole font, just in case this is a web font that
// is not present on the system.
if (!mRecorder->HasStoredFontData(fontDetails.fontDataKey)) {
mRecorder->RecordEvent(fontData);
mRecorder->AddStoredFontData(fontDetails.fontDataKey);
}
mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, fontDetails));
} else {
// If that fails, record just the font description and try to load it from
// the system on the other side.
RecordedFontDescriptor fontDesc(aFont);
if (fontDesc.IsValid()) {
mRecorder->RecordEvent(fontDesc);
} else {
gfxWarning() << "DrawTargetRecording::FillGlyphs failed to serialise ScaledFont";
}
}
RecordingFontUserData *userData = new RecordingFontUserData;
userData->refPtr = aFont;
userData->recorder = mRecorder;
aFont->AddUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()), userData,
&RecordingFontUserDataDestroyFunc);
}
mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
mFinalDT->FillGlyphs(aFont, aBuffer, *AdjustedPattern(aPattern), aOptions, aRenderingOptions);
}
void
DrawTargetRecording::Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions)
{
EnsurePatternDependenciesStored(aSource);
EnsurePatternDependenciesStored(aMask);
mRecorder->RecordEvent(RecordedMask(this, aSource, aMask, aOptions));
mFinalDT->Mask(*AdjustedPattern(aSource), *AdjustedPattern(aMask), aOptions);
}
void
DrawTargetRecording::MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions)
{
EnsurePatternDependenciesStored(aSource);
EnsureSurfaceStored(mRecorder, aMask, "MaskSurface");
mRecorder->RecordEvent(RecordedMaskSurface(this, aSource, aMask, aOffset, aOptions));
mFinalDT->MaskSurface(*AdjustedPattern(aSource), GetSourceSurface(aMask), aOffset, aOptions);
}
void
DrawTargetRecording::Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aOptions)
{
RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath);
EnsurePatternDependenciesStored(aPattern);
mRecorder->RecordEvent(RecordedStroke(this, pathRecording, aPattern, aStrokeOptions, aOptions));
mFinalDT->Stroke(pathRecording->mPath, *AdjustedPattern(aPattern), aStrokeOptions, aOptions);
}
already_AddRefed<SourceSurface>
DrawTargetRecording::Snapshot()
{
RefPtr<SourceSurface> surf = mFinalDT->Snapshot();
RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder);
mRecorder->RecordEvent(RecordedSnapshot(retSurf, this));
return retSurf.forget();
}
void
DrawTargetRecording::DetachAllSnapshots()
{
mFinalDT->DetachAllSnapshots();
}
void
DrawTargetRecording::DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aOptions)
{
EnsureSurfaceStored(mRecorder, aSurface, "DrawSurface");
mRecorder->RecordEvent(RecordedDrawSurface(this, aSurface, aDest, aSource, aSurfOptions, aOptions));
mFinalDT->DrawSurface(GetSourceSurface(aSurface), aDest, aSource, aSurfOptions, aOptions);
}
void
DrawTargetRecording::DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOp)
{
EnsureSurfaceStored(mRecorder, aSurface, "DrawSurfaceWithShadow");
mRecorder->RecordEvent(RecordedDrawSurfaceWithShadow(this, aSurface, aDest, aColor, aOffset, aSigma, aOp));
mFinalDT->DrawSurfaceWithShadow(GetSourceSurface(aSurface), aDest, aColor, aOffset, aSigma, aOp);
}
void
DrawTargetRecording::DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions)
{
MOZ_ASSERT(mRecorder->HasStoredObject(aNode));
mRecorder->RecordEvent(RecordedDrawFilter(this, aNode, aSourceRect, aDestPoint, aOptions));
mFinalDT->DrawFilter(FilterNodeRecording::GetFilterNode(aNode), aSourceRect, aDestPoint, aOptions);
}
already_AddRefed<FilterNode>
DrawTargetRecording::CreateFilter(FilterType aType)
{
RefPtr<FilterNode> node = mFinalDT->CreateFilter(aType);
RefPtr<FilterNode> retNode = new FilterNodeRecording(node, mRecorder);
mRecorder->RecordEvent(RecordedFilterNodeCreation(retNode, aType));
return retNode.forget();
}
void
DrawTargetRecording::ClearRect(const Rect &aRect)
{
mRecorder->RecordEvent(RecordedClearRect(this, aRect));
mFinalDT->ClearRect(aRect);
}
void
DrawTargetRecording::CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination)
{
EnsureSurfaceStored(mRecorder, aSurface, "CopySurface");
mRecorder->RecordEvent(RecordedCopySurface(this, aSurface, aSourceRect, aDestination));
mFinalDT->CopySurface(GetSourceSurface(aSurface), aSourceRect, aDestination);
}
void
DrawTargetRecording::PushClip(const Path *aPath)
{
RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath);
mRecorder->RecordEvent(RecordedPushClip(this, pathRecording));
mFinalDT->PushClip(pathRecording->mPath);
}
void
DrawTargetRecording::PushClipRect(const Rect &aRect)
{
mRecorder->RecordEvent(RecordedPushClipRect(this, aRect));
mFinalDT->PushClipRect(aRect);
}
void
DrawTargetRecording::PopClip()
{
mRecorder->RecordEvent(RecordedPopClip(this));
mFinalDT->PopClip();
}
void
DrawTargetRecording::PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds, bool aCopyBackground)
{
if (aMask) {
EnsureSurfaceStored(mRecorder, aMask, "PushLayer");
}
mRecorder->RecordEvent(RecordedPushLayer(this, aOpaque, aOpacity, aMask,
aMaskTransform, aBounds,
aCopyBackground));
mFinalDT->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
aCopyBackground);
}
void
DrawTargetRecording::PopLayer()
{
mRecorder->RecordEvent(RecordedPopLayer(this));
mFinalDT->PopLayer();
}
already_AddRefed<SourceSurface>
DrawTargetRecording::CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const
{
RefPtr<SourceSurface> surf = mFinalDT->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder);
mRecorder->RecordEvent(RecordedSourceSurfaceCreation(retSurf, aData, aStride, aSize, aFormat));
return retSurf.forget();
}
already_AddRefed<SourceSurface>
DrawTargetRecording::OptimizeSourceSurface(SourceSurface *aSurface) const
{
RefPtr<SourceSurface> surf = mFinalDT->OptimizeSourceSurface(aSurface);
RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder);
RefPtr<DataSourceSurface> dataSurf = surf->GetDataSurface();
if (!dataSurf) {
// Let's try get it off the original surface.
dataSurf = aSurface->GetDataSurface();
}
StoreSourceSurface(mRecorder, retSurf, dataSurf, "OptimizeSourceSurface");
return retSurf.forget();
}
already_AddRefed<SourceSurface>
DrawTargetRecording::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const
{
RefPtr<SourceSurface> surf = mFinalDT->CreateSourceSurfaceFromNativeSurface(aSurface);
RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder);
RefPtr<DataSourceSurface> dataSurf = surf->GetDataSurface();
StoreSourceSurface(mRecorder, retSurf, dataSurf, "CreateSourceSurfaceFromNativeSurface");
return retSurf.forget();
}
already_AddRefed<DrawTarget>
DrawTargetRecording::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
{
RefPtr<DrawTarget> similarDT =
mFinalDT->CreateSimilarDrawTarget(aSize, aFormat);
if (!similarDT) {
return nullptr;
}
similarDT = new DrawTargetRecording(this, similarDT);
return similarDT.forget();
}
already_AddRefed<PathBuilder>
DrawTargetRecording::CreatePathBuilder(FillRule aFillRule) const
{
RefPtr<PathBuilder> builder = mFinalDT->CreatePathBuilder(aFillRule);
return MakeAndAddRef<PathBuilderRecording>(builder, aFillRule);
}
already_AddRefed<GradientStops>
DrawTargetRecording::CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode) const
{
RefPtr<GradientStops> stops = mFinalDT->CreateGradientStops(aStops, aNumStops, aExtendMode);
RefPtr<GradientStops> retStops = new GradientStopsRecording(stops, mRecorder);
mRecorder->RecordEvent(RecordedGradientStopsCreation(retStops, aStops, aNumStops, aExtendMode));
return retStops.forget();
}
void
DrawTargetRecording::SetTransform(const Matrix &aTransform)
{
mRecorder->RecordEvent(RecordedSetTransform(this, aTransform));
DrawTarget::SetTransform(aTransform);
mFinalDT->SetTransform(aTransform);
}
already_AddRefed<PathRecording>
DrawTargetRecording::EnsurePathStored(const Path *aPath)
{
RefPtr<PathRecording> pathRecording;
if (aPath->GetBackendType() == BackendType::RECORDING) {
pathRecording = const_cast<PathRecording*>(static_cast<const PathRecording*>(aPath));
if (mRecorder->HasStoredObject(aPath)) {
return pathRecording.forget();
}
} else {
MOZ_ASSERT(!mRecorder->HasStoredObject(aPath));
FillRule fillRule = aPath->GetFillRule();
RefPtr<PathBuilder> builder = mFinalDT->CreatePathBuilder(fillRule);
RefPtr<PathBuilderRecording> builderRecording =
new PathBuilderRecording(builder, fillRule);
aPath->StreamToSink(builderRecording);
pathRecording = builderRecording->Finish().downcast<PathRecording>();
}
mRecorder->RecordEvent(RecordedPathCreation(pathRecording));
mRecorder->AddStoredObject(pathRecording);
pathRecording->mStoredRecorders.push_back(mRecorder);
return pathRecording.forget();
}
void
DrawTargetRecording::EnsurePatternDependenciesStored(const Pattern &aPattern)
{
switch (aPattern.GetType()) {
case PatternType::COLOR:
// No dependencies here.
return;
case PatternType::LINEAR_GRADIENT:
{
MOZ_ASSERT(mRecorder->HasStoredObject(static_cast<const LinearGradientPattern*>(&aPattern)->mStops));
return;
}
case PatternType::RADIAL_GRADIENT:
{
MOZ_ASSERT(mRecorder->HasStoredObject(static_cast<const RadialGradientPattern*>(&aPattern)->mStops));
return;
}
case PatternType::SURFACE:
{
const SurfacePattern *pat = static_cast<const SurfacePattern*>(&aPattern);
EnsureSurfaceStored(mRecorder, pat->mSurface, "EnsurePatternDependenciesStored");
return;
}
}
}
} // namespace gfx
} // namespace mozilla

View file

@ -0,0 +1,337 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/. */
#ifndef MOZILLA_GFX_DRAWTARGETRECORDING_H_
#define MOZILLA_GFX_DRAWTARGETRECORDING_H_
#include "2D.h"
#include "DrawEventRecorder.h"
namespace mozilla {
namespace gfx {
class DrawTargetRecording : public DrawTarget
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetRecording, override)
DrawTargetRecording(DrawEventRecorder *aRecorder, DrawTarget *aDT, bool aHasData = false);
~DrawTargetRecording();
virtual DrawTargetType GetType() const override { return mFinalDT->GetType(); }
virtual BackendType GetBackendType() const override { return mFinalDT->GetBackendType(); }
virtual bool IsRecording() const override { return true; }
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual void DetachAllSnapshots() override;
virtual IntSize GetSize() override { return mFinalDT->GetSize(); }
/* Ensure that the DrawTarget backend has flushed all drawing operations to
* this draw target. This must be called before using the backing surface of
* this draw target outside of GFX 2D code.
*/
virtual void Flush() override { mFinalDT->Flush(); }
/*
* Draw a surface to the draw target. Possibly doing partial drawing or
* applying scaling. No sampling happens outside the source.
*
* aSurface Source surface to draw
* aDest Destination rectangle that this drawing operation should draw to
* aSource Source rectangle in aSurface coordinates, this area of aSurface
* will be stretched to the size of aDest.
* aOptions General draw options that are applied to the operation
* aSurfOptions DrawSurface options that are applied
*/
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Blend a surface to the draw target with a shadow. The shadow is drawn as a
* gaussian blur using a specified sigma. The shadow is clipped to the size
* of the input surface, so the input surface should contain a transparent
* border the size of the approximate coverage of the blur (3 * aSigma).
* NOTE: This function works in device space!
*
* aSurface Source surface to draw.
* aDest Destination point that this drawing operation should draw to.
* aColor Color of the drawn shadow
* aOffset Offset of the shadow
* aSigma Sigma used for the guassian filter kernel
* aOperator Composition operator used
*/
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator) override;
/*
* Clear a rectangle on the draw target to transparent black. This will
* respect the clipping region and transform.
*
* aRect Rectangle to clear
*/
virtual void ClearRect(const Rect &aRect) override;
/*
* This is essentially a 'memcpy' between two surfaces. It moves a pixel
* aligned area from the source surface unscaled directly onto the
* drawtarget. This ignores both transform and clip.
*
* aSurface Surface to copy from
* aSourceRect Source rectangle to be copied
* aDest Destination point to copy the surface to
*/
virtual void CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination) override;
/*
* Fill a rectangle on the DrawTarget with a certain source pattern.
*
* aRect Rectangle that forms the mask of this filling operation
* aPattern Pattern that forms the source of this filling operation
* aOptions Options that are applied to this operation
*/
virtual void FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Stroke a rectangle on the DrawTarget with a certain source pattern.
*
* aRect Rectangle that forms the mask of this stroking operation
* aPattern Pattern that forms the source of this stroking operation
* aOptions Options that are applied to this operation
*/
virtual void StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Stroke a line on the DrawTarget with a certain source pattern.
*
* aStart Starting point of the line
* aEnd End point of the line
* aPattern Pattern that forms the source of this stroking operation
* aOptions Options that are applied to this operation
*/
virtual void StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Stroke a path on the draw target with a certain source pattern.
*
* aPath Path that is to be stroked
* aPattern Pattern that should be used for the stroke
* aStrokeOptions Stroke options used for this operation
* aOptions Draw options used for this operation
*/
virtual void Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Fill a path on the draw target with a certain source pattern.
*
* aPath Path that is to be filled
* aPattern Pattern that should be used for the fill
* aOptions Draw options used for this operation
*/
virtual void Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Fill a series of clyphs on the draw target with a certain source pattern.
*/
virtual void FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions(),
const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
/*
* This takes a source pattern and a mask, and composites the source pattern
* onto the destination surface using the alpha channel of the mask pattern
* as a mask for the operation.
*
* aSource Source pattern
* aMask Mask pattern
* aOptions Drawing options
*/
virtual void Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions()) override;
/*
* Push a clip to the DrawTarget.
*
* aPath The path to clip to
*/
virtual void PushClip(const Path *aPath) override;
/*
* Push an axis-aligned rectangular clip to the DrawTarget. This rectangle
* is specified in user space.
*
* aRect The rect to clip to
*/
virtual void PushClipRect(const Rect &aRect) override;
/* Pop a clip from the DrawTarget. A pop without a corresponding push will
* be ignored.
*/
virtual void PopClip() override;
/**
* Push a 'layer' to the DrawTarget, a layer is a temporary surface that all
* drawing will be redirected to, this is used for example to support group
* opacity or the masking of groups. Clips must be balanced within a layer,
* i.e. between a matching PushLayer/PopLayer pair there must be as many
* PushClip(Rect) calls as there are PopClip calls.
*
* @param aOpaque Whether the layer will be opaque
* @param aOpacity Opacity of the layer
* @param aMask Mask applied to the layer
* @param aMaskTransform Transform applied to the layer mask
* @param aBounds Optional bounds in device space to which the layer is
* limited in size.
* @param aCopyBackground Whether to copy the background into the layer, this
* is only supported when aOpaque is true.
*/
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
/**
* This balances a call to PushLayer and proceeds to blend the layer back
* onto the background. This blend will blend the temporary surface back
* onto the target in device space using POINT sampling and operator over.
*/
virtual void PopLayer() override;
/*
* Create a SourceSurface optimized for use with this DrawTarget from
* existing bitmap data in memory.
*
* The SourceSurface does not take ownership of aData, and may be freed at any time.
*/
virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const override;
/*
* Create a SourceSurface optimized for use with this DrawTarget from
* an arbitrary other SourceSurface. This may return aSourceSurface or some
* other existing surface.
*/
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
/*
* Create a SourceSurface for a type of NativeSurface. This may fail if the
* draw target does not know how to deal with the type of NativeSurface passed
* in.
*/
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override;
/*
* Create a DrawTarget whose snapshot is optimized for use with this DrawTarget.
*/
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
/*
* Create a path builder with the specified fillmode.
*
* We need the fill mode up front because of Direct2D.
* ID2D1SimplifiedGeometrySink requires the fill mode
* to be set before calling BeginFigure().
*/
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
/*
* Create a GradientStops object that holds information about a set of
* gradient stops, this object is required for linear or radial gradient
* patterns to represent the color stops in the gradient.
*
* aStops An array of gradient stops
* aNumStops Number of stops in the array aStops
* aExtendNone This describes how to extend the stop color outside of the
* gradient area.
*/
virtual already_AddRefed<GradientStops>
CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
/*
* Set a transform on the surface, this transform is applied at drawing time
* to both the mask and source of the operation.
*/
virtual void SetTransform(const Matrix &aTransform) override;
/* Tries to get a native surface for a DrawTarget, this may fail if the
* draw target cannot convert to this surface type.
*/
virtual void *GetNativeSurface(NativeSurfaceType aType) override { return mFinalDT->GetNativeSurface(aType); }
virtual bool IsCurrentGroupOpaque() override {
return mFinalDT->IsCurrentGroupOpaque();
}
private:
/**
* Used for creating a DrawTargetRecording for a CreateSimilarDrawTarget call.
* We have to call CreateSimilarDrawTarget on mFinalDT up front and pass it in
* as it can fail.
*
* @param aDT DrawTargetRecording on which CreateSimilarDrawTarget was called
* @param aSimilarDT Similar DrawTarget created from aDT.mFinalDT.
*/
DrawTargetRecording(const DrawTargetRecording *aDT,
DrawTarget *aSimilarDT);
Path *GetPathForPathRecording(const Path *aPath) const;
already_AddRefed<PathRecording> EnsurePathStored(const Path *aPath);
void EnsurePatternDependenciesStored(const Pattern &aPattern);
RefPtr<DrawEventRecorderPrivate> mRecorder;
RefPtr<DrawTarget> mFinalDT;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_DRAWTARGETRECORDING_H_ */

2135
gfx/2d/DrawTargetSkia.cpp Normal file

File diff suppressed because it is too large Load diff

214
gfx/2d/DrawTargetSkia.h Normal file
View file

@ -0,0 +1,214 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_SOURCESURFACESKIA_H
#define _MOZILLA_GFX_SOURCESURFACESKIA_H
#include "skia/include/core/SkCanvas.h"
#include "skia/include/core/SkSurface.h"
#include "2D.h"
#include "HelpersSkia.h"
#include "Rect.h"
#include "PathSkia.h"
#include <sstream>
#include <vector>
#ifdef MOZ_WIDGET_COCOA
#include <ApplicationServices/ApplicationServices.h>
#endif
namespace mozilla {
namespace gfx {
class SourceSurfaceSkia;
class DrawTargetSkia : public DrawTarget
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetSkia, override)
DrawTargetSkia();
virtual ~DrawTargetSkia();
virtual DrawTargetType GetType() const override;
virtual BackendType GetBackendType() const override { return BackendType::SKIA; }
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual IntSize GetSize() override { return mSize; }
virtual bool LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat,
IntPoint* aOrigin = nullptr) override;
virtual void ReleaseBits(uint8_t* aData) override;
virtual void Flush() override;
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator) override;
virtual void ClearRect(const Rect &aRect) override;
virtual void CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination) override;
virtual void FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
#ifdef MOZ_WIDGET_COCOA
CGContextRef BorrowCGContext(const DrawOptions &aOptions);
void ReturnCGContext(CGContextRef);
bool FillGlyphsWithCG(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions(),
const GlyphRenderingOptions *aRenderingOptions = nullptr);
#endif
virtual void FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions(),
const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
virtual void Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions()) override;
virtual bool Draw3DTransformedSurface(SourceSurface* aSurface,
const Matrix4x4& aMatrix) override;
virtual void PushClip(const Path *aPath) override;
virtual void PushClipRect(const Rect& aRect) override;
virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override;
virtual void PopClip() override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
virtual void PopLayer() override;
virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const override;
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const override;
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override;
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
virtual already_AddRefed<GradientStops> CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
virtual void SetTransform(const Matrix &aTransform) override;
virtual void *GetNativeSurface(NativeSurfaceType aType) override;
virtual void DetachAllSnapshots() override { MarkChanged(); }
bool Init(const IntSize &aSize, SurfaceFormat aFormat);
bool Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat, bool aUninitialized = false);
bool Init(SkCanvas* aCanvas);
#ifdef USE_SKIA_GPU
bool InitWithGrContext(GrContext* aGrContext,
const IntSize &aSize,
SurfaceFormat aFormat,
bool aCached);
virtual bool
InitWithGrContext(GrContext* aGrContext,
const IntSize &aSize,
SurfaceFormat aFormat) override {
return InitWithGrContext(aGrContext, aSize, aFormat, false);
}
already_AddRefed<SourceSurface> OptimizeGPUSourceSurface(SourceSurface *aSurface) const;
#endif
// Skia assumes that texture sizes fit in 16-bit signed integers.
static size_t GetMaxSurfaceSize() {
return 32767;
}
operator std::string() const {
std::stringstream stream;
stream << "DrawTargetSkia(" << this << ")";
return stream.str();
}
private:
friend class SourceSurfaceSkia;
void SnapshotDestroyed();
void MarkChanged();
bool ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode);
bool UsingSkiaGPU() const;
struct PushedLayer
{
PushedLayer(bool aOldPermitSubpixelAA,
bool aOpaque,
Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform)
: mOldPermitSubpixelAA(aOldPermitSubpixelAA),
mOpaque(aOpaque),
mOpacity(aOpacity),
mMask(aMask),
mMaskTransform(aMaskTransform)
{}
bool mOldPermitSubpixelAA;
bool mOpaque;
Float mOpacity;
RefPtr<SourceSurface> mMask;
Matrix mMaskTransform;
};
std::vector<PushedLayer> mPushedLayers;
#ifdef USE_SKIA_GPU
sk_sp<GrContext> mGrContext;
#endif
IntSize mSize;
sk_sp<SkSurface> mSurface;
sk_sp<SkCanvas> mCanvas;
SourceSurfaceSkia* mSnapshot;
#ifdef MOZ_WIDGET_COCOA
CGContextRef mCG;
CGColorSpaceRef mColorSpace;
uint8_t* mCanvasData;
IntSize mCGSize;
#endif
};
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_SOURCESURFACESKIA_H

337
gfx/2d/DrawTargetTiled.cpp Normal file
View file

@ -0,0 +1,337 @@
/* -*- 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 "DrawTargetTiled.h"
#include "Logging.h"
#include "PathHelpers.h"
using namespace std;
namespace mozilla {
namespace gfx {
DrawTargetTiled::DrawTargetTiled()
{
}
bool
DrawTargetTiled::Init(const TileSet& aTiles)
{
if (!aTiles.mTileCount) {
return false;
}
mTiles.reserve(aTiles.mTileCount);
for (size_t i = 0; i < aTiles.mTileCount; ++i) {
mTiles.push_back(TileInternal(aTiles.mTiles[i]));
if (!aTiles.mTiles[i].mDrawTarget) {
return false;
}
if (mTiles[0].mDrawTarget->GetFormat() != mTiles.back().mDrawTarget->GetFormat() ||
mTiles[0].mDrawTarget->GetBackendType() != mTiles.back().mDrawTarget->GetBackendType()) {
return false;
}
uint32_t newXMost = max(mRect.XMost(),
mTiles[i].mTileOrigin.x + mTiles[i].mDrawTarget->GetSize().width);
uint32_t newYMost = max(mRect.YMost(),
mTiles[i].mTileOrigin.y + mTiles[i].mDrawTarget->GetSize().height);
mRect.x = min(mRect.x, mTiles[i].mTileOrigin.x);
mRect.y = min(mRect.y, mTiles[i].mTileOrigin.y);
mRect.width = newXMost - mRect.x;
mRect.height = newYMost - mRect.y;
mTiles[i].mDrawTarget->SetTransform(Matrix::Translation(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y));
}
mFormat = mTiles[0].mDrawTarget->GetFormat();
return true;
}
already_AddRefed<SourceSurface>
DrawTargetTiled::Snapshot()
{
return MakeAndAddRef<SnapshotTiled>(mTiles, mRect);
}
void
DrawTargetTiled::DetachAllSnapshots()
{}
// Skip the mClippedOut check since this is only used for Flush() which
// should happen even if we're clipped.
#define TILED_COMMAND(command) \
void \
DrawTargetTiled::command() \
{ \
for (size_t i = 0; i < mTiles.size(); i++) { \
mTiles[i].mDrawTarget->command(); \
} \
}
#define TILED_COMMAND1(command, type1) \
void \
DrawTargetTiled::command(type1 arg1) \
{ \
for (size_t i = 0; i < mTiles.size(); i++) { \
if (!mTiles[i].mClippedOut) \
mTiles[i].mDrawTarget->command(arg1); \
} \
}
#define TILED_COMMAND3(command, type1, type2, type3) \
void \
DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3) \
{ \
for (size_t i = 0; i < mTiles.size(); i++) { \
if (!mTiles[i].mClippedOut) \
mTiles[i].mDrawTarget->command(arg1, arg2, arg3); \
} \
}
#define TILED_COMMAND4(command, type1, type2, type3, type4) \
void \
DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
for (size_t i = 0; i < mTiles.size(); i++) { \
if (!mTiles[i].mClippedOut) \
mTiles[i].mDrawTarget->command(arg1, arg2, arg3, arg4); \
} \
}
#define TILED_COMMAND5(command, type1, type2, type3, type4, type5) \
void \
DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5) \
{ \
for (size_t i = 0; i < mTiles.size(); i++) { \
if (!mTiles[i].mClippedOut) \
mTiles[i].mDrawTarget->command(arg1, arg2, arg3, arg4, arg5); \
} \
}
TILED_COMMAND(Flush)
TILED_COMMAND4(DrawFilter, FilterNode*, const Rect&, const Point&, const DrawOptions&)
TILED_COMMAND1(ClearRect, const Rect&)
TILED_COMMAND4(MaskSurface, const Pattern&, SourceSurface*, Point, const DrawOptions&)
TILED_COMMAND5(FillGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&, const DrawOptions&, const GlyphRenderingOptions*)
TILED_COMMAND3(Mask, const Pattern&, const Pattern&, const DrawOptions&)
void
DrawTargetTiled::PushClip(const Path* aPath)
{
mClippedOutTilesStack.push_back(std::vector<uint32_t>());
std::vector<uint32_t>& clippedTiles = mClippedOutTilesStack.back();
Rect deviceRect = aPath->GetBounds(mTransform);
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut) {
if (deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->PushClip(aPath);
} else {
mTiles[i].mClippedOut = true;
clippedTiles.push_back(i);
}
}
}
}
void
DrawTargetTiled::PushClipRect(const Rect& aRect)
{
mClippedOutTilesStack.push_back(std::vector<uint32_t>());
std::vector<uint32_t>& clippedTiles = mClippedOutTilesStack.back();
Rect deviceRect = mTransform.TransformBounds(aRect);
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut) {
if (deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->PushClipRect(aRect);
} else {
mTiles[i].mClippedOut = true;
clippedTiles.push_back(i);
}
}
}
}
void
DrawTargetTiled::PopClip()
{
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut) {
mTiles[i].mDrawTarget->PopClip();
}
}
std::vector<uint32_t>& clippedTiles = mClippedOutTilesStack.back();
for (size_t i = 0; i < clippedTiles.size(); i++) {
mTiles[clippedTiles[i]].mClippedOut = false;
}
mClippedOutTilesStack.pop_back();
}
void
DrawTargetTiled::CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination)
{
for (size_t i = 0; i < mTiles.size(); i++) {
IntPoint tileOrigin = mTiles[i].mTileOrigin;
IntSize tileSize = mTiles[i].mDrawTarget->GetSize();
if (!IntRect(aDestination, aSourceRect.Size()).Intersects(IntRect(tileOrigin, tileSize))) {
continue;
}
// CopySurface ignores the transform, account for that here.
mTiles[i].mDrawTarget->CopySurface(aSurface, aSourceRect, aDestination - tileOrigin);
}
}
void
DrawTargetTiled::SetTransform(const Matrix& aTransform)
{
for (size_t i = 0; i < mTiles.size(); i++) {
Matrix mat = aTransform;
mat.PostTranslate(Float(-mTiles[i].mTileOrigin.x), Float(-mTiles[i].mTileOrigin.y));
mTiles[i].mDrawTarget->SetTransform(mat);
}
DrawTarget::SetTransform(aTransform);
}
void
DrawTargetTiled::DrawSurface(SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, const DrawSurfaceOptions& aSurfaceOptions, const DrawOptions& aDrawOptions)
{
Rect deviceRect = mTransform.TransformBounds(aDest);
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut &&
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->DrawSurface(aSurface, aDest, aSource, aSurfaceOptions, aDrawOptions);
}
}
}
void
DrawTargetTiled::FillRect(const Rect& aRect, const Pattern& aPattern, const DrawOptions& aDrawOptions)
{
Rect deviceRect = mTransform.TransformBounds(aRect);
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut &&
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->FillRect(aRect, aPattern, aDrawOptions);
}
}
}
void
DrawTargetTiled::Stroke(const Path* aPath, const Pattern& aPattern, const StrokeOptions& aStrokeOptions, const DrawOptions& aDrawOptions)
{
// Approximate the stroke extents, since Path::GetStrokeExtents can be slow
Rect deviceRect = aPath->GetBounds(mTransform);
deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform));
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut &&
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->Stroke(aPath, aPattern, aStrokeOptions, aDrawOptions);
}
}
}
void
DrawTargetTiled::StrokeRect(const Rect& aRect, const Pattern& aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions& aDrawOptions)
{
Rect deviceRect = mTransform.TransformBounds(aRect);
Margin strokeMargin = MaxStrokeExtents(aStrokeOptions, mTransform);
Rect outerRect = deviceRect;
outerRect.Inflate(strokeMargin);
Rect innerRect;
if (mTransform.IsRectilinear()) {
// If rects are mapped to rects, we can compute the inner rect
// of the stroked rect.
innerRect = deviceRect;
innerRect.Deflate(strokeMargin);
}
for (size_t i = 0; i < mTiles.size(); i++) {
if (mTiles[i].mClippedOut) {
continue;
}
Rect tileRect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height);
if (outerRect.Intersects(tileRect) && !innerRect.Contains(tileRect)) {
mTiles[i].mDrawTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aDrawOptions);
}
}
}
void
DrawTargetTiled::StrokeLine(const Point& aStart, const Point& aEnd, const Pattern& aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions& aDrawOptions)
{
Rect lineBounds = Rect(aStart, Size()).UnionEdges(Rect(aEnd, Size()));
Rect deviceRect = mTransform.TransformBounds(lineBounds);
deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform));
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut &&
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aDrawOptions);
}
}
}
void
DrawTargetTiled::Fill(const Path* aPath, const Pattern& aPattern, const DrawOptions& aDrawOptions)
{
Rect deviceRect = aPath->GetBounds(mTransform);
for (size_t i = 0; i < mTiles.size(); i++) {
if (!mTiles[i].mClippedOut &&
deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
mTiles[i].mTileOrigin.y,
mTiles[i].mDrawTarget->GetSize().width,
mTiles[i].mDrawTarget->GetSize().height))) {
mTiles[i].mDrawTarget->Fill(aPath, aPattern, aDrawOptions);
}
}
}
void
DrawTargetTiled::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
const Matrix& aMaskTransform, const IntRect& aBounds,
bool aCopyBackground)
{
// XXX - not sure this is what we want or whether we want to continue drawing to a larger
// intermediate surface, that would require tweaking the code in here a little though.
for (size_t i = 0; i < mTiles.size(); i++) {
IntRect bounds = aBounds;
bounds.MoveBy(-mTiles[i].mTileOrigin);
mTiles[i].mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds);
}
}
void
DrawTargetTiled::PopLayer()
{
// XXX - not sure this is what we want or whether we want to continue drawing to a larger
// intermediate surface, that would require tweaking the code in here a little though.
for (size_t i = 0; i < mTiles.size(); i++) {
mTiles[i].mDrawTarget->PopLayer();
}
}
} // namespace gfx
} // namespace mozilla

221
gfx/2d/DrawTargetTiled.h Normal file
View file

@ -0,0 +1,221 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_DRAWTARGETTILED_H_
#define MOZILLA_GFX_DRAWTARGETTILED_H_
#include "2D.h"
#include "Filters.h"
#include "Logging.h"
#include <vector>
namespace mozilla {
namespace gfx {
struct TileInternal : public Tile {
TileInternal()
: mClippedOut(false)
{}
explicit TileInternal(const Tile& aOther)
: Tile(aOther)
, mClippedOut(false)
{}
bool mClippedOut;
};
class DrawTargetTiled : public DrawTarget
{
public:
DrawTargetTiled();
bool Init(const TileSet& mTiles);
virtual bool IsTiledDrawTarget() const override { return true; }
virtual DrawTargetType GetType() const override { return mTiles[0].mDrawTarget->GetType(); }
virtual BackendType GetBackendType() const override { return mTiles[0].mDrawTarget->GetBackendType(); }
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual void DetachAllSnapshots() override;
virtual IntSize GetSize() override {
MOZ_ASSERT(mRect.width > 0 && mRect.height > 0);
return IntSize(mRect.XMost(), mRect.YMost());
}
virtual void Flush() override;
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aOptions) override;
virtual void DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator) override { /* Not implemented */ MOZ_CRASH("GFX: DrawSurfaceWithShadow"); }
virtual void ClearRect(const Rect &aRect) override;
virtual void MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination) override;
virtual void FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions = StrokeOptions(),
const DrawOptions &aOptions = DrawOptions()) override;
virtual void Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions = DrawOptions(),
const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
virtual void Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions = DrawOptions()) override;
virtual void PushClip(const Path *aPath) override;
virtual void PushClipRect(const Rect &aRect) override;
virtual void PopClip() override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
virtual void PopLayer() override;
virtual void SetTransform(const Matrix &aTransform) override;
virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const override
{
return mTiles[0].mDrawTarget->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
}
virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override
{
return mTiles[0].mDrawTarget->OptimizeSourceSurface(aSurface);
}
virtual already_AddRefed<SourceSurface>
CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override
{
return mTiles[0].mDrawTarget->CreateSourceSurfaceFromNativeSurface(aSurface);
}
virtual already_AddRefed<DrawTarget>
CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override
{
return mTiles[0].mDrawTarget->CreateSimilarDrawTarget(aSize, aFormat);
}
virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override
{
return mTiles[0].mDrawTarget->CreatePathBuilder(aFillRule);
}
virtual already_AddRefed<GradientStops>
CreateGradientStops(GradientStop *aStops,
uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const override
{
return mTiles[0].mDrawTarget->CreateGradientStops(aStops, aNumStops, aExtendMode);
}
virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override
{
return mTiles[0].mDrawTarget->CreateFilter(aType);
}
private:
std::vector<TileInternal> mTiles;
std::vector<std::vector<uint32_t> > mClippedOutTilesStack;
IntRect mRect;
};
class SnapshotTiled : public SourceSurface
{
public:
SnapshotTiled(const std::vector<TileInternal>& aTiles, const IntRect& aRect)
: mRect(aRect)
{
for (size_t i = 0; i < aTiles.size(); i++) {
mSnapshots.push_back(aTiles[i].mDrawTarget->Snapshot());
mOrigins.push_back(aTiles[i].mTileOrigin);
}
}
virtual SurfaceType GetType() const { return SurfaceType::TILED; }
virtual IntSize GetSize() const {
MOZ_ASSERT(mRect.width > 0 && mRect.height > 0);
return IntSize(mRect.XMost(), mRect.YMost());
}
virtual SurfaceFormat GetFormat() const { return mSnapshots[0]->GetFormat(); }
virtual already_AddRefed<DataSourceSurface> GetDataSurface()
{
RefPtr<DataSourceSurface> surf = Factory::CreateDataSourceSurface(GetSize(), GetFormat());
DataSourceSurface::MappedSurface mappedSurf;
if (!surf->Map(DataSourceSurface::MapType::WRITE, &mappedSurf)) {
gfxCriticalError() << "DrawTargetTiled::GetDataSurface failed to map surface";
return nullptr;
}
{
RefPtr<DrawTarget> dt =
Factory::CreateDrawTargetForData(BackendType::CAIRO, mappedSurf.mData,
GetSize(), mappedSurf.mStride, GetFormat());
if (!dt) {
gfxWarning() << "DrawTargetTiled::GetDataSurface failed in CreateDrawTargetForData";
surf->Unmap();
return nullptr;
}
for (size_t i = 0; i < mSnapshots.size(); i++) {
RefPtr<DataSourceSurface> dataSurf = mSnapshots[i]->GetDataSurface();
dt->CopySurface(dataSurf, IntRect(IntPoint(0, 0), mSnapshots[i]->GetSize()), mOrigins[i]);
}
}
surf->Unmap();
return surf.forget();
}
std::vector<RefPtr<SourceSurface>> mSnapshots;
std::vector<IntPoint> mOrigins;
IntRect mRect;
};
} // namespace gfx
} // namespace mozilla
#endif

120
gfx/2d/DrawingJob.cpp Normal file
View file

@ -0,0 +1,120 @@
/* -*- 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 "DrawingJob.h"
#include "JobScheduler.h"
#include "mozilla/gfx/2D.h"
namespace mozilla {
namespace gfx {
DrawingJobBuilder::DrawingJobBuilder()
{}
DrawingJobBuilder::~DrawingJobBuilder()
{
MOZ_ASSERT(!mDrawTarget);
}
void
DrawingJob::Clear()
{
mCommandBuffer = nullptr;
mCursor = 0;
}
void
DrawingJobBuilder::BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart)
{
MOZ_ASSERT(mCommandOffsets.empty());
MOZ_ASSERT(aTarget);
mDrawTarget = aTarget;
mOffset = aOffset;
mStart = aStart;
}
DrawingJob*
DrawingJobBuilder::EndDrawingJob(CommandBuffer* aCmdBuffer,
SyncObject* aCompletion,
WorkerThread* aPinToWorker)
{
MOZ_ASSERT(mDrawTarget);
DrawingJob* task = new DrawingJob(mDrawTarget, mOffset, mStart, aCompletion, aPinToWorker);
task->mCommandBuffer = aCmdBuffer;
task->mCommandOffsets = Move(mCommandOffsets);
mDrawTarget = nullptr;
mOffset = IntPoint();
mStart = nullptr;
return task;
}
DrawingJob::DrawingJob(DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart, SyncObject* aCompletion,
WorkerThread* aPinToWorker)
: Job(aStart, aCompletion, aPinToWorker)
, mCommandBuffer(nullptr)
, mCursor(0)
, mDrawTarget(aTarget)
, mOffset(aOffset)
{
mCommandOffsets.reserve(64);
}
JobStatus
DrawingJob::Run()
{
while (mCursor < mCommandOffsets.size()) {
const DrawingCommand* cmd = mCommandBuffer->GetDrawingCommand(mCommandOffsets[mCursor]);
if (!cmd) {
return JobStatus::Error;
}
cmd->ExecuteOnDT(mDrawTarget);
++mCursor;
}
return JobStatus::Complete;
}
DrawingJob::~DrawingJob()
{
Clear();
}
const DrawingCommand*
CommandBuffer::GetDrawingCommand(ptrdiff_t aId)
{
return static_cast<DrawingCommand*>(mStorage.GetStorage(aId));
}
CommandBuffer::~CommandBuffer()
{
mStorage.ForEach([](void* item){
static_cast<DrawingCommand*>(item)->~DrawingCommand();
});
mStorage.Clear();
}
void
CommandBufferBuilder::BeginCommandBuffer(size_t aBufferSize)
{
MOZ_ASSERT(!mCommands);
mCommands = new CommandBuffer(aBufferSize);
}
already_AddRefed<CommandBuffer>
CommandBufferBuilder::EndCommandBuffer()
{
return mCommands.forget();
}
} // namespace
} // namespace

158
gfx/2d/DrawingJob.h Normal file
View file

@ -0,0 +1,158 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_COMMANDBUFFER_H_
#define MOZILLA_GFX_COMMANDBUFFER_H_
#include <stdint.h>
#include "mozilla/RefPtr.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/JobScheduler.h"
#include "mozilla/gfx/IterableArena.h"
#include "mozilla/RefCounted.h"
#include "DrawCommand.h"
namespace mozilla {
namespace gfx {
class DrawingCommand;
class PrintCommand;
class SignalCommand;
class DrawingJob;
class WaitCommand;
class SyncObject;
class MultiThreadedJobQueue;
class DrawTarget;
class DrawingJobBuilder;
class CommandBufferBuilder;
/// Contains a sequence of immutable drawing commands that are typically used by
/// several DrawingJobs.
///
/// CommandBuffer objects are built using CommandBufferBuilder.
class CommandBuffer : public external::AtomicRefCounted<CommandBuffer>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(CommandBuffer)
~CommandBuffer();
const DrawingCommand* GetDrawingCommand(ptrdiff_t aId);
protected:
explicit CommandBuffer(size_t aSize = 256)
: mStorage(IterableArena::GROWABLE, aSize)
{}
IterableArena mStorage;
friend class CommandBufferBuilder;
};
/// Generates CommandBuffer objects.
///
/// The builder is a separate object to ensure that commands are not added to a
/// submitted CommandBuffer.
class CommandBufferBuilder
{
public:
void BeginCommandBuffer(size_t aBufferSize = 256);
already_AddRefed<CommandBuffer> EndCommandBuffer();
/// Build the CommandBuffer, command after command.
/// This must be used between BeginCommandBuffer and EndCommandBuffer.
template<typename T, typename... Args>
ptrdiff_t AddCommand(Args&&... aArgs)
{
static_assert(IsBaseOf<DrawingCommand, T>::value,
"T must derive from DrawingCommand");
return mCommands->mStorage.Alloc<T>(Forward<Args>(aArgs)...);
}
bool HasCommands() const { return !!mCommands; }
protected:
RefPtr<CommandBuffer> mCommands;
};
/// Stores multiple commands to be executed sequencially.
class DrawingJob : public Job {
public:
~DrawingJob();
virtual JobStatus Run() override;
protected:
DrawingJob(DrawTarget* aTarget,
IntPoint aOffset,
SyncObject* aStart,
SyncObject* aCompletion,
WorkerThread* aPinToWorker = nullptr);
/// Runs the tasks's destructors and resets the buffer.
void Clear();
std::vector<ptrdiff_t> mCommandOffsets;
RefPtr<CommandBuffer> mCommandBuffer;
uint32_t mCursor;
RefPtr<DrawTarget> mDrawTarget;
IntPoint mOffset;
friend class DrawingJobBuilder;
};
/// Generates DrawingJob objects.
///
/// The builder is a separate object to ensure that commands are not added to a
/// submitted DrawingJob.
class DrawingJobBuilder {
public:
DrawingJobBuilder();
~DrawingJobBuilder();
/// Allocates a DrawingJob.
///
/// call this method before starting to add commands.
void BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart = nullptr);
/// Build the DrawingJob, command after command.
/// This must be used between BeginDrawingJob and EndDrawingJob.
void AddCommand(ptrdiff_t offset)
{
mCommandOffsets.push_back(offset);
}
/// Finalizes and returns the drawing task.
///
/// If aCompletion is not null, the sync object will be signaled after the
/// task buffer is destroyed (and after the destructor of the tasks have run).
/// In most cases this means after the completion of all tasks in the task buffer,
/// but also when the task buffer is destroyed due to an error.
DrawingJob* EndDrawingJob(CommandBuffer* aCmdBuffer,
SyncObject* aCompletion = nullptr,
WorkerThread* aPinToWorker = nullptr);
/// Returns true between BeginDrawingJob and EndDrawingJob, false otherwise.
bool HasDrawingJob() const { return !!mDrawTarget; }
protected:
std::vector<ptrdiff_t> mCommandOffsets;
RefPtr<DrawTarget> mDrawTarget;
IntPoint mOffset;
RefPtr<SyncObject> mStart;
};
} // namespace
} // namespace
#endif

View file

@ -0,0 +1,204 @@
/* -*- 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 "ExtendInputEffectD2D1.h"
#include "Logging.h"
#include "ShadersD2D1.h"
#include "HelpersD2D.h"
#include <vector>
#define TEXTW(x) L##x
#define XML(X) TEXTW(#X) // This macro creates a single string from multiple lines of text.
static const PCWSTR kXmlDescription =
XML(
<?xml version='1.0'?>
<Effect>
<!-- System Properties -->
<Property name='DisplayName' type='string' value='ExtendInputEffect'/>
<Property name='Author' type='string' value='Mozilla'/>
<Property name='Category' type='string' value='Utility Effects'/>
<Property name='Description' type='string' value='This effect is used to extend the output rect of any input effect to a specified rect.'/>
<Inputs>
<Input name='InputEffect'/>
</Inputs>
<Property name='OutputRect' type='vector4'>
<Property name='DisplayName' type='string' value='Output Rect'/>
</Property>
</Effect>
);
namespace mozilla {
namespace gfx {
ExtendInputEffectD2D1::ExtendInputEffectD2D1()
: mRefCount(0)
, mOutputRect(D2D1::Vector4F(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX))
{
}
IFACEMETHODIMP
ExtendInputEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, ID2D1TransformGraph* pTransformGraph)
{
HRESULT hr;
hr = pTransformGraph->SetSingleTransformNode(this);
if (FAILED(hr)) {
return hr;
}
return S_OK;
}
IFACEMETHODIMP
ExtendInputEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType)
{
return S_OK;
}
IFACEMETHODIMP
ExtendInputEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph)
{
return E_NOTIMPL;
}
IFACEMETHODIMP_(ULONG)
ExtendInputEffectD2D1::AddRef()
{
return ++mRefCount;
}
IFACEMETHODIMP_(ULONG)
ExtendInputEffectD2D1::Release()
{
if (!--mRefCount) {
delete this;
return 0;
}
return mRefCount;
}
IFACEMETHODIMP
ExtendInputEffectD2D1::QueryInterface(const IID &aIID, void **aPtr)
{
if (!aPtr) {
return E_POINTER;
}
if (aIID == IID_IUnknown) {
*aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this));
} else if (aIID == IID_ID2D1EffectImpl) {
*aPtr = static_cast<ID2D1EffectImpl*>(this);
} else if (aIID == IID_ID2D1DrawTransform) {
*aPtr = static_cast<ID2D1DrawTransform*>(this);
} else if (aIID == IID_ID2D1Transform) {
*aPtr = static_cast<ID2D1Transform*>(this);
} else if (aIID == IID_ID2D1TransformNode) {
*aPtr = static_cast<ID2D1TransformNode*>(this);
} else {
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*aPtr)->AddRef();
return S_OK;
}
static D2D1_RECT_L
ConvertFloatToLongRect(const D2D1_VECTOR_4F& aRect)
{
// Clamp values to LONG range. We can't use std::min/max here because we want
// the comparison to operate on a type that's different from the type of the
// result.
return D2D1::RectL(aRect.x <= LONG_MIN ? LONG_MIN : LONG(aRect.x),
aRect.y <= LONG_MIN ? LONG_MIN : LONG(aRect.y),
aRect.z >= LONG_MAX ? LONG_MAX : LONG(aRect.z),
aRect.w >= LONG_MAX ? LONG_MAX : LONG(aRect.w));
}
static D2D1_RECT_L
IntersectRect(const D2D1_RECT_L& aRect1, const D2D1_RECT_L& aRect2)
{
return D2D1::RectL(std::max(aRect1.left, aRect2.left),
std::max(aRect1.top, aRect2.top),
std::min(aRect1.right, aRect2.right),
std::min(aRect1.bottom, aRect2.bottom));
}
IFACEMETHODIMP
ExtendInputEffectD2D1::MapInputRectsToOutputRect(const D2D1_RECT_L* pInputRects,
const D2D1_RECT_L* pInputOpaqueSubRects,
UINT32 inputRectCount,
D2D1_RECT_L* pOutputRect,
D2D1_RECT_L* pOutputOpaqueSubRect)
{
// This transform only accepts one input, so there will only be one input rect.
if (inputRectCount != 1) {
return E_INVALIDARG;
}
// Set the output rect to the specified rect. This is the whole purpose of this effect.
*pOutputRect = ConvertFloatToLongRect(mOutputRect);
*pOutputOpaqueSubRect = IntersectRect(*pOutputRect, pInputOpaqueSubRects[0]);
return S_OK;
}
IFACEMETHODIMP
ExtendInputEffectD2D1::MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect,
D2D1_RECT_L* pInputRects,
UINT32 inputRectCount) const
{
if (inputRectCount != 1) {
return E_INVALIDARG;
}
*pInputRects = *pOutputRect;
return S_OK;
}
IFACEMETHODIMP
ExtendInputEffectD2D1::MapInvalidRect(UINT32 inputIndex,
D2D1_RECT_L invalidInputRect,
D2D1_RECT_L* pInvalidOutputRect) const
{
MOZ_ASSERT(inputIndex == 0);
*pInvalidOutputRect = invalidInputRect;
return S_OK;
}
HRESULT
ExtendInputEffectD2D1::Register(ID2D1Factory1 *aFactory)
{
D2D1_PROPERTY_BINDING bindings[] = {
D2D1_VALUE_TYPE_BINDING(L"OutputRect", &ExtendInputEffectD2D1::SetOutputRect, &ExtendInputEffectD2D1::GetOutputRect),
};
HRESULT hr = aFactory->RegisterEffectFromString(CLSID_ExtendInputEffect, kXmlDescription, bindings, 1, CreateEffect);
if (FAILED(hr)) {
gfxWarning() << "Failed to register extend input effect.";
}
return hr;
}
void
ExtendInputEffectD2D1::Unregister(ID2D1Factory1 *aFactory)
{
aFactory->UnregisterEffect(CLSID_ExtendInputEffect);
}
HRESULT __stdcall
ExtendInputEffectD2D1::CreateEffect(IUnknown **aEffectImpl)
{
*aEffectImpl = static_cast<ID2D1EffectImpl*>(new ExtendInputEffectD2D1());
(*aEffectImpl)->AddRef();
return S_OK;
}
}
}

View file

@ -0,0 +1,88 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_EXTENDINPUTEFFECTD2D1_H_
#define MOZILLA_GFX_EXTENDINPUTEFFECTD2D1_H_
#include <d2d1_1.h>
#include <d2d1effectauthor.h>
#include <d2d1effecthelpers.h>
#include "2D.h"
#include "mozilla/Attributes.h"
// {97143DC6-CBC4-4DD4-A8BA-13342B0BA46D}
DEFINE_GUID(CLSID_ExtendInputEffect,
0x5fb55c7c, 0xd795, 0x4ba3, 0xa9, 0x5c, 0x22, 0x82, 0x5d, 0x0c, 0x4d, 0xf7);
namespace mozilla {
namespace gfx {
enum {
EXTENDINPUT_PROP_OUTPUT_RECT = 0
};
// An effect type that passes through its input unchanged but sets the effect's
// output rect to a specified rect. Unlike the built-in Crop effect, the
// ExtendInput effect can extend the input rect, and not just make it smaller.
// The added margins are filled with transparent black.
// Some effects have different output depending on their input effect's output
// rect, for example the Border effect (which repeats the edges of its input
// effect's output rect) or the component transfer and color matrix effects
// (which can transform transparent pixels into non-transparent ones, but only
// inside their input effect's output rect).
class ExtendInputEffectD2D1 final : public ID2D1EffectImpl
, public ID2D1DrawTransform
{
public:
// ID2D1EffectImpl
IFACEMETHODIMP Initialize(ID2D1EffectContext* pContextInternal, ID2D1TransformGraph* pTransformGraph);
IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType);
IFACEMETHODIMP SetGraph(ID2D1TransformGraph* pGraph);
// IUnknown
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppOutput);
// ID2D1Transform
IFACEMETHODIMP MapInputRectsToOutputRect(const D2D1_RECT_L* pInputRects,
const D2D1_RECT_L* pInputOpaqueSubRects,
UINT32 inputRectCount,
D2D1_RECT_L* pOutputRect,
D2D1_RECT_L* pOutputOpaqueSubRect);
IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect,
D2D1_RECT_L* pInputRects,
UINT32 inputRectCount) const;
IFACEMETHODIMP MapInvalidRect(UINT32 inputIndex,
D2D1_RECT_L invalidInputRect,
D2D1_RECT_L* pInvalidOutputRect) const;
// ID2D1TransformNode
IFACEMETHODIMP_(UINT32) GetInputCount() const { return 1; }
// ID2D1DrawTransform
IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo *pDrawInfo) { return S_OK; }
static HRESULT Register(ID2D1Factory1* aFactory);
static void Unregister(ID2D1Factory1* aFactory);
static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl);
HRESULT SetOutputRect(D2D1_VECTOR_4F aOutputRect)
{ mOutputRect = aOutputRect; return S_OK; }
D2D1_VECTOR_4F GetOutputRect() const { return mOutputRect; }
private:
ExtendInputEffectD2D1();
uint32_t mRefCount;
D2D1_VECTOR_4F mOutputRect;
};
}
}
#undef SIMPLE_PROP
#endif

985
gfx/2d/Factory.cpp Normal file
View file

@ -0,0 +1,985 @@
/* -*- 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 "2D.h"
#ifdef USE_CAIRO
#include "DrawTargetCairo.h"
#include "ScaledFontCairo.h"
#include "SourceSurfaceCairo.h"
#endif
#ifdef USE_SKIA
#include "DrawTargetSkia.h"
#include "ScaledFontBase.h"
#ifdef MOZ_ENABLE_FREETYPE
#define USE_SKIA_FREETYPE
#include "ScaledFontCairo.h"
#endif
#endif
#if defined(WIN32)
#include "ScaledFontWin.h"
#include "NativeFontResourceGDI.h"
#endif
#ifdef XP_DARWIN
#include "ScaledFontMac.h"
#include "NativeFontResourceMac.h"
#endif
#ifdef MOZ_WIDGET_GTK
#include "ScaledFontFontconfig.h"
#endif
#ifdef WIN32
#include "DrawTargetD2D1.h"
#include "ScaledFontDWrite.h"
#include "NativeFontResourceDWrite.h"
#include <d3d10_1.h>
#include "HelpersD2D.h"
#endif
#include "DrawTargetDual.h"
#include "DrawTargetTiled.h"
#include "DrawTargetRecording.h"
#include "SourceSurfaceRawData.h"
#include "DrawEventRecorder.h"
#include "Logging.h"
#include "mozilla/CheckedInt.h"
#if defined(MOZ_LOGGING)
GFX2D_API mozilla::LogModule*
GetGFX2DLog()
{
static mozilla::LazyLogModule sLog("gfx2d");
return sLog;
}
#endif
// The following code was largely taken from xpcom/glue/SSE.cpp and
// made a little simpler.
enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 };
#ifdef HAVE_CPUID_H
#if !(defined(__SSE2__) || defined(_M_X64) || \
(defined(_M_IX86_FP) && _M_IX86_FP >= 2))
// cpuid.h is available on gcc 4.3 and higher on i386 and x86_64
#include <cpuid.h>
static inline bool
HasCPUIDBit(unsigned int level, CPUIDRegister reg, unsigned int bit)
{
unsigned int regs[4];
return __get_cpuid(level, &regs[0], &regs[1], &regs[2], &regs[3]) &&
(regs[reg] & bit);
}
#endif
#define HAVE_CPU_DETECTION
#else
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
// MSVC 2005 or later supports __cpuid by intrin.h
#include <intrin.h>
#define HAVE_CPU_DETECTION
#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__))
// Define a function identical to MSVC function.
#ifdef __i386
static void
__cpuid(int CPUInfo[4], int InfoType)
{
asm (
"xchg %esi, %ebx\n"
"cpuid\n"
"movl %eax, (%edi)\n"
"movl %ebx, 4(%edi)\n"
"movl %ecx, 8(%edi)\n"
"movl %edx, 12(%edi)\n"
"xchg %esi, %ebx\n"
:
: "a"(InfoType), // %eax
"D"(CPUInfo) // %edi
: "%ecx", "%edx", "%esi"
);
}
#else
static void
__cpuid(int CPUInfo[4], int InfoType)
{
asm (
"xchg %rsi, %rbx\n"
"cpuid\n"
"movl %eax, (%rdi)\n"
"movl %ebx, 4(%rdi)\n"
"movl %ecx, 8(%rdi)\n"
"movl %edx, 12(%rdi)\n"
"xchg %rsi, %rbx\n"
:
: "a"(InfoType), // %eax
"D"(CPUInfo) // %rdi
: "%ecx", "%edx", "%rsi"
);
}
#define HAVE_CPU_DETECTION
#endif
#endif
#ifdef HAVE_CPU_DETECTION
static inline bool
HasCPUIDBit(unsigned int level, CPUIDRegister reg, unsigned int bit)
{
// Check that the level in question is supported.
volatile int regs[4];
__cpuid((int *)regs, level & 0x80000000u);
if (unsigned(regs[0]) < level)
return false;
__cpuid((int *)regs, level);
return !!(unsigned(regs[reg]) & bit);
}
#endif
#endif
namespace mozilla {
namespace gfx {
// In Gecko, this value is managed by gfx.logging.level in gfxPrefs.
int32_t LoggingPrefs::sGfxLogLevel = LOG_DEFAULT;
#ifdef WIN32
ID3D11Device *Factory::mD3D11Device = nullptr;
ID2D1Device *Factory::mD2D1Device = nullptr;
IDWriteFactory *Factory::mDWriteFactory = nullptr;
#endif
DrawEventRecorder *Factory::mRecorder;
mozilla::gfx::Config* Factory::sConfig = nullptr;
void
Factory::Init(const Config& aConfig)
{
MOZ_ASSERT(!sConfig);
sConfig = new Config(aConfig);
// Make sure we don't completely break rendering because of a typo in the
// pref or whatnot.
const int32_t kMinAllocPref = 10000000;
const int32_t kMinSizePref = 2048;
if (sConfig->mMaxAllocSize < kMinAllocPref) {
sConfig->mMaxAllocSize = kMinAllocPref;
}
if (sConfig->mMaxTextureSize < kMinSizePref) {
sConfig->mMaxTextureSize = kMinSizePref;
}
}
void
Factory::ShutDown()
{
if (sConfig) {
delete sConfig->mLogForwarder;
delete sConfig;
sConfig = nullptr;
}
}
bool
Factory::HasSSE2()
{
#if defined(__SSE2__) || defined(_M_X64) || \
(defined(_M_IX86_FP) && _M_IX86_FP >= 2)
// gcc with -msse2 (default on OSX and x86-64)
// cl.exe with -arch:SSE2 (default on x64 compiler)
return true;
#elif defined(HAVE_CPU_DETECTION)
static enum {
UNINITIALIZED,
NO_SSE2,
HAS_SSE2
} sDetectionState = UNINITIALIZED;
if (sDetectionState == UNINITIALIZED) {
sDetectionState = HasCPUIDBit(1u, edx, (1u<<26)) ? HAS_SSE2 : NO_SSE2;
}
return sDetectionState == HAS_SSE2;
#else
return false;
#endif
}
// If the size is "reasonable", we want gfxCriticalError to assert, so
// this is the option set up for it.
inline int LoggerOptionsBasedOnSize(const IntSize& aSize)
{
return CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize));
}
bool
Factory::ReasonableSurfaceSize(const IntSize &aSize)
{
return Factory::CheckSurfaceSize(aSize, 8192);
}
bool
Factory::AllowedSurfaceSize(const IntSize &aSize)
{
if (sConfig) {
return Factory::CheckSurfaceSize(aSize,
sConfig->mMaxTextureSize,
sConfig->mMaxAllocSize);
}
return CheckSurfaceSize(aSize);
}
bool
Factory::CheckBufferSize(int32_t bufSize)
{
return !sConfig || bufSize < sConfig->mMaxAllocSize;
}
bool
Factory::CheckSurfaceSize(const IntSize &sz,
int32_t extentLimit,
int32_t allocLimit)
{
if (sz.width <= 0 || sz.height <= 0) {
gfxDebug() << "Surface width or height <= 0!";
return false;
}
// reject images with sides bigger than limit
if (extentLimit && (sz.width > extentLimit || sz.height > extentLimit)) {
gfxDebug() << "Surface size too large (exceeds extent limit)!";
return false;
}
#if defined(XP_MACOSX)
// CoreGraphics is limited to images < 32K in *height*,
// so clamp all surfaces on the Mac to that height
if (sz.height > SHRT_MAX) {
gfxDebug() << "Surface size too large (exceeds CoreGraphics limit)!";
return false;
}
#endif
// assuming 4 bytes per pixel, make sure the allocation size
// doesn't overflow a int32_t either
CheckedInt<int32_t> stride = GetAlignedStride<16>(sz.width, 4);
if (!stride.isValid() || stride.value() == 0) {
gfxDebug() << "Surface size too large (stride overflows int32_t)!";
return false;
}
CheckedInt<int32_t> numBytes = stride * sz.height;
if (!numBytes.isValid()) {
gfxDebug() << "Surface size too large (allocation size would overflow int32_t)!";
return false;
}
if (allocLimit && allocLimit < numBytes.value()) {
gfxDebug() << "Surface size too large (exceeds allocation limit)!";
return false;
}
return true;
}
already_AddRefed<DrawTarget>
Factory::CreateDrawTarget(BackendType aBackend, const IntSize &aSize, SurfaceFormat aFormat)
{
if (!AllowedSurfaceSize(aSize)) {
gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to allocate a surface due to invalid size (CDT) " << aSize;
return nullptr;
}
RefPtr<DrawTarget> retVal;
switch (aBackend) {
#ifdef WIN32
case BackendType::DIRECT2D1_1:
{
RefPtr<DrawTargetD2D1> newTarget;
newTarget = new DrawTargetD2D1();
if (newTarget->Init(aSize, aFormat)) {
retVal = newTarget;
}
break;
}
#endif
#ifdef USE_SKIA
case BackendType::SKIA:
{
RefPtr<DrawTargetSkia> newTarget;
newTarget = new DrawTargetSkia();
if (newTarget->Init(aSize, aFormat)) {
retVal = newTarget;
}
break;
}
#endif
#ifdef USE_CAIRO
case BackendType::CAIRO:
{
RefPtr<DrawTargetCairo> newTarget;
newTarget = new DrawTargetCairo();
if (newTarget->Init(aSize, aFormat)) {
retVal = newTarget;
}
break;
}
#endif
default:
return nullptr;
}
if (mRecorder && retVal) {
return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal);
}
if (!retVal) {
// Failed
gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to create DrawTarget, Type: " << int(aBackend) << " Size: " << aSize;
}
return retVal.forget();
}
already_AddRefed<DrawTarget>
Factory::CreateRecordingDrawTarget(DrawEventRecorder *aRecorder, DrawTarget *aDT)
{
return MakeAndAddRef<DrawTargetRecording>(aRecorder, aDT);
}
already_AddRefed<DrawTarget>
Factory::CreateDrawTargetForData(BackendType aBackend,
unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat,
bool aUninitialized)
{
MOZ_ASSERT(aData);
if (!AllowedSurfaceSize(aSize)) {
gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to allocate a surface due to invalid size (DTD) " << aSize;
return nullptr;
}
RefPtr<DrawTarget> retVal;
switch (aBackend) {
#ifdef USE_SKIA
case BackendType::SKIA:
{
RefPtr<DrawTargetSkia> newTarget;
newTarget = new DrawTargetSkia();
if (newTarget->Init(aData, aSize, aStride, aFormat, aUninitialized)) {
retVal = newTarget;
}
break;
}
#endif
#ifdef USE_CAIRO
case BackendType::CAIRO:
{
RefPtr<DrawTargetCairo> newTarget;
newTarget = new DrawTargetCairo();
if (newTarget->Init(aData, aSize, aStride, aFormat)) {
retVal = newTarget.forget();
}
break;
}
#endif
default:
gfxCriticalNote << "Invalid draw target type specified: " << (int)aBackend;
return nullptr;
}
if (mRecorder && retVal) {
return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal, true);
}
if (!retVal) {
gfxCriticalNote << "Failed to create DrawTarget, Type: " << int(aBackend) << " Size: " << aSize << ", Data: " << hexa((void *)aData) << ", Stride: " << aStride;
}
return retVal.forget();
}
already_AddRefed<DrawTarget>
Factory::CreateTiledDrawTarget(const TileSet& aTileSet)
{
RefPtr<DrawTargetTiled> dt = new DrawTargetTiled();
if (!dt->Init(aTileSet)) {
return nullptr;
}
return dt.forget();
}
bool
Factory::DoesBackendSupportDataDrawtarget(BackendType aType)
{
switch (aType) {
case BackendType::DIRECT2D:
case BackendType::DIRECT2D1_1:
case BackendType::RECORDING:
case BackendType::NONE:
case BackendType::BACKEND_LAST:
return false;
case BackendType::CAIRO:
case BackendType::SKIA:
return true;
}
return false;
}
uint32_t
Factory::GetMaxSurfaceSize(BackendType aType)
{
switch (aType) {
case BackendType::CAIRO:
return DrawTargetCairo::GetMaxSurfaceSize();
#ifdef USE_SKIA
case BackendType::SKIA:
return DrawTargetSkia::GetMaxSurfaceSize();
#endif
#ifdef WIN32
case BackendType::DIRECT2D1_1:
return DrawTargetD2D1::GetMaxSurfaceSize();
#endif
default:
return 0;
}
}
already_AddRefed<ScaledFont>
Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize)
{
switch (aNativeFont.mType) {
#ifdef WIN32
case NativeFontType::DWRITE_FONT_FACE:
{
return MakeAndAddRef<ScaledFontDWrite>(static_cast<IDWriteFontFace*>(aNativeFont.mFont), aSize);
}
#if defined(USE_CAIRO) || defined(USE_SKIA)
case NativeFontType::GDI_FONT_FACE:
{
return MakeAndAddRef<ScaledFontWin>(static_cast<LOGFONT*>(aNativeFont.mFont), aSize);
}
#endif
#endif
#ifdef XP_DARWIN
case NativeFontType::MAC_FONT_FACE:
{
return MakeAndAddRef<ScaledFontMac>(static_cast<CGFontRef>(aNativeFont.mFont), aSize);
}
#endif
#if defined(USE_CAIRO) || defined(USE_SKIA_FREETYPE)
case NativeFontType::CAIRO_FONT_FACE:
{
return MakeAndAddRef<ScaledFontCairo>(static_cast<cairo_scaled_font_t*>(aNativeFont.mFont), aSize);
}
#endif
default:
gfxWarning() << "Invalid native font type specified.";
return nullptr;
}
}
already_AddRefed<NativeFontResource>
Factory::CreateNativeFontResource(uint8_t *aData, uint32_t aSize,
FontType aType)
{
switch (aType) {
#ifdef WIN32
case FontType::DWRITE:
{
return NativeFontResourceDWrite::Create(aData, aSize,
/* aNeedsCairo = */ false);
}
#endif
case FontType::CAIRO:
#ifdef USE_SKIA
case FontType::SKIA:
#endif
{
#ifdef WIN32
if (GetDWriteFactory()) {
return NativeFontResourceDWrite::Create(aData, aSize,
/* aNeedsCairo = */ true);
} else {
return NativeFontResourceGDI::Create(aData, aSize,
/* aNeedsCairo = */ true);
}
#elif XP_DARWIN
return NativeFontResourceMac::Create(aData, aSize);
#else
gfxWarning() << "Unable to create cairo scaled font from truetype data";
return nullptr;
#endif
}
default:
gfxWarning() << "Unable to create requested font resource from truetype data";
return nullptr;
}
}
already_AddRefed<ScaledFont>
Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont, Float aSize, cairo_scaled_font_t* aScaledFont)
{
#ifdef USE_CAIRO
// In theory, we could pull the NativeFont out of the cairo_scaled_font_t*,
// but that would require a lot of code that would be otherwise repeated in
// various backends.
// Therefore, we just reuse CreateScaledFontForNativeFont's implementation.
RefPtr<ScaledFont> font = CreateScaledFontForNativeFont(aNativeFont, aSize);
static_cast<ScaledFontBase*>(font.get())->SetCairoScaledFont(aScaledFont);
return font.forget();
#else
return nullptr;
#endif
}
#ifdef MOZ_WIDGET_GTK
already_AddRefed<ScaledFont>
Factory::CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize)
{
return MakeAndAddRef<ScaledFontFontconfig>(aScaledFont, aPattern, aSize);
}
#endif
already_AddRefed<DrawTarget>
Factory::CreateDualDrawTarget(DrawTarget *targetA, DrawTarget *targetB)
{
MOZ_ASSERT(targetA && targetB);
RefPtr<DrawTarget> newTarget =
new DrawTargetDual(targetA, targetB);
RefPtr<DrawTarget> retVal = newTarget;
if (mRecorder) {
retVal = new DrawTargetRecording(mRecorder, retVal);
}
return retVal.forget();
}
#ifdef WIN32
already_AddRefed<DrawTarget>
Factory::CreateDrawTargetForD3D11Texture(ID3D11Texture2D *aTexture, SurfaceFormat aFormat)
{
MOZ_ASSERT(aTexture);
RefPtr<DrawTargetD2D1> newTarget;
newTarget = new DrawTargetD2D1();
if (newTarget->Init(aTexture, aFormat)) {
RefPtr<DrawTarget> retVal = newTarget;
if (mRecorder) {
retVal = new DrawTargetRecording(mRecorder, retVal, true);
}
return retVal.forget();
}
gfxWarning() << "Failed to create draw target for D3D11 texture.";
// Failed
return nullptr;
}
bool
Factory::SetDWriteFactory(IDWriteFactory *aFactory)
{
mDWriteFactory = aFactory;
return true;
}
bool
Factory::SetDirect3D11Device(ID3D11Device *aDevice)
{
mD3D11Device = aDevice;
if (mD2D1Device) {
mD2D1Device->Release();
mD2D1Device = nullptr;
}
if (!aDevice) {
return true;
}
RefPtr<ID2D1Factory1> factory = D2DFactory1();
RefPtr<IDXGIDevice> device;
aDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(device));
HRESULT hr = factory->CreateDevice(device, &mD2D1Device);
if (FAILED(hr)) {
gfxCriticalError() << "[D2D1] Failed to create gfx factory's D2D1 device, code: " << hexa(hr);
mD3D11Device = nullptr;
return false;
}
return true;
}
ID3D11Device*
Factory::GetDirect3D11Device()
{
return mD3D11Device;
}
ID2D1Device*
Factory::GetD2D1Device()
{
return mD2D1Device;
}
IDWriteFactory*
Factory::GetDWriteFactory()
{
return mDWriteFactory;
}
bool
Factory::SupportsD2D1()
{
return !!D2DFactory1();
}
already_AddRefed<GlyphRenderingOptions>
Factory::CreateDWriteGlyphRenderingOptions(IDWriteRenderingParams *aParams)
{
return MakeAndAddRef<GlyphRenderingOptionsDWrite>(aParams);
}
uint64_t
Factory::GetD2DVRAMUsageDrawTarget()
{
return DrawTargetD2D1::mVRAMUsageDT;
}
uint64_t
Factory::GetD2DVRAMUsageSourceSurface()
{
return DrawTargetD2D1::mVRAMUsageSS;
}
void
Factory::D2DCleanup()
{
if (mD2D1Device) {
mD2D1Device->Release();
mD2D1Device = nullptr;
}
DrawTargetD2D1::CleanupD2D();
}
already_AddRefed<ScaledFont>
Factory::CreateScaledFontForDWriteFont(IDWriteFontFace* aFontFace,
const gfxFontStyle* aStyle,
float aSize,
bool aUseEmbeddedBitmap,
bool aForceGDIMode)
{
return MakeAndAddRef<ScaledFontDWrite>(aFontFace, aSize,
aUseEmbeddedBitmap, aForceGDIMode,
aStyle);
}
#endif // XP_WIN
#ifdef USE_SKIA_GPU
already_AddRefed<DrawTarget>
Factory::CreateDrawTargetSkiaWithGrContext(GrContext* aGrContext,
const IntSize &aSize,
SurfaceFormat aFormat)
{
RefPtr<DrawTarget> newTarget = new DrawTargetSkia();
if (!newTarget->InitWithGrContext(aGrContext, aSize, aFormat)) {
return nullptr;
}
return newTarget.forget();
}
#endif // USE_SKIA_GPU
#ifdef USE_SKIA
already_AddRefed<DrawTarget>
Factory::CreateDrawTargetWithSkCanvas(SkCanvas* aCanvas)
{
RefPtr<DrawTargetSkia> newTarget = new DrawTargetSkia();
if (!newTarget->Init(aCanvas)) {
return nullptr;
}
return newTarget.forget();
}
#endif
void
Factory::PurgeAllCaches()
{
}
already_AddRefed<DrawTarget>
Factory::CreateDrawTargetForCairoSurface(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat)
{
if (!AllowedSurfaceSize(aSize)) {
gfxWarning() << "Allowing surface with invalid size (Cairo) " << aSize;
}
RefPtr<DrawTarget> retVal;
#ifdef USE_CAIRO
RefPtr<DrawTargetCairo> newTarget = new DrawTargetCairo();
if (newTarget->Init(aSurface, aSize, aFormat)) {
retVal = newTarget;
}
if (mRecorder && retVal) {
return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal, true);
}
#endif
return retVal.forget();
}
already_AddRefed<SourceSurface>
Factory::CreateSourceSurfaceForCairoSurface(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat aFormat)
{
if (aSize.width <= 0 || aSize.height <= 0) {
gfxWarning() << "Can't create a SourceSurface without a valid size";
return nullptr;
}
#ifdef USE_CAIRO
return MakeAndAddRef<SourceSurfaceCairo>(aSurface, aSize, aFormat);
#else
return nullptr;
#endif
}
already_AddRefed<DataSourceSurface>
Factory::CreateWrappingDataSourceSurface(uint8_t *aData,
int32_t aStride,
const IntSize &aSize,
SurfaceFormat aFormat,
SourceSurfaceDeallocator aDeallocator /* = nullptr */,
void* aClosure /* = nullptr */)
{
// Just check for negative/zero size instead of the full AllowedSurfaceSize() - since
// the data is already allocated we do not need to check for a possible overflow - it
// already worked.
if (aSize.width <= 0 || aSize.height <= 0) {
return nullptr;
}
if (!aDeallocator && aClosure) {
return nullptr;
}
MOZ_ASSERT(aData);
RefPtr<SourceSurfaceRawData> newSurf = new SourceSurfaceRawData();
newSurf->InitWrappingData(aData, aSize, aStride, aFormat, aDeallocator, aClosure);
return newSurf.forget();
}
#ifdef XP_DARWIN
already_AddRefed<GlyphRenderingOptions>
Factory::CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor)
{
return MakeAndAddRef<GlyphRenderingOptionsCG>(aFontSmoothingBackgroundColor);
}
#endif
already_AddRefed<DataSourceSurface>
Factory::CreateDataSourceSurface(const IntSize &aSize,
SurfaceFormat aFormat,
bool aZero)
{
if (!AllowedSurfaceSize(aSize)) {
gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to allocate a surface due to invalid size (DSS) " << aSize;
return nullptr;
}
// Skia doesn't support RGBX, so memset RGBX to 0xFF
bool clearSurface = aZero || aFormat == SurfaceFormat::B8G8R8X8;
uint8_t clearValue = aFormat == SurfaceFormat::B8G8R8X8 ? 0xFF : 0;
RefPtr<SourceSurfaceAlignedRawData> newSurf = new SourceSurfaceAlignedRawData();
if (newSurf->Init(aSize, aFormat, clearSurface, clearValue)) {
return newSurf.forget();
}
gfxWarning() << "CreateDataSourceSurface failed in init";
return nullptr;
}
already_AddRefed<DataSourceSurface>
Factory::CreateDataSourceSurfaceWithStride(const IntSize &aSize,
SurfaceFormat aFormat,
int32_t aStride,
bool aZero)
{
if (!AllowedSurfaceSize(aSize) ||
aStride < aSize.width * BytesPerPixel(aFormat)) {
gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "CreateDataSourceSurfaceWithStride failed with bad stride " << aStride << ", " << aSize << ", " << aFormat;
return nullptr;
}
// Skia doesn't support RGBX, so memset RGBX to 0xFF
bool clearSurface = aZero || aFormat == SurfaceFormat::B8G8R8X8;
uint8_t clearValue = aFormat == SurfaceFormat::B8G8R8X8 ? 0xFF : 0;
RefPtr<SourceSurfaceAlignedRawData> newSurf = new SourceSurfaceAlignedRawData();
if (newSurf->Init(aSize, aFormat, clearSurface, clearValue, aStride)) {
return newSurf.forget();
}
gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "CreateDataSourceSurfaceWithStride failed to initialize " << aSize << ", " << aFormat << ", " << aStride << ", " << aZero;
return nullptr;
}
static uint16_t
PackRGB565(uint8_t r, uint8_t g, uint8_t b)
{
uint16_t pixel = ((r << 11) & 0xf800) |
((g << 5) & 0x07e0) |
((b ) & 0x001f);
return pixel;
}
void
Factory::CopyDataSourceSurface(DataSourceSurface* aSource,
DataSourceSurface* aDest)
{
// Don't worry too much about speed.
MOZ_ASSERT(aSource->GetSize() == aDest->GetSize());
MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aSource->GetFormat() == SurfaceFormat::R8G8B8X8 ||
aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aDest->GetFormat() == SurfaceFormat::R8G8B8X8 ||
aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
aDest->GetFormat() == SurfaceFormat::B8G8R8X8 ||
aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16);
const bool isSrcBGR = aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
aSource->GetFormat() == SurfaceFormat::B8G8R8X8;
const bool isDestBGR = aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
aDest->GetFormat() == SurfaceFormat::B8G8R8X8;
const bool needsSwap02 = isSrcBGR != isDestBGR;
const bool srcHasAlpha = aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aSource->GetFormat() == SurfaceFormat::B8G8R8A8;
const bool destHasAlpha = aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aDest->GetFormat() == SurfaceFormat::B8G8R8A8;
const bool needsAlphaMask = !srcHasAlpha && destHasAlpha;
const bool needsConvertTo16Bits = aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16;
DataSourceSurface::MappedSurface srcMap;
DataSourceSurface::MappedSurface destMap;
if (!aSource->Map(DataSourceSurface::MapType::READ, &srcMap) ||
!aDest->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
MOZ_ASSERT(false, "CopyDataSourceSurface: Failed to map surface.");
return;
}
MOZ_ASSERT(srcMap.mStride >= 0);
MOZ_ASSERT(destMap.mStride >= 0);
const size_t srcBPP = BytesPerPixel(aSource->GetFormat());
const size_t srcRowBytes = aSource->GetSize().width * srcBPP;
const size_t srcRowHole = srcMap.mStride - srcRowBytes;
const size_t destBPP = BytesPerPixel(aDest->GetFormat());
const size_t destRowBytes = aDest->GetSize().width * destBPP;
const size_t destRowHole = destMap.mStride - destRowBytes;
uint8_t* srcRow = srcMap.mData;
uint8_t* destRow = destMap.mData;
const size_t rows = aSource->GetSize().height;
for (size_t i = 0; i < rows; i++) {
const uint8_t* srcRowEnd = srcRow + srcRowBytes;
while (srcRow != srcRowEnd) {
uint8_t d0 = needsSwap02 ? srcRow[2] : srcRow[0];
uint8_t d1 = srcRow[1];
uint8_t d2 = needsSwap02 ? srcRow[0] : srcRow[2];
uint8_t d3 = needsAlphaMask ? 0xff : srcRow[3];
if (needsConvertTo16Bits) {
*(uint16_t*)destRow = PackRGB565(d0, d1, d2);
} else {
destRow[0] = d0;
destRow[1] = d1;
destRow[2] = d2;
destRow[3] = d3;
}
srcRow += srcBPP;
destRow += destBPP;
}
srcRow += srcRowHole;
destRow += destRowHole;
}
aSource->Unmap();
aDest->Unmap();
}
already_AddRefed<DrawEventRecorder>
Factory::CreateEventRecorderForFile(const char *aFilename)
{
return MakeAndAddRef<DrawEventRecorderFile>(aFilename);
}
void
Factory::SetGlobalEventRecorder(DrawEventRecorder *aRecorder)
{
mRecorder = aRecorder;
}
// static
void
CriticalLogger::OutputMessage(const std::string &aString,
int aLevel, bool aNoNewline)
{
if (Factory::GetLogForwarder()) {
Factory::GetLogForwarder()->Log(aString);
}
BasicLogger::OutputMessage(aString, aLevel, aNoNewline);
}
void
CriticalLogger::CrashAction(LogReason aReason)
{
if (Factory::GetLogForwarder()) {
Factory::GetLogForwarder()->CrashAction(aReason);
}
}
} // namespace gfx
} // namespace mozilla

1102
gfx/2d/FilterNodeD2D1.cpp Normal file

File diff suppressed because it is too large Load diff

133
gfx/2d/FilterNodeD2D1.h Normal file
View file

@ -0,0 +1,133 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_FILTERNODED2D1_H_
#define MOZILLA_GFX_FILTERNODED2D1_H_
#include "2D.h"
#include "Filters.h"
#include <vector>
#include <d2d1_1.h>
#include <cguid.h>
namespace mozilla {
namespace gfx {
class FilterNodeD2D1 : public FilterNode
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeD2D1)
static already_AddRefed<FilterNode> Create(ID2D1DeviceContext *aDC, FilterType aType);
FilterNodeD2D1(ID2D1Effect *aEffect, FilterType aType)
: mEffect(aEffect)
, mType(aType)
{
InitUnmappedProperties();
}
virtual FilterBackend GetBackendType() { return FILTER_BACKEND_DIRECT2D1_1; }
virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface);
virtual void SetInput(uint32_t aIndex, FilterNode *aFilter);
virtual void SetAttribute(uint32_t aIndex, uint32_t aValue);
virtual void SetAttribute(uint32_t aIndex, Float aValue);
virtual void SetAttribute(uint32_t aIndex, const Point &aValue);
virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &aValue);
virtual void SetAttribute(uint32_t aIndex, const Point3D &aValue);
virtual void SetAttribute(uint32_t aIndex, const Size &aValue);
virtual void SetAttribute(uint32_t aIndex, const IntSize &aValue);
virtual void SetAttribute(uint32_t aIndex, const Color &aValue);
virtual void SetAttribute(uint32_t aIndex, const Rect &aValue);
virtual void SetAttribute(uint32_t aIndex, const IntRect &aValue);
virtual void SetAttribute(uint32_t aIndex, bool aValue);
virtual void SetAttribute(uint32_t aIndex, const Float *aValues, uint32_t aSize);
virtual void SetAttribute(uint32_t aIndex, const IntPoint &aValue);
virtual void SetAttribute(uint32_t aIndex, const Matrix &aValue);
// Called by DrawTarget before it draws our OutputEffect, and recursively
// by the filter nodes that have this filter as one of their inputs. This
// gives us a chance to convert any input surfaces to the target format for
// the DrawTarget that we will draw to.
virtual void WillDraw(DrawTarget *aDT);
virtual ID2D1Effect* MainEffect() { return mEffect.get(); }
virtual ID2D1Effect* InputEffect() { return mEffect.get(); }
virtual ID2D1Effect* OutputEffect() { return mEffect.get(); }
protected:
friend class DrawTargetD2D1;
friend class DrawTargetD2D;
friend class FilterNodeConvolveD2D1;
void InitUnmappedProperties();
RefPtr<ID2D1Effect> mEffect;
std::vector<RefPtr<FilterNodeD2D1>> mInputFilters;
std::vector<RefPtr<SourceSurface>> mInputSurfaces;
FilterType mType;
};
class FilterNodeConvolveD2D1 : public FilterNodeD2D1
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveD2D1, override)
FilterNodeConvolveD2D1(ID2D1DeviceContext *aDC);
virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
virtual void SetAttribute(uint32_t aIndex, const IntSize &aValue) override;
virtual void SetAttribute(uint32_t aIndex, const IntPoint &aValue) override;
virtual void SetAttribute(uint32_t aIndex, const IntRect &aValue) override;
virtual ID2D1Effect* InputEffect() override;
private:
void UpdateChain();
void UpdateOffset();
void UpdateSourceRect();
RefPtr<ID2D1Effect> mExtendInputEffect;
RefPtr<ID2D1Effect> mBorderEffect;
ConvolveMatrixEdgeMode mEdgeMode;
IntPoint mTarget;
IntSize mKernelSize;
IntRect mSourceRect;
};
class FilterNodeExtendInputAdapterD2D1 : public FilterNodeD2D1
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeExtendInputAdapterD2D1, override)
FilterNodeExtendInputAdapterD2D1(ID2D1DeviceContext *aDC, FilterNodeD2D1 *aFilterNode, FilterType aType);
virtual ID2D1Effect* InputEffect() override { return mExtendInputEffect.get(); }
virtual ID2D1Effect* OutputEffect() override { return mWrappedFilterNode->OutputEffect(); }
private:
RefPtr<FilterNodeD2D1> mWrappedFilterNode;
RefPtr<ID2D1Effect> mExtendInputEffect;
};
class FilterNodePremultiplyAdapterD2D1 : public FilterNodeD2D1
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplyAdapterD2D1, override)
FilterNodePremultiplyAdapterD2D1(ID2D1DeviceContext *aDC, FilterNodeD2D1 *aFilterNode, FilterType aType);
virtual ID2D1Effect* InputEffect() override { return mPrePremultiplyEffect.get(); }
virtual ID2D1Effect* OutputEffect() override { return mPostUnpremultiplyEffect.get(); }
private:
RefPtr<ID2D1Effect> mPrePremultiplyEffect;
RefPtr<ID2D1Effect> mPostUnpremultiplyEffect;
};
}
}
#endif

File diff suppressed because it is too large Load diff

724
gfx/2d/FilterNodeSoftware.h Normal file
View file

@ -0,0 +1,724 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_FILTERNODESOFTWARE_H_
#define _MOZILLA_GFX_FILTERNODESOFTWARE_H_
#include "Filters.h"
#include <vector>
namespace mozilla {
namespace gfx {
class DataSourceSurface;
class DrawTarget;
struct DrawOptions;
class FilterNodeSoftware;
/**
* Can be attached to FilterNodeSoftware instances using
* AddInvalidationListener. FilterInvalidated is called whenever the output of
* the observed filter may have changed; that is, whenever cached GetOutput()
* results (and results derived from them) need to discarded.
*/
class FilterInvalidationListener
{
public:
virtual void FilterInvalidated(FilterNodeSoftware* aFilter) = 0;
};
/**
* This is the base class for the software (i.e. pure CPU, non-accelerated)
* FilterNode implementation. The software implementation is backend-agnostic,
* so it can be used as a fallback for all DrawTarget implementations.
*/
class FilterNodeSoftware : public FilterNode,
public FilterInvalidationListener
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeSoftware, override)
virtual ~FilterNodeSoftware();
// Factory method, intended to be called from DrawTarget*::CreateFilter.
static already_AddRefed<FilterNode> Create(FilterType aType);
// Draw the filter, intended to be called by DrawTarget*::DrawFilter.
void Draw(DrawTarget* aDrawTarget, const Rect &aSourceRect,
const Point &aDestPoint, const DrawOptions &aOptions);
virtual FilterBackend GetBackendType() override { return FILTER_BACKEND_SOFTWARE; }
virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) override;
virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override;
virtual const char* GetName() { return "Unknown"; }
virtual void AddInvalidationListener(FilterInvalidationListener* aListener);
virtual void RemoveInvalidationListener(FilterInvalidationListener* aListener);
// FilterInvalidationListener implementation
virtual void FilterInvalidated(FilterNodeSoftware* aFilter) override;
protected:
// The following methods are intended to be overriden by subclasses.
/**
* Translates a *FilterInputs enum value into an index for the
* mInputFilters / mInputSurfaces arrays. Returns -1 for invalid inputs.
* If somebody calls SetInput(enumValue, input) with an enumValue for which
* InputIndex(enumValue) is -1, we abort.
*/
virtual int32_t InputIndex(uint32_t aInputEnumIndex) { return -1; }
/**
* Every filter node has an output rect, which can also be infinite. The
* output rect can depend on the values of any set attributes and on the
* output rects of any input filters or surfaces.
* This method returns the intersection of the filter's output rect with
* aInRect. Filters with unconstrained output always return aInRect.
*/
virtual IntRect GetOutputRectInRect(const IntRect& aInRect) = 0;
/**
* Return a surface with the rendered output which is of size aRect.Size().
* aRect is required to be a subrect of this filter's output rect; in other
* words, aRect == GetOutputRectInRect(aRect) must always be true.
* May return nullptr in error conditions or for an empty aRect.
* Implementations are not required to allocate a new surface and may even
* pass through input surfaces unchanged.
* Callers need to treat the returned surface as immutable.
*/
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) = 0;
/**
* Call RequestRect (see below) on any input filters with the desired input
* rect, so that the input filter knows what to cache the next time it
* renders.
*/
virtual void RequestFromInputsForRect(const IntRect &aRect) {}
/**
* This method provides a caching default implementation but can be overriden
* by subclasses that don't want to cache their output. Those classes should
* call Render(aRect) directly from here.
*/
virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect &aRect);
// The following methods are non-virtual helper methods.
/**
* Format hints for GetInputDataSourceSurface. Some callers of
* GetInputDataSourceSurface can handle both B8G8R8A8 and A8 surfaces, these
* should pass CAN_HANDLE_A8 in order to avoid unnecessary conversions.
* Callers that can only handle B8G8R8A8 surfaces pass NEED_COLOR_CHANNELS.
*/
enum FormatHint {
CAN_HANDLE_A8,
NEED_COLOR_CHANNELS
};
/**
* Returns SurfaceFormat::B8G8R8A8 or SurfaceFormat::A8, depending on the current surface
* format and the format hint.
*/
SurfaceFormat DesiredFormat(SurfaceFormat aCurrentFormat,
FormatHint aFormatHint);
/**
* Intended to be called by FilterNodeSoftware::Render implementations.
* Returns a surface of size aRect.Size() or nullptr in error conditions. The
* returned surface contains the output of the specified input filter or
* input surface in aRect. If aRect extends beyond the input filter's output
* rect (or the input surface's dimensions), the remaining area is filled
* according to aEdgeMode: The default, EDGE_MODE_NONE, simply pads with
* transparent black.
* If non-null, the returned surface is guaranteed to be of SurfaceFormat::A8 or
* SurfaceFormat::B8G8R8A8. If aFormatHint is NEED_COLOR_CHANNELS, the returned
* surface is guaranteed to be of SurfaceFormat::B8G8R8A8 always.
* Each pixel row of the returned surface is guaranteed to be 16-byte aligned.
*/
already_AddRefed<DataSourceSurface>
GetInputDataSourceSurface(uint32_t aInputEnumIndex, const IntRect& aRect,
FormatHint aFormatHint = CAN_HANDLE_A8,
ConvolveMatrixEdgeMode aEdgeMode = EDGE_MODE_NONE,
const IntRect *aTransparencyPaddedSourceRect = nullptr);
/**
* Returns the intersection of the input filter's or surface's output rect
* with aInRect.
*/
IntRect GetInputRectInRect(uint32_t aInputEnumIndex, const IntRect& aInRect);
/**
* Calls RequestRect on the specified input, if it's a filter.
*/
void RequestInputRect(uint32_t aInputEnumIndex, const IntRect& aRect);
/**
* Returns the number of set input filters or surfaces. Needed for filters
* which can have an arbitrary number of inputs.
*/
size_t NumberOfSetInputs();
/**
* Discard the cached surface that was stored in the GetOutput default
* implementation. Needs to be called whenever attributes or inputs are set
* that might change the result of a Render() call.
*/
void Invalidate();
/**
* Called in order to let this filter know what to cache during the next
* GetOutput call. Expected to call RequestRect on this filter's input
* filters.
*/
void RequestRect(const IntRect &aRect);
/**
* Set input filter and clear input surface for this input index, or set
* input surface and clear input filter. One of aSurface and aFilter should
* be null.
*/
void SetInput(uint32_t aIndex, SourceSurface *aSurface,
FilterNodeSoftware *aFilter);
protected:
/**
* mInputSurfaces / mInputFilters: For each input index, either a surface or
* a filter is set, and the other is null.
*/
std::vector<RefPtr<SourceSurface> > mInputSurfaces;
std::vector<RefPtr<FilterNodeSoftware> > mInputFilters;
/**
* Weak pointers to our invalidation listeners, i.e. to those filters who
* have this filter as an input. Invalidation listeners are required to
* unsubscribe themselves from us when they let go of their reference to us.
* This ensures that the pointers in this array are never stale.
*/
std::vector<FilterInvalidationListener*> mInvalidationListeners;
/**
* Stores the rect which we want to render and cache on the next call to
* GetOutput.
*/
IntRect mRequestedRect;
/**
* Stores our cached output.
*/
IntRect mCachedRect;
RefPtr<DataSourceSurface> mCachedOutput;
};
// Subclasses for specific filters.
class FilterNodeTransformSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTransformSoftware, override)
FilterNodeTransformSoftware();
virtual const char* GetName() override { return "Transform"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, uint32_t aGraphicsFilter) override;
virtual void SetAttribute(uint32_t aIndex, const Matrix &aMatrix) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
IntRect SourceRectForOutputRect(const IntRect &aRect);
private:
Matrix mMatrix;
SamplingFilter mSamplingFilter;
};
class FilterNodeBlendSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlendSoftware, override)
FilterNodeBlendSoftware();
virtual const char* GetName() override { return "Blend"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, uint32_t aBlendMode) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
BlendMode mBlendMode;
};
class FilterNodeMorphologySoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeMorphologySoftware, override)
FilterNodeMorphologySoftware();
virtual const char* GetName() override { return "Morphology"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const IntSize &aRadii) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
IntSize mRadii;
MorphologyOperator mOperator;
};
class FilterNodeColorMatrixSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeColorMatrixSoftware, override)
virtual const char* GetName() override { return "ColorMatrix"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &aMatrix) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
Matrix5x4 mMatrix;
AlphaMode mAlphaMode;
};
class FilterNodeFloodSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeFloodSoftware, override)
virtual const char* GetName() override { return "Flood"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Color &aColor) override;
protected:
virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect &aRect) override;
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
private:
Color mColor;
};
class FilterNodeTileSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTileSoftware, override)
virtual const char* GetName() override { return "Tile"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
IntRect mSourceRect;
};
/**
* Baseclass for the four different component transfer filters.
*/
class FilterNodeComponentTransferSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeComponentTransferSoftware, override)
FilterNodeComponentTransferSoftware();
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, bool aDisable) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
virtual void GenerateLookupTable(ptrdiff_t aComponent, uint8_t aTables[4][256],
bool aDisabled);
virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0;
bool mDisableR;
bool mDisableG;
bool mDisableB;
bool mDisableA;
};
class FilterNodeTableTransferSoftware : public FilterNodeComponentTransferSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTableTransferSoftware, override)
virtual const char* GetName() override { return "TableTransfer"; }
using FilterNodeComponentTransferSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override;
protected:
virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(std::vector<Float>& aTableValues, uint8_t aTable[256]);
std::vector<Float> mTableR;
std::vector<Float> mTableG;
std::vector<Float> mTableB;
std::vector<Float> mTableA;
};
class FilterNodeDiscreteTransferSoftware : public FilterNodeComponentTransferSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDiscreteTransferSoftware, override)
virtual const char* GetName() override { return "DiscreteTransfer"; }
using FilterNodeComponentTransferSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override;
protected:
virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(std::vector<Float>& aTableValues, uint8_t aTable[256]);
std::vector<Float> mTableR;
std::vector<Float> mTableG;
std::vector<Float> mTableB;
std::vector<Float> mTableA;
};
class FilterNodeLinearTransferSoftware : public FilterNodeComponentTransferSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeLinearTransformSoftware, override)
FilterNodeLinearTransferSoftware();
virtual const char* GetName() override { return "LinearTransfer"; }
using FilterNodeComponentTransferSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, Float aValue) override;
protected:
virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(Float aSlope, Float aIntercept, uint8_t aTable[256]);
Float mSlopeR;
Float mSlopeG;
Float mSlopeB;
Float mSlopeA;
Float mInterceptR;
Float mInterceptG;
Float mInterceptB;
Float mInterceptA;
};
class FilterNodeGammaTransferSoftware : public FilterNodeComponentTransferSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGammaTransferSoftware, override)
FilterNodeGammaTransferSoftware();
virtual const char* GetName() override { return "GammaTransfer"; }
using FilterNodeComponentTransferSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, Float aValue) override;
protected:
virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(Float aAmplitude, Float aExponent, Float aOffset, uint8_t aTable[256]);
Float mAmplitudeR;
Float mAmplitudeG;
Float mAmplitudeB;
Float mAmplitudeA;
Float mExponentR;
Float mExponentG;
Float mExponentB;
Float mExponentA;
Float mOffsetR;
Float mOffsetG;
Float mOffsetB;
Float mOffsetA;
};
class FilterNodeConvolveMatrixSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveMatrixSoftware, override)
FilterNodeConvolveMatrixSoftware();
virtual const char* GetName() override { return "ConvolveMatrix"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const IntSize &aKernelSize) override;
virtual void SetAttribute(uint32_t aIndex, const Float* aMatrix, uint32_t aSize) override;
virtual void SetAttribute(uint32_t aIndex, Float aValue) override;
virtual void SetAttribute(uint32_t aIndex, const Size &aKernelUnitLength) override;
virtual void SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) override;
virtual void SetAttribute(uint32_t aIndex, const IntPoint &aTarget) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) override;
virtual void SetAttribute(uint32_t aIndex, bool aPreserveAlpha) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
template<typename CoordType>
already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect,
CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY);
IntRect InflatedSourceRect(const IntRect &aDestRect);
IntRect InflatedDestRect(const IntRect &aSourceRect);
IntSize mKernelSize;
std::vector<Float> mKernelMatrix;
Float mDivisor;
Float mBias;
IntPoint mTarget;
IntRect mSourceRect;
ConvolveMatrixEdgeMode mEdgeMode;
Size mKernelUnitLength;
bool mPreserveAlpha;
};
class FilterNodeDisplacementMapSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDisplacementMapSoftware, override)
FilterNodeDisplacementMapSoftware();
virtual const char* GetName() override { return "DisplacementMap"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, Float aScale) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
IntRect InflatedSourceOrDestRect(const IntRect &aDestOrSourceRect);
Float mScale;
ColorChannel mChannelX;
ColorChannel mChannelY;
};
class FilterNodeTurbulenceSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTurbulenceSoftware, override)
FilterNodeTurbulenceSoftware();
virtual const char* GetName() override { return "Turbulence"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Size &aSize) override;
virtual void SetAttribute(uint32_t aIndex, const IntRect &aRenderRect) override;
virtual void SetAttribute(uint32_t aIndex, bool aStitchable) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
private:
IntRect mRenderRect;
Size mBaseFrequency;
uint32_t mNumOctaves;
uint32_t mSeed;
bool mStitchable;
TurbulenceType mType;
};
class FilterNodeArithmeticCombineSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeArithmeticCombineSoftware, override)
FilterNodeArithmeticCombineSoftware();
virtual const char* GetName() override { return "ArithmeticCombine"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
Float mK1;
Float mK2;
Float mK3;
Float mK4;
};
class FilterNodeCompositeSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCompositeSoftware, override)
FilterNodeCompositeSoftware();
virtual const char* GetName() override { return "Composite"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
CompositeOperator mOperator;
};
// Base class for FilterNodeGaussianBlurSoftware and
// FilterNodeDirectionalBlurSoftware.
class FilterNodeBlurXYSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlurXYSoftware, override)
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
IntRect InflatedSourceOrDestRect(const IntRect &aDestRect);
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
// Implemented by subclasses.
virtual Size StdDeviationXY() = 0;
};
class FilterNodeGaussianBlurSoftware : public FilterNodeBlurXYSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGaussianBlurSoftware, override)
FilterNodeGaussianBlurSoftware();
virtual const char* GetName() override { return "GaussianBlur"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, Float aStdDeviation) override;
protected:
virtual Size StdDeviationXY() override;
private:
Float mStdDeviation;
};
class FilterNodeDirectionalBlurSoftware : public FilterNodeBlurXYSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDirectionalBlurSoftware, override)
FilterNodeDirectionalBlurSoftware();
virtual const char* GetName() override { return "DirectionalBlur"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, Float aStdDeviation) override;
virtual void SetAttribute(uint32_t aIndex, uint32_t aBlurDirection) override;
protected:
virtual Size StdDeviationXY() override;
private:
Float mStdDeviation;
BlurDirection mBlurDirection;
};
class FilterNodeCropSoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCropSoftware, override)
virtual const char* GetName() override { return "Crop"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, const Rect &aSourceRect) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
IntRect mCropRect;
};
class FilterNodePremultiplySoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplySoftware, override)
virtual const char* GetName() override { return "Premultiply"; }
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
};
class FilterNodeUnpremultiplySoftware : public FilterNodeSoftware
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeUnpremultiplySoftware, override)
virtual const char* GetName() override { return "Unpremultiply"; }
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
};
template<typename LightType, typename LightingType>
class FilterNodeLightingSoftware : public FilterNodeSoftware
{
public:
#if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING))
// Helpers for refcounted
virtual const char* typeName() const override { return mTypeName; }
virtual size_t typeSize() const override { return sizeof(*this); }
#endif
explicit FilterNodeLightingSoftware(const char* aTypeName);
virtual const char* GetName() override { return "Lighting"; }
using FilterNodeSoftware::SetAttribute;
virtual void SetAttribute(uint32_t aIndex, Float) override;
virtual void SetAttribute(uint32_t aIndex, const Size &) override;
virtual void SetAttribute(uint32_t aIndex, const Point3D &) override;
virtual void SetAttribute(uint32_t aIndex, const Color &) override;
protected:
virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
virtual void RequestFromInputsForRect(const IntRect &aRect) override;
private:
template<typename CoordType>
already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect,
CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY);
LightType mLight;
LightingType mLighting;
Float mSurfaceScale;
Size mKernelUnitLength;
Color mColor;
#if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING))
const char* mTypeName;
#endif
};
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_FILTERNODESOFTWARE_H_

262
gfx/2d/FilterProcessing.cpp Normal file
View file

@ -0,0 +1,262 @@
/* -*- 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 "FilterProcessing.h"
#include "Logging.h"
namespace mozilla {
namespace gfx {
already_AddRefed<DataSourceSurface>
FilterProcessing::ExtractAlpha(DataSourceSurface* aSource)
{
IntSize size = aSource->GetSize();
RefPtr<DataSourceSurface> alpha = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8);
if (MOZ2D_WARN_IF(!alpha)) {
return nullptr;
}
DataSourceSurface::ScopedMap sourceMap(aSource, DataSourceSurface::READ);
DataSourceSurface::ScopedMap alphaMap(alpha, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !alphaMap.IsMapped())) {
return nullptr;
}
uint8_t* sourceData = sourceMap.GetData();
int32_t sourceStride = sourceMap.GetStride();
uint8_t* alphaData = alphaMap.GetData();
int32_t alphaStride = alphaMap.GetStride();
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
ExtractAlpha_SSE2(size, sourceData, sourceStride, alphaData, alphaStride);
#endif
} else {
ExtractAlpha_Scalar(size, sourceData, sourceStride, alphaData, alphaStride);
}
return alpha.forget();
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ConvertToB8G8R8A8(SourceSurface* aSurface)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
return ConvertToB8G8R8A8_SSE2(aSurface);
#endif
}
return ConvertToB8G8R8A8_Scalar(aSurface);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyBlending(DataSourceSurface* aInput1, DataSourceSurface* aInput2,
BlendMode aBlendMode)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
return ApplyBlending_SSE2(aInput1, aInput2, aBlendMode);
#endif
}
return nullptr;
}
void
FilterProcessing::ApplyMorphologyHorizontal(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOp)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
ApplyMorphologyHorizontal_SSE2(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp);
#endif
} else {
ApplyMorphologyHorizontal_Scalar(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp);
}
}
void
FilterProcessing::ApplyMorphologyVertical(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOp)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
ApplyMorphologyVertical_SSE2(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp);
#endif
} else {
ApplyMorphologyVertical_Scalar(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp);
}
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyColorMatrix(DataSourceSurface* aInput, const Matrix5x4 &aMatrix)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
return ApplyColorMatrix_SSE2(aInput, aMatrix);
#endif
}
return ApplyColorMatrix_Scalar(aInput, aMatrix);
}
void
FilterProcessing::ApplyComposition(DataSourceSurface* aSource, DataSourceSurface* aDest,
CompositeOperator aOperator)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
ApplyComposition_SSE2(aSource, aDest, aOperator);
#endif
} else {
ApplyComposition_Scalar(aSource, aDest, aOperator);
}
}
void
FilterProcessing::SeparateColorChannels(DataSourceSurface* aSource,
RefPtr<DataSourceSurface>& aChannel0,
RefPtr<DataSourceSurface>& aChannel1,
RefPtr<DataSourceSurface>& aChannel2,
RefPtr<DataSourceSurface>& aChannel3)
{
IntSize size = aSource->GetSize();
aChannel0 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8);
aChannel1 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8);
aChannel2 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8);
aChannel3 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8);
if (MOZ2D_WARN_IF(!(aChannel0 && aChannel1 && aChannel2 && aChannel3))) {
return;
}
DataSourceSurface::ScopedMap sourceMap(aSource, DataSourceSurface::READ);
DataSourceSurface::ScopedMap channel0Map(aChannel0, DataSourceSurface::WRITE);
DataSourceSurface::ScopedMap channel1Map(aChannel1, DataSourceSurface::WRITE);
DataSourceSurface::ScopedMap channel2Map(aChannel2, DataSourceSurface::WRITE);
DataSourceSurface::ScopedMap channel3Map(aChannel3, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!(sourceMap.IsMapped() &&
channel0Map.IsMapped() && channel1Map.IsMapped() &&
channel2Map.IsMapped() && channel3Map.IsMapped()))) {
return;
}
uint8_t* sourceData = sourceMap.GetData();
int32_t sourceStride = sourceMap.GetStride();
uint8_t* channel0Data = channel0Map.GetData();
uint8_t* channel1Data = channel1Map.GetData();
uint8_t* channel2Data = channel2Map.GetData();
uint8_t* channel3Data = channel3Map.GetData();
int32_t channelStride = channel0Map.GetStride();
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
SeparateColorChannels_SSE2(size, sourceData, sourceStride, channel0Data, channel1Data, channel2Data, channel3Data, channelStride);
#endif
} else {
SeparateColorChannels_Scalar(size, sourceData, sourceStride, channel0Data, channel1Data, channel2Data, channel3Data, channelStride);
}
}
already_AddRefed<DataSourceSurface>
FilterProcessing::CombineColorChannels(DataSourceSurface* aChannel0, DataSourceSurface* aChannel1,
DataSourceSurface* aChannel2, DataSourceSurface* aChannel3)
{
IntSize size = aChannel0->GetSize();
RefPtr<DataSourceSurface> result =
Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!result)) {
return nullptr;
}
DataSourceSurface::ScopedMap resultMap(result, DataSourceSurface::WRITE);
DataSourceSurface::ScopedMap channel0Map(aChannel0, DataSourceSurface::READ);
DataSourceSurface::ScopedMap channel1Map(aChannel1, DataSourceSurface::READ);
DataSourceSurface::ScopedMap channel2Map(aChannel2, DataSourceSurface::READ);
DataSourceSurface::ScopedMap channel3Map(aChannel3, DataSourceSurface::READ);
if (MOZ2D_WARN_IF(!(resultMap.IsMapped() &&
channel0Map.IsMapped() && channel1Map.IsMapped() &&
channel2Map.IsMapped() && channel3Map.IsMapped()))) {
return nullptr;
}
int32_t resultStride = resultMap.GetStride();
uint8_t* resultData = resultMap.GetData();
int32_t channelStride = channel0Map.GetStride();
uint8_t* channel0Data = channel0Map.GetData();
uint8_t* channel1Data = channel1Map.GetData();
uint8_t* channel2Data = channel2Map.GetData();
uint8_t* channel3Data = channel3Map.GetData();
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
CombineColorChannels_SSE2(size, resultStride, resultData, channelStride, channel0Data, channel1Data, channel2Data, channel3Data);
#endif
} else {
CombineColorChannels_Scalar(size, resultStride, resultData, channelStride, channel0Data, channel1Data, channel2Data, channel3Data);
}
return result.forget();
}
void
FilterProcessing::DoPremultiplicationCalculation(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
DoPremultiplicationCalculation_SSE2(
aSize, aTargetData, aTargetStride, aSourceData, aSourceStride);
#endif
} else {
DoPremultiplicationCalculation_Scalar(
aSize, aTargetData, aTargetStride, aSourceData, aSourceStride);
}
}
void
FilterProcessing::DoUnpremultiplicationCalculation(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
DoUnpremultiplicationCalculation_SSE2(
aSize, aTargetData, aTargetStride, aSourceData, aSourceStride);
#endif
} else {
DoUnpremultiplicationCalculation_Scalar(
aSize, aTargetData, aTargetStride, aSourceData, aSourceStride);
}
}
already_AddRefed<DataSourceSurface>
FilterProcessing::RenderTurbulence(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency,
int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
return RenderTurbulence_SSE2(aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect);
#endif
}
return RenderTurbulence_Scalar(aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyArithmeticCombine(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4)
{
if (Factory::HasSSE2()) {
#ifdef USE_SSE2
return ApplyArithmeticCombine_SSE2(aInput1, aInput2, aK1, aK2, aK3, aK4);
#endif
}
return ApplyArithmeticCombine_Scalar(aInput1, aInput2, aK1, aK2, aK3, aK4);
}
} // namespace gfx
} // namespace mozilla

141
gfx/2d/FilterProcessing.h Normal file
View file

@ -0,0 +1,141 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_FILTERPROCESSING_H_
#define _MOZILLA_GFX_FILTERPROCESSING_H_
#include "2D.h"
#include "Filters.h"
namespace mozilla {
namespace gfx {
const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_B = 0;
const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_G = 1;
const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_R = 2;
const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_A = 3;
class FilterProcessing
{
public:
// Fast approximate division by 255. It has the property that
// for all 0 <= v <= 255*255, FastDivideBy255(v) == v/255.
// But it only uses two adds and two shifts instead of an
// integer division (which is expensive on many processors).
template<class B, class A>
static B FastDivideBy255(A v)
{
return ((v << 8) + v + 255) >> 16;
}
static already_AddRefed<DataSourceSurface> ExtractAlpha(DataSourceSurface* aSource);
static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8(SourceSurface* aSurface);
static already_AddRefed<DataSourceSurface> ApplyBlending(DataSourceSurface* aInput1, DataSourceSurface* aInput2, BlendMode aBlendMode);
static void ApplyMorphologyHorizontal(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOperator);
static void ApplyMorphologyVertical(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOperator);
static already_AddRefed<DataSourceSurface> ApplyColorMatrix(DataSourceSurface* aInput, const Matrix5x4 &aMatrix);
static void ApplyComposition(DataSourceSurface* aSource, DataSourceSurface* aDest, CompositeOperator aOperator);
static void SeparateColorChannels(DataSourceSurface* aSource,
RefPtr<DataSourceSurface>& aChannel0,
RefPtr<DataSourceSurface>& aChannel1,
RefPtr<DataSourceSurface>& aChannel2,
RefPtr<DataSourceSurface>& aChannel3);
static already_AddRefed<DataSourceSurface>
CombineColorChannels(DataSourceSurface* aChannel0, DataSourceSurface* aChannel1,
DataSourceSurface* aChannel2, DataSourceSurface* aChannel3);
static void DoPremultiplicationCalculation(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride);
static void DoUnpremultiplicationCalculation(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride);
static already_AddRefed<DataSourceSurface>
RenderTurbulence(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency,
int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect);
static already_AddRefed<DataSourceSurface>
ApplyArithmeticCombine(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4);
protected:
static void ExtractAlpha_Scalar(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride);
static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_Scalar(SourceSurface* aSurface);
static void ApplyMorphologyHorizontal_Scalar(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOperator);
static void ApplyMorphologyVertical_Scalar(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOperator);
static already_AddRefed<DataSourceSurface> ApplyColorMatrix_Scalar(DataSourceSurface* aInput, const Matrix5x4 &aMatrix);
static void ApplyComposition_Scalar(DataSourceSurface* aSource, DataSourceSurface* aDest, CompositeOperator aOperator);
static void SeparateColorChannels_Scalar(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride);
static void CombineColorChannels_Scalar(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data);
static void DoPremultiplicationCalculation_Scalar(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride);
static void DoUnpremultiplicationCalculation_Scalar(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride);
static already_AddRefed<DataSourceSurface>
RenderTurbulence_Scalar(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency,
int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect);
static already_AddRefed<DataSourceSurface>
ApplyArithmeticCombine_Scalar(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4);
#ifdef USE_SSE2
static void ExtractAlpha_SSE2(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride);
static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_SSE2(SourceSurface* aSurface);
static already_AddRefed<DataSourceSurface> ApplyBlending_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, BlendMode aBlendMode);
static void ApplyMorphologyHorizontal_SSE2(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOperator);
static void ApplyMorphologyVertical_SSE2(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOperator);
static already_AddRefed<DataSourceSurface> ApplyColorMatrix_SSE2(DataSourceSurface* aInput, const Matrix5x4 &aMatrix);
static void ApplyComposition_SSE2(DataSourceSurface* aSource, DataSourceSurface* aDest, CompositeOperator aOperator);
static void SeparateColorChannels_SSE2(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride);
static void CombineColorChannels_SSE2(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data);
static void DoPremultiplicationCalculation_SSE2(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride);
static void DoUnpremultiplicationCalculation_SSE2(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride);
static already_AddRefed<DataSourceSurface>
RenderTurbulence_SSE2(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency,
int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect);
static already_AddRefed<DataSourceSurface>
ApplyArithmeticCombine_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4);
#endif
};
// Constant-time max and min functions for unsigned arguments
static inline unsigned
umax(unsigned a, unsigned b)
{
return a - ((a - b) & -(a < b));
}
static inline unsigned
umin(unsigned a, unsigned b)
{
return a - ((a - b) & -(a > b));
}
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_FILTERPROCESSING_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,112 @@
/* -*- 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/. */
#define SIMD_COMPILE_SSE2
#include "FilterProcessingSIMD-inl.h"
#ifndef USE_SSE2
static_assert(false, "If this file is built, FilterProcessing.h should know about it!");
#endif
namespace mozilla {
namespace gfx {
void
FilterProcessing::ExtractAlpha_SSE2(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride)
{
ExtractAlpha_SIMD<__m128i>(size, sourceData, sourceStride, alphaData, alphaStride);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ConvertToB8G8R8A8_SSE2(SourceSurface* aSurface)
{
return ConvertToB8G8R8A8_SIMD<__m128i>(aSurface);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyBlending_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2,
BlendMode aBlendMode)
{
return ApplyBlending_SIMD<__m128i,__m128i,__m128i>(aInput1, aInput2, aBlendMode);
}
void
FilterProcessing::ApplyMorphologyHorizontal_SSE2(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOp)
{
ApplyMorphologyHorizontal_SIMD<__m128i,__m128i>(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp);
}
void
FilterProcessing::ApplyMorphologyVertical_SSE2(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOp)
{
ApplyMorphologyVertical_SIMD<__m128i,__m128i>(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyColorMatrix_SSE2(DataSourceSurface* aInput, const Matrix5x4 &aMatrix)
{
return ApplyColorMatrix_SIMD<__m128i,__m128i,__m128i>(aInput, aMatrix);
}
void
FilterProcessing::ApplyComposition_SSE2(DataSourceSurface* aSource, DataSourceSurface* aDest,
CompositeOperator aOperator)
{
return ApplyComposition_SIMD<__m128i,__m128i,__m128i>(aSource, aDest, aOperator);
}
void
FilterProcessing::SeparateColorChannels_SSE2(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride)
{
SeparateColorChannels_SIMD<__m128i>(size, sourceData, sourceStride, channel0Data, channel1Data, channel2Data, channel3Data, channelStride);
}
void
FilterProcessing::CombineColorChannels_SSE2(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data)
{
CombineColorChannels_SIMD<__m128i>(size, resultStride, resultData, channelStride, channel0Data, channel1Data, channel2Data, channel3Data);
}
void
FilterProcessing::DoPremultiplicationCalculation_SSE2(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride)
{
DoPremultiplicationCalculation_SIMD<__m128i,__m128i,__m128i>(aSize, aTargetData, aTargetStride, aSourceData, aSourceStride);
}
void
FilterProcessing::DoUnpremultiplicationCalculation_SSE2(
const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride)
{
DoUnpremultiplicationCalculation_SIMD<__m128i,__m128i>(aSize, aTargetData, aTargetStride, aSourceData, aSourceStride);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::RenderTurbulence_SSE2(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency,
int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect)
{
return RenderTurbulence_SIMD<__m128,__m128i,__m128i>(aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyArithmeticCombine_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4)
{
return ApplyArithmeticCombine_SIMD<__m128i,__m128i,__m128i>(aInput1, aInput2, aK1, aK2, aK3, aK4);
}
} // namespace gfx
} // namespace mozilla

View file

@ -0,0 +1,244 @@
/* -*- 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/. */
#define FILTER_PROCESSING_SCALAR
#include "FilterProcessingSIMD-inl.h"
#include "Logging.h"
namespace mozilla {
namespace gfx {
void
FilterProcessing::ExtractAlpha_Scalar(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride)
{
for (int32_t y = 0; y < size.height; y++) {
for (int32_t x = 0; x < size.width; x++) {
int32_t sourceIndex = y * sourceStride + 4 * x;
int32_t targetIndex = y * alphaStride + x;
alphaData[targetIndex] = sourceData[sourceIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A];
}
}
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ConvertToB8G8R8A8_Scalar(SourceSurface* aSurface)
{
return ConvertToB8G8R8A8_SIMD<simd::Scalaru8x16_t>(aSurface);
}
template<MorphologyOperator Operator>
static void
ApplyMorphologyHorizontal_Scalar(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius)
{
static_assert(Operator == MORPHOLOGY_OPERATOR_ERODE ||
Operator == MORPHOLOGY_OPERATOR_DILATE,
"unexpected morphology operator");
for (int32_t y = aDestRect.y; y < aDestRect.YMost(); y++) {
int32_t startX = aDestRect.x - aRadius;
int32_t endX = aDestRect.x + aRadius;
for (int32_t x = aDestRect.x; x < aDestRect.XMost(); x++, startX++, endX++) {
int32_t sourceIndex = y * aSourceStride + 4 * startX;
uint8_t u[4];
for (size_t i = 0; i < 4; i++) {
u[i] = aSourceData[sourceIndex + i];
}
sourceIndex += 4;
for (int32_t ix = startX + 1; ix <= endX; ix++, sourceIndex += 4) {
for (size_t i = 0; i < 4; i++) {
if (Operator == MORPHOLOGY_OPERATOR_ERODE) {
u[i] = umin(u[i], aSourceData[sourceIndex + i]);
} else {
u[i] = umax(u[i], aSourceData[sourceIndex + i]);
}
}
}
int32_t destIndex = y * aDestStride + 4 * x;
for (size_t i = 0; i < 4; i++) {
aDestData[destIndex+i] = u[i];
}
}
}
}
void
FilterProcessing::ApplyMorphologyHorizontal_Scalar(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOp)
{
if (aOp == MORPHOLOGY_OPERATOR_ERODE) {
gfx::ApplyMorphologyHorizontal_Scalar<MORPHOLOGY_OPERATOR_ERODE>(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
} else {
gfx::ApplyMorphologyHorizontal_Scalar<MORPHOLOGY_OPERATOR_DILATE>(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
}
}
template<MorphologyOperator Operator>
static void ApplyMorphologyVertical_Scalar(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius)
{
static_assert(Operator == MORPHOLOGY_OPERATOR_ERODE ||
Operator == MORPHOLOGY_OPERATOR_DILATE,
"unexpected morphology operator");
int32_t startY = aDestRect.y - aRadius;
int32_t endY = aDestRect.y + aRadius;
for (int32_t y = aDestRect.y; y < aDestRect.YMost(); y++, startY++, endY++) {
for (int32_t x = aDestRect.x; x < aDestRect.XMost(); x++) {
int32_t sourceIndex = startY * aSourceStride + 4 * x;
uint8_t u[4];
for (size_t i = 0; i < 4; i++) {
u[i] = aSourceData[sourceIndex + i];
}
sourceIndex += aSourceStride;
for (int32_t iy = startY + 1; iy <= endY; iy++, sourceIndex += aSourceStride) {
for (size_t i = 0; i < 4; i++) {
if (Operator == MORPHOLOGY_OPERATOR_ERODE) {
u[i] = umin(u[i], aSourceData[sourceIndex + i]);
} else {
u[i] = umax(u[i], aSourceData[sourceIndex + i]);
}
}
}
int32_t destIndex = y * aDestStride + 4 * x;
for (size_t i = 0; i < 4; i++) {
aDestData[destIndex+i] = u[i];
}
}
}
}
void
FilterProcessing::ApplyMorphologyVertical_Scalar(uint8_t* aSourceData, int32_t aSourceStride,
uint8_t* aDestData, int32_t aDestStride,
const IntRect& aDestRect, int32_t aRadius,
MorphologyOperator aOp)
{
if (aOp == MORPHOLOGY_OPERATOR_ERODE) {
gfx::ApplyMorphologyVertical_Scalar<MORPHOLOGY_OPERATOR_ERODE>(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
} else {
gfx::ApplyMorphologyVertical_Scalar<MORPHOLOGY_OPERATOR_DILATE>(
aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
}
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyColorMatrix_Scalar(DataSourceSurface* aInput, const Matrix5x4 &aMatrix)
{
return ApplyColorMatrix_SIMD<simd::Scalari32x4_t,simd::Scalari16x8_t,simd::Scalaru8x16_t>(aInput, aMatrix);
}
void
FilterProcessing::ApplyComposition_Scalar(DataSourceSurface* aSource, DataSourceSurface* aDest,
CompositeOperator aOperator)
{
return ApplyComposition_SIMD<simd::Scalari32x4_t,simd::Scalaru16x8_t,simd::Scalaru8x16_t>(aSource, aDest, aOperator);
}
void
FilterProcessing::SeparateColorChannels_Scalar(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride)
{
for (int32_t y = 0; y < size.height; y++) {
for (int32_t x = 0; x < size.width; x++) {
int32_t sourceIndex = y * sourceStride + 4 * x;
int32_t targetIndex = y * channelStride + x;
channel0Data[targetIndex] = sourceData[sourceIndex];
channel1Data[targetIndex] = sourceData[sourceIndex+1];
channel2Data[targetIndex] = sourceData[sourceIndex+2];
channel3Data[targetIndex] = sourceData[sourceIndex+3];
}
}
}
void
FilterProcessing::CombineColorChannels_Scalar(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data)
{
for (int32_t y = 0; y < size.height; y++) {
for (int32_t x = 0; x < size.width; x++) {
int32_t resultIndex = y * resultStride + 4 * x;
int32_t channelIndex = y * channelStride + x;
resultData[resultIndex] = channel0Data[channelIndex];
resultData[resultIndex+1] = channel1Data[channelIndex];
resultData[resultIndex+2] = channel2Data[channelIndex];
resultData[resultIndex+3] = channel3Data[channelIndex];
}
}
}
void
FilterProcessing::DoPremultiplicationCalculation_Scalar(const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride)
{
for (int32_t y = 0; y < aSize.height; y++) {
for (int32_t x = 0; x < aSize.width; x++) {
int32_t inputIndex = y * aSourceStride + 4 * x;
int32_t targetIndex = y * aTargetStride + 4 * x;
uint8_t alpha = aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A];
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
FastDivideBy255<uint8_t>(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * alpha);
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
FastDivideBy255<uint8_t>(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * alpha);
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
FastDivideBy255<uint8_t>(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * alpha);
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = alpha;
}
}
}
void
FilterProcessing::DoUnpremultiplicationCalculation_Scalar(
const IntSize& aSize,
uint8_t* aTargetData, int32_t aTargetStride,
uint8_t* aSourceData, int32_t aSourceStride)
{
for (int32_t y = 0; y < aSize.height; y++) {
for (int32_t x = 0; x < aSize.width; x++) {
int32_t inputIndex = y * aSourceStride + 4 * x;
int32_t targetIndex = y * aTargetStride + 4 * x;
uint8_t alpha = aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A];
uint16_t alphaFactor = sAlphaFactors[alpha];
// inputColor * alphaFactor + 128 is guaranteed to fit into uint16_t
// because the input is premultiplied and thus inputColor <= inputAlpha.
// The maximum value this can attain is 65520 (which is less than 65535)
// for color == alpha == 244:
// 244 * sAlphaFactors[244] + 128 == 244 * 268 + 128 == 65520
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * alphaFactor + 128) >> 8;
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * alphaFactor + 128) >> 8;
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * alphaFactor + 128) >> 8;
aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = alpha;
}
}
}
already_AddRefed<DataSourceSurface>
FilterProcessing::RenderTurbulence_Scalar(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency,
int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect)
{
return RenderTurbulence_SIMD<simd::Scalarf32x4_t,simd::Scalari32x4_t,simd::Scalaru8x16_t>(
aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect);
}
already_AddRefed<DataSourceSurface>
FilterProcessing::ApplyArithmeticCombine_Scalar(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4)
{
return ApplyArithmeticCombine_SIMD<simd::Scalari32x4_t,simd::Scalari16x8_t,simd::Scalaru8x16_t>(aInput1, aInput2, aK1, aK2, aK3, aK4);
}
} // namespace gfx
} // namespace mozilla

512
gfx/2d/Filters.h Normal file
View file

@ -0,0 +1,512 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_FILTERS_H_
#define MOZILLA_GFX_FILTERS_H_
#include "Types.h"
#include "mozilla/RefPtr.h"
#include "Point.h"
#include "Matrix.h"
#include <vector>
namespace mozilla {
namespace gfx {
class SourceSurface;
enum FilterBackend {
FILTER_BACKEND_SOFTWARE = 0,
FILTER_BACKEND_DIRECT2D1_1,
FILTER_BACKEND_RECORDING
};
enum TransformFilterAtts
{
ATT_TRANSFORM_MATRIX = 0, // Matrix
ATT_TRANSFORM_FILTER // Filter
};
enum TransformFilterInputs
{
IN_TRANSFORM_IN = 0
};
enum BlendFilterAtts
{
ATT_BLEND_BLENDMODE = 0 // uint32_t
};
enum BlendMode
{
BLEND_MODE_MULTIPLY = 0,
BLEND_MODE_SCREEN,
BLEND_MODE_DARKEN,
BLEND_MODE_LIGHTEN,
BLEND_MODE_OVERLAY,
BLEND_MODE_COLOR_DODGE,
BLEND_MODE_COLOR_BURN,
BLEND_MODE_HARD_LIGHT,
BLEND_MODE_SOFT_LIGHT,
BLEND_MODE_DIFFERENCE,
BLEND_MODE_EXCLUSION,
BLEND_MODE_HUE,
BLEND_MODE_SATURATION,
BLEND_MODE_COLOR,
BLEND_MODE_LUMINOSITY
};
enum BlendFilterInputs
{
IN_BLEND_IN = 0,
IN_BLEND_IN2
};
enum MorphologyFilterAtts
{
ATT_MORPHOLOGY_RADII = 0, // IntSize
ATT_MORPHOLOGY_OPERATOR // MorphologyOperator
};
enum MorphologyOperator
{
MORPHOLOGY_OPERATOR_ERODE = 0,
MORPHOLOGY_OPERATOR_DILATE
};
enum MorphologyFilterInputs
{
IN_MORPHOLOGY_IN = 0
};
enum AlphaMode
{
ALPHA_MODE_PREMULTIPLIED = 0,
ALPHA_MODE_STRAIGHT
};
enum ColorMatrixFilterAtts
{
ATT_COLOR_MATRIX_MATRIX = 0, // Matrix5x4
ATT_COLOR_MATRIX_ALPHA_MODE // AlphaMode
};
enum ColorMatrixFilterInputs
{
IN_COLOR_MATRIX_IN = 0
};
enum FloodFilterAtts
{
ATT_FLOOD_COLOR = 0 // Color
};
enum FloodFilterInputs
{
IN_FLOOD_IN = 0
};
enum TileFilterAtts
{
ATT_TILE_SOURCE_RECT = 0 // IntRect
};
enum TileFilterInputs
{
IN_TILE_IN = 0
};
enum TransferAtts
{
ATT_TRANSFER_DISABLE_R = 0, // bool
ATT_TRANSFER_DISABLE_G, // bool
ATT_TRANSFER_DISABLE_B, // bool
ATT_TRANSFER_DISABLE_A // bool
};
enum TransferInputs
{
IN_TRANSFER_IN = 0
};
enum TableTransferAtts
{
ATT_TABLE_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R,
ATT_TABLE_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G,
ATT_TABLE_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B,
ATT_TABLE_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A,
ATT_TABLE_TRANSFER_TABLE_R, // Float[]
ATT_TABLE_TRANSFER_TABLE_G, // Float[]
ATT_TABLE_TRANSFER_TABLE_B, // Float[]
ATT_TABLE_TRANSFER_TABLE_A // Float[]
};
enum TableTransferInputs
{
IN_TABLE_TRANSFER_IN = IN_TRANSFER_IN
};
enum DiscreteTransferAtts
{
ATT_DISCRETE_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R,
ATT_DISCRETE_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G,
ATT_DISCRETE_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B,
ATT_DISCRETE_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A,
ATT_DISCRETE_TRANSFER_TABLE_R, // Float[]
ATT_DISCRETE_TRANSFER_TABLE_G, // Float[]
ATT_DISCRETE_TRANSFER_TABLE_B, // Float[]
ATT_DISCRETE_TRANSFER_TABLE_A // Float[]
};
enum DiscreteTransferInputs
{
IN_DISCRETE_TRANSFER_IN = IN_TRANSFER_IN
};
enum LinearTransferAtts
{
ATT_LINEAR_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R,
ATT_LINEAR_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G,
ATT_LINEAR_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B,
ATT_LINEAR_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A,
ATT_LINEAR_TRANSFER_SLOPE_R, // Float
ATT_LINEAR_TRANSFER_SLOPE_G, // Float
ATT_LINEAR_TRANSFER_SLOPE_B, // Float
ATT_LINEAR_TRANSFER_SLOPE_A, // Float
ATT_LINEAR_TRANSFER_INTERCEPT_R, // Float
ATT_LINEAR_TRANSFER_INTERCEPT_G, // Float
ATT_LINEAR_TRANSFER_INTERCEPT_B, // Float
ATT_LINEAR_TRANSFER_INTERCEPT_A // Float
};
enum LinearTransferInputs
{
IN_LINEAR_TRANSFER_IN = IN_TRANSFER_IN
};
enum GammaTransferAtts
{
ATT_GAMMA_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R,
ATT_GAMMA_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G,
ATT_GAMMA_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B,
ATT_GAMMA_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A,
ATT_GAMMA_TRANSFER_AMPLITUDE_R, // Float
ATT_GAMMA_TRANSFER_AMPLITUDE_G, // Float
ATT_GAMMA_TRANSFER_AMPLITUDE_B, // Float
ATT_GAMMA_TRANSFER_AMPLITUDE_A, // Float
ATT_GAMMA_TRANSFER_EXPONENT_R, // Float
ATT_GAMMA_TRANSFER_EXPONENT_G, // Float
ATT_GAMMA_TRANSFER_EXPONENT_B, // Float
ATT_GAMMA_TRANSFER_EXPONENT_A, // Float
ATT_GAMMA_TRANSFER_OFFSET_R, // Float
ATT_GAMMA_TRANSFER_OFFSET_G, // Float
ATT_GAMMA_TRANSFER_OFFSET_B, // Float
ATT_GAMMA_TRANSFER_OFFSET_A // Float
};
enum GammaTransferInputs
{
IN_GAMMA_TRANSFER_IN = IN_TRANSFER_IN
};
enum ConvolveMatrixAtts
{
ATT_CONVOLVE_MATRIX_KERNEL_SIZE = 0, // IntSize
ATT_CONVOLVE_MATRIX_KERNEL_MATRIX, // Float[]
ATT_CONVOLVE_MATRIX_DIVISOR, // Float
ATT_CONVOLVE_MATRIX_BIAS, // Float
ATT_CONVOLVE_MATRIX_TARGET, // IntPoint
ATT_CONVOLVE_MATRIX_SOURCE_RECT, // IntRect
ATT_CONVOLVE_MATRIX_EDGE_MODE, // ConvolveMatrixEdgeMode
ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH, // Size
ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA, // bool
};
enum ConvolveMatrixEdgeMode
{
EDGE_MODE_DUPLICATE = 0,
EDGE_MODE_WRAP,
EDGE_MODE_NONE
};
enum ConvolveMatrixInputs
{
IN_CONVOLVE_MATRIX_IN = 0
};
enum DisplacementMapAtts
{
ATT_DISPLACEMENT_MAP_SCALE = 0, // Float
ATT_DISPLACEMENT_MAP_X_CHANNEL, // ColorChannel
ATT_DISPLACEMENT_MAP_Y_CHANNEL // ColorChannel
};
enum ColorChannel
{
COLOR_CHANNEL_R = 0,
COLOR_CHANNEL_G,
COLOR_CHANNEL_B,
COLOR_CHANNEL_A
};
enum DisplacementMapInputs
{
IN_DISPLACEMENT_MAP_IN = 0,
IN_DISPLACEMENT_MAP_IN2
};
enum TurbulenceAtts
{
ATT_TURBULENCE_BASE_FREQUENCY = 0, // Size
ATT_TURBULENCE_NUM_OCTAVES, // uint32_t
ATT_TURBULENCE_SEED, // uint32_t
ATT_TURBULENCE_STITCHABLE, // bool
ATT_TURBULENCE_TYPE, // TurbulenceType
ATT_TURBULENCE_RECT // IntRect
};
enum TurbulenceType
{
TURBULENCE_TYPE_TURBULENCE = 0,
TURBULENCE_TYPE_FRACTAL_NOISE
};
enum ArithmeticCombineAtts
{
ATT_ARITHMETIC_COMBINE_COEFFICIENTS = 0 // Float[4]
};
enum ArithmeticCombineInputs
{
IN_ARITHMETIC_COMBINE_IN = 0,
IN_ARITHMETIC_COMBINE_IN2
};
enum CompositeAtts
{
ATT_COMPOSITE_OPERATOR = 0 // CompositeOperator
};
enum CompositeOperator
{
COMPOSITE_OPERATOR_OVER = 0,
COMPOSITE_OPERATOR_IN,
COMPOSITE_OPERATOR_OUT,
COMPOSITE_OPERATOR_ATOP,
COMPOSITE_OPERATOR_XOR
};
enum CompositeInputs
{
// arbitrary number of inputs
IN_COMPOSITE_IN_START = 0
};
enum GaussianBlurAtts
{
ATT_GAUSSIAN_BLUR_STD_DEVIATION = 0 // Float
};
enum GaussianBlurInputs
{
IN_GAUSSIAN_BLUR_IN = 0
};
enum DirectionalBlurAtts
{
ATT_DIRECTIONAL_BLUR_STD_DEVIATION = 0, // Float
ATT_DIRECTIONAL_BLUR_DIRECTION // BlurDirection
};
enum BlurDirection
{
BLUR_DIRECTION_X = 0,
BLUR_DIRECTION_Y
};
enum DirectionalBlurInputs
{
IN_DIRECTIONAL_BLUR_IN = 0
};
enum LightingAtts
{
ATT_POINT_LIGHT_POSITION = 0, // Point3D
ATT_SPOT_LIGHT_POSITION, // Point3D
ATT_SPOT_LIGHT_POINTS_AT, // Point3D
ATT_SPOT_LIGHT_FOCUS, // Float
ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, // Float
ATT_DISTANT_LIGHT_AZIMUTH, // Float
ATT_DISTANT_LIGHT_ELEVATION, // Float
ATT_LIGHTING_COLOR, // Color
ATT_LIGHTING_SURFACE_SCALE, // Float
ATT_LIGHTING_KERNEL_UNIT_LENGTH, // Size
ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT, // Float
ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, // Float
ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT // Float
};
enum LightingInputs
{
IN_LIGHTING_IN = 0
};
enum PointDiffuseAtts
{
ATT_POINT_DIFFUSE_POSITION = ATT_POINT_LIGHT_POSITION,
ATT_POINT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR,
ATT_POINT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE,
ATT_POINT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH,
ATT_POINT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT
};
enum PointDiffuseInputs
{
IN_POINT_DIFFUSE_IN = IN_LIGHTING_IN
};
enum SpotDiffuseAtts
{
ATT_SPOT_DIFFUSE_POSITION = ATT_SPOT_LIGHT_POSITION,
ATT_SPOT_DIFFUSE_POINTS_AT = ATT_SPOT_LIGHT_POINTS_AT,
ATT_SPOT_DIFFUSE_FOCUS = ATT_SPOT_LIGHT_FOCUS,
ATT_SPOT_DIFFUSE_LIMITING_CONE_ANGLE = ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE,
ATT_SPOT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR,
ATT_SPOT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE,
ATT_SPOT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH,
ATT_SPOT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT
};
enum SpotDiffuseInputs
{
IN_SPOT_DIFFUSE_IN = IN_LIGHTING_IN
};
enum DistantDiffuseAtts
{
ATT_DISTANT_DIFFUSE_AZIMUTH = ATT_DISTANT_LIGHT_AZIMUTH,
ATT_DISTANT_DIFFUSE_ELEVATION = ATT_DISTANT_LIGHT_ELEVATION,
ATT_DISTANT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR,
ATT_DISTANT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE,
ATT_DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH,
ATT_DISTANT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT
};
enum DistantDiffuseInputs
{
IN_DISTANT_DIFFUSE_IN = IN_LIGHTING_IN
};
enum PointSpecularAtts
{
ATT_POINT_SPECULAR_POSITION = ATT_POINT_LIGHT_POSITION,
ATT_POINT_SPECULAR_COLOR = ATT_LIGHTING_COLOR,
ATT_POINT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE,
ATT_POINT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH,
ATT_POINT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT,
ATT_POINT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT
};
enum PointSpecularInputs
{
IN_POINT_SPECULAR_IN = IN_LIGHTING_IN
};
enum SpotSpecularAtts
{
ATT_SPOT_SPECULAR_POSITION = ATT_SPOT_LIGHT_POSITION,
ATT_SPOT_SPECULAR_POINTS_AT = ATT_SPOT_LIGHT_POINTS_AT,
ATT_SPOT_SPECULAR_FOCUS = ATT_SPOT_LIGHT_FOCUS,
ATT_SPOT_SPECULAR_LIMITING_CONE_ANGLE = ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE,
ATT_SPOT_SPECULAR_COLOR = ATT_LIGHTING_COLOR,
ATT_SPOT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE,
ATT_SPOT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH,
ATT_SPOT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT,
ATT_SPOT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT
};
enum SpotSpecularInputs
{
IN_SPOT_SPECULAR_IN = IN_LIGHTING_IN
};
enum DistantSpecularAtts
{
ATT_DISTANT_SPECULAR_AZIMUTH = ATT_DISTANT_LIGHT_AZIMUTH,
ATT_DISTANT_SPECULAR_ELEVATION = ATT_DISTANT_LIGHT_ELEVATION,
ATT_DISTANT_SPECULAR_COLOR = ATT_LIGHTING_COLOR,
ATT_DISTANT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE,
ATT_DISTANT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH,
ATT_DISTANT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT,
ATT_DISTANT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT
};
enum DistantSpecularInputs
{
IN_DISTANT_SPECULAR_IN = IN_LIGHTING_IN
};
enum CropAtts
{
ATT_CROP_RECT = 0 // Rect
};
enum CropInputs
{
IN_CROP_IN = 0
};
enum PremultiplyInputs
{
IN_PREMULTIPLY_IN = 0
};
enum UnpremultiplyInputs
{
IN_UNPREMULTIPLY_IN = 0
};
class FilterNode : public RefCounted<FilterNode>
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNode)
virtual ~FilterNode() {}
virtual FilterBackend GetBackendType() = 0;
virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, bool) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, uint32_t) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, Float) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Size &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const IntSize &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const IntPoint &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Rect &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const IntRect &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Point &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Matrix &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Point3D &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Color &) { MOZ_CRASH("GFX: FilterNode"); }
virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) { MOZ_CRASH("GFX: FilterNode"); }
protected:
friend class Factory;
FilterNode() {}
};
} // namespace gfx
} // namespace mozilla
#endif

132
gfx/2d/GenericRefCounted.h Normal file
View file

@ -0,0 +1,132 @@
/* -*- 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/. */
// This header provides virtual, non-templated alternatives to MFBT's RefCounted<T>.
// It intentionally uses MFBT coding style with the intention of moving there
// should there be other use cases for it.
#ifndef MOZILLA_GENERICREFCOUNTED_H_
#define MOZILLA_GENERICREFCOUNTED_H_
#include "mozilla/RefPtr.h"
#include "mozilla/RefCounted.h"
namespace mozilla {
/**
* Common base class for GenericRefCounted and GenericAtomicRefCounted.
*
* Having this shared base class, common to both the atomic and non-atomic
* cases, allows to have RefPtr's that don't care about whether the
* objects they're managing have atomic refcounts or not.
*/
class GenericRefCountedBase
{
protected:
virtual ~GenericRefCountedBase() {};
public:
// AddRef() and Release() method names are for compatibility with nsRefPtr.
virtual void AddRef() = 0;
virtual void Release() = 0;
// ref() and deref() method names are for compatibility with wtf::RefPtr.
// No virtual keywords here: if a subclass wants to override the refcounting
// mechanism, it is welcome to do so by overriding AddRef() and Release().
void ref() { AddRef(); }
void deref() { Release(); }
#ifdef MOZ_REFCOUNTED_LEAK_CHECKING
virtual const char* typeName() const = 0;
virtual size_t typeSize() const = 0;
#endif
};
namespace detail {
template<RefCountAtomicity Atomicity>
class GenericRefCounted : public GenericRefCountedBase
{
protected:
GenericRefCounted() : refCnt(0) { }
virtual ~GenericRefCounted() {
MOZ_ASSERT(refCnt == detail::DEAD);
}
public:
virtual void AddRef() override {
// Note: this method must be thread safe for GenericAtomicRefCounted.
MOZ_ASSERT(int32_t(refCnt) >= 0);
#ifndef MOZ_REFCOUNTED_LEAK_CHECKING
++refCnt;
#else
const char* type = typeName();
uint32_t size = typeSize();
const void* ptr = this;
MozRefCountType cnt = ++refCnt;
detail::RefCountLogger::logAddRef(ptr, cnt, type, size);
#endif
}
virtual void Release() override {
// Note: this method must be thread safe for GenericAtomicRefCounted.
MOZ_ASSERT(int32_t(refCnt) > 0);
#ifndef MOZ_REFCOUNTED_LEAK_CHECKING
MozRefCountType cnt = --refCnt;
#else
const char* type = typeName();
const void* ptr = this;
MozRefCountType cnt = --refCnt;
// Note: it's not safe to touch |this| after decrementing the refcount,
// except for below.
detail::RefCountLogger::logRelease(ptr, cnt, type);
#endif
if (0 == cnt) {
// Because we have atomically decremented the refcount above, only
// one thread can get a 0 count here, so as long as we can assume that
// everything else in the system is accessing this object through
// RefPtrs, it's safe to access |this| here.
#ifdef DEBUG
refCnt = detail::DEAD;
#endif
delete this;
}
}
MozRefCountType refCount() const { return refCnt; }
bool hasOneRef() const {
MOZ_ASSERT(refCnt > 0);
return refCnt == 1;
}
private:
typename Conditional<Atomicity == AtomicRefCount, Atomic<MozRefCountType>, MozRefCountType>::Type refCnt;
};
} // namespace detail
/**
* This reference-counting base class is virtual instead of
* being templated, which is useful in cases where one needs
* genericity at binary code level, but comes at the cost
* of a moderate performance and size overhead, like anything virtual.
*/
class GenericRefCounted : public detail::GenericRefCounted<detail::NonAtomicRefCount>
{
};
/**
* GenericAtomicRefCounted is like GenericRefCounted, with an atomically updated
* reference counter.
*/
class GenericAtomicRefCounted : public detail::GenericRefCounted<detail::AtomicRefCount>
{
};
} // namespace mozilla
#endif

40
gfx/2d/GradientStopsD2D.h Normal file
View file

@ -0,0 +1,40 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_GRADIENTSTOPSD2D_H_
#define MOZILLA_GFX_GRADIENTSTOPSD2D_H_
#include "2D.h"
#include <d2d1.h>
namespace mozilla {
namespace gfx {
class GradientStopsD2D : public GradientStops
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsD2D)
GradientStopsD2D(ID2D1GradientStopCollection *aStopCollection, ID3D11Device *aDevice)
: mStopCollection(aStopCollection)
, mDevice(aDevice)
{}
virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; }
virtual bool IsValid() const final{ return mDevice == Factory::GetDirect3D11Device(); }
private:
friend class DrawTargetD2D;
friend class DrawTargetD2D1;
mutable RefPtr<ID2D1GradientStopCollection> mStopCollection;
RefPtr<ID3D11Device> mDevice;
};
}
}
#endif /* MOZILLA_GFX_GRADIENTSTOPSD2D_H_ */

97
gfx/2d/Helpers.h Normal file
View file

@ -0,0 +1,97 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_2D_HELPERS_H_
#define MOZILLA_GFX_2D_HELPERS_H_
#include "2D.h"
namespace mozilla {
namespace gfx {
class AutoRestoreTransform
{
public:
AutoRestoreTransform()
{
}
explicit AutoRestoreTransform(DrawTarget *aTarget)
: mDrawTarget(aTarget),
mOldTransform(aTarget->GetTransform())
{
}
void Init(DrawTarget *aTarget)
{
MOZ_ASSERT(!mDrawTarget || aTarget == mDrawTarget);
if (!mDrawTarget) {
mDrawTarget = aTarget;
mOldTransform = aTarget->GetTransform();
}
}
~AutoRestoreTransform()
{
if (mDrawTarget) {
mDrawTarget->SetTransform(mOldTransform);
}
}
private:
RefPtr<DrawTarget> mDrawTarget;
Matrix mOldTransform;
};
class AutoPopClips
{
public:
explicit AutoPopClips(DrawTarget *aTarget)
: mDrawTarget(aTarget)
, mPushCount(0)
{
MOZ_ASSERT(mDrawTarget);
}
~AutoPopClips()
{
PopAll();
}
void PushClip(const Path *aPath)
{
mDrawTarget->PushClip(aPath);
++mPushCount;
}
void PushClipRect(const Rect &aRect)
{
mDrawTarget->PushClipRect(aRect);
++mPushCount;
}
void PopClip()
{
MOZ_ASSERT(mPushCount > 0);
mDrawTarget->PopClip();
--mPushCount;
}
void PopAll()
{
while (mPushCount-- > 0) {
mDrawTarget->PopClip();
}
}
private:
RefPtr<DrawTarget> mDrawTarget;
int32_t mPushCount;
};
} // namespace gfx
} // namespace mozilla
#endif // MOZILLA_GFX_2D_HELPERS_H_

352
gfx/2d/HelpersCairo.h Normal file
View file

@ -0,0 +1,352 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_HELPERSCAIRO_H_
#define MOZILLA_GFX_HELPERSCAIRO_H_
#include "2D.h"
#include "cairo.h"
#include "Logging.h"
namespace mozilla {
namespace gfx {
static inline cairo_operator_t
GfxOpToCairoOp(CompositionOp op)
{
switch (op)
{
case CompositionOp::OP_OVER:
return CAIRO_OPERATOR_OVER;
case CompositionOp::OP_ADD:
return CAIRO_OPERATOR_ADD;
case CompositionOp::OP_ATOP:
return CAIRO_OPERATOR_ATOP;
case CompositionOp::OP_OUT:
return CAIRO_OPERATOR_OUT;
case CompositionOp::OP_IN:
return CAIRO_OPERATOR_IN;
case CompositionOp::OP_SOURCE:
return CAIRO_OPERATOR_SOURCE;
case CompositionOp::OP_DEST_IN:
return CAIRO_OPERATOR_DEST_IN;
case CompositionOp::OP_DEST_OUT:
return CAIRO_OPERATOR_DEST_OUT;
case CompositionOp::OP_DEST_OVER:
return CAIRO_OPERATOR_DEST_OVER;
case CompositionOp::OP_DEST_ATOP:
return CAIRO_OPERATOR_DEST_ATOP;
case CompositionOp::OP_XOR:
return CAIRO_OPERATOR_XOR;
case CompositionOp::OP_MULTIPLY:
return CAIRO_OPERATOR_MULTIPLY;
case CompositionOp::OP_SCREEN:
return CAIRO_OPERATOR_SCREEN;
case CompositionOp::OP_OVERLAY:
return CAIRO_OPERATOR_OVERLAY;
case CompositionOp::OP_DARKEN:
return CAIRO_OPERATOR_DARKEN;
case CompositionOp::OP_LIGHTEN:
return CAIRO_OPERATOR_LIGHTEN;
case CompositionOp::OP_COLOR_DODGE:
return CAIRO_OPERATOR_COLOR_DODGE;
case CompositionOp::OP_COLOR_BURN:
return CAIRO_OPERATOR_COLOR_BURN;
case CompositionOp::OP_HARD_LIGHT:
return CAIRO_OPERATOR_HARD_LIGHT;
case CompositionOp::OP_SOFT_LIGHT:
return CAIRO_OPERATOR_SOFT_LIGHT;
case CompositionOp::OP_DIFFERENCE:
return CAIRO_OPERATOR_DIFFERENCE;
case CompositionOp::OP_EXCLUSION:
return CAIRO_OPERATOR_EXCLUSION;
case CompositionOp::OP_HUE:
return CAIRO_OPERATOR_HSL_HUE;
case CompositionOp::OP_SATURATION:
return CAIRO_OPERATOR_HSL_SATURATION;
case CompositionOp::OP_COLOR:
return CAIRO_OPERATOR_HSL_COLOR;
case CompositionOp::OP_LUMINOSITY:
return CAIRO_OPERATOR_HSL_LUMINOSITY;
case CompositionOp::OP_COUNT:
break;
}
return CAIRO_OPERATOR_OVER;
}
static inline cairo_antialias_t
GfxAntialiasToCairoAntialias(AntialiasMode antialias)
{
switch (antialias)
{
case AntialiasMode::NONE:
return CAIRO_ANTIALIAS_NONE;
case AntialiasMode::GRAY:
return CAIRO_ANTIALIAS_GRAY;
case AntialiasMode::SUBPIXEL:
return CAIRO_ANTIALIAS_SUBPIXEL;
default:
return CAIRO_ANTIALIAS_DEFAULT;
}
}
static inline AntialiasMode
CairoAntialiasToGfxAntialias(cairo_antialias_t aAntialias)
{
switch(aAntialias) {
case CAIRO_ANTIALIAS_NONE:
return AntialiasMode::NONE;
case CAIRO_ANTIALIAS_GRAY:
return AntialiasMode::GRAY;
case CAIRO_ANTIALIAS_SUBPIXEL:
return AntialiasMode::SUBPIXEL;
default:
return AntialiasMode::DEFAULT;
}
}
static inline cairo_filter_t
GfxSamplingFilterToCairoFilter(SamplingFilter filter)
{
switch (filter)
{
case SamplingFilter::GOOD:
return CAIRO_FILTER_GOOD;
case SamplingFilter::LINEAR:
return CAIRO_FILTER_BILINEAR;
case SamplingFilter::POINT:
return CAIRO_FILTER_NEAREST;
default:
MOZ_CRASH("GFX: bad Cairo filter");
}
return CAIRO_FILTER_BILINEAR;
}
static inline cairo_extend_t
GfxExtendToCairoExtend(ExtendMode extend)
{
switch (extend)
{
case ExtendMode::CLAMP:
return CAIRO_EXTEND_PAD;
// Cairo doesn't support tiling in only 1 direction,
// So we have to fallback and tile in both.
case ExtendMode::REPEAT_X:
case ExtendMode::REPEAT_Y:
case ExtendMode::REPEAT:
return CAIRO_EXTEND_REPEAT;
case ExtendMode::REFLECT:
return CAIRO_EXTEND_REFLECT;
}
return CAIRO_EXTEND_PAD;
}
static inline cairo_format_t
GfxFormatToCairoFormat(SurfaceFormat format)
{
switch (format)
{
case SurfaceFormat::A8R8G8B8_UINT32:
return CAIRO_FORMAT_ARGB32;
case SurfaceFormat::X8R8G8B8_UINT32:
return CAIRO_FORMAT_RGB24;
case SurfaceFormat::A8:
return CAIRO_FORMAT_A8;
case SurfaceFormat::R5G6B5_UINT16:
return CAIRO_FORMAT_RGB16_565;
default:
gfxCriticalError() << "Unknown image format " << (int)format;
return CAIRO_FORMAT_ARGB32;
}
}
static inline cairo_content_t
GfxFormatToCairoContent(SurfaceFormat format)
{
switch (format)
{
case SurfaceFormat::A8R8G8B8_UINT32:
return CAIRO_CONTENT_COLOR_ALPHA;
case SurfaceFormat::X8R8G8B8_UINT32:
case SurfaceFormat::R5G6B5_UINT16: //fall through
return CAIRO_CONTENT_COLOR;
case SurfaceFormat::A8:
return CAIRO_CONTENT_ALPHA;
default:
gfxCriticalError() << "Unknown image content format " << (int)format;
return CAIRO_CONTENT_COLOR_ALPHA;
}
}
static inline cairo_line_join_t
GfxLineJoinToCairoLineJoin(JoinStyle style)
{
switch (style)
{
case JoinStyle::BEVEL:
return CAIRO_LINE_JOIN_BEVEL;
case JoinStyle::ROUND:
return CAIRO_LINE_JOIN_ROUND;
case JoinStyle::MITER:
return CAIRO_LINE_JOIN_MITER;
case JoinStyle::MITER_OR_BEVEL:
return CAIRO_LINE_JOIN_MITER;
}
return CAIRO_LINE_JOIN_MITER;
}
static inline cairo_line_cap_t
GfxLineCapToCairoLineCap(CapStyle style)
{
switch (style)
{
case CapStyle::BUTT:
return CAIRO_LINE_CAP_BUTT;
case CapStyle::ROUND:
return CAIRO_LINE_CAP_ROUND;
case CapStyle::SQUARE:
return CAIRO_LINE_CAP_SQUARE;
}
return CAIRO_LINE_CAP_BUTT;
}
static inline SurfaceFormat
CairoContentToGfxFormat(cairo_content_t content)
{
switch (content)
{
case CAIRO_CONTENT_COLOR_ALPHA:
return SurfaceFormat::A8R8G8B8_UINT32;
case CAIRO_CONTENT_COLOR:
// BEWARE! format may be 565
return SurfaceFormat::X8R8G8B8_UINT32;
case CAIRO_CONTENT_ALPHA:
return SurfaceFormat::A8;
}
return SurfaceFormat::B8G8R8A8;
}
static inline SurfaceFormat
CairoFormatToGfxFormat(cairo_format_t format)
{
switch (format) {
case CAIRO_FORMAT_ARGB32:
return SurfaceFormat::A8R8G8B8_UINT32;
case CAIRO_FORMAT_RGB24:
return SurfaceFormat::X8R8G8B8_UINT32;
case CAIRO_FORMAT_A8:
return SurfaceFormat::A8;
case CAIRO_FORMAT_RGB16_565:
return SurfaceFormat::R5G6B5_UINT16;
default:
gfxCriticalError() << "Unknown cairo format " << format;
return SurfaceFormat::UNKNOWN;
}
}
static inline FontHinting
CairoHintingToGfxHinting(cairo_hint_style_t aHintStyle)
{
switch (aHintStyle) {
case CAIRO_HINT_STYLE_NONE:
return FontHinting::NONE;
case CAIRO_HINT_STYLE_SLIGHT:
return FontHinting::LIGHT;
case CAIRO_HINT_STYLE_MEDIUM:
return FontHinting::NORMAL;
case CAIRO_HINT_STYLE_FULL:
return FontHinting::FULL;
default:
return FontHinting::NORMAL;
}
}
SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface);
static inline void
GfxMatrixToCairoMatrix(const Matrix& mat, cairo_matrix_t& retval)
{
cairo_matrix_init(&retval, mat._11, mat._12, mat._21, mat._22, mat._31, mat._32);
}
static inline void
SetCairoStrokeOptions(cairo_t* aCtx, const StrokeOptions& aStrokeOptions)
{
cairo_set_line_width(aCtx, aStrokeOptions.mLineWidth);
cairo_set_miter_limit(aCtx, aStrokeOptions.mMiterLimit);
if (aStrokeOptions.mDashPattern) {
// Convert array of floats to array of doubles
std::vector<double> dashes(aStrokeOptions.mDashLength);
bool nonZero = false;
for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) {
if (aStrokeOptions.mDashPattern[i] != 0) {
nonZero = true;
}
dashes[i] = aStrokeOptions.mDashPattern[i];
}
// Avoid all-zero patterns that would trigger the CAIRO_STATUS_INVALID_DASH context error state.
if (nonZero) {
cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength,
aStrokeOptions.mDashOffset);
}
}
cairo_set_line_join(aCtx, GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin));
cairo_set_line_cap(aCtx, GfxLineCapToCairoLineCap(aStrokeOptions.mLineCap));
}
static inline cairo_fill_rule_t
GfxFillRuleToCairoFillRule(FillRule rule)
{
switch (rule)
{
case FillRule::FILL_WINDING:
return CAIRO_FILL_RULE_WINDING;
case FillRule::FILL_EVEN_ODD:
return CAIRO_FILL_RULE_EVEN_ODD;
}
return CAIRO_FILL_RULE_WINDING;
}
// RAII class for temporarily changing the cairo matrix transform. It will use
// the given matrix transform while it is in scope. When it goes out of scope
// it will put the cairo context back the way it was.
class CairoTempMatrix
{
public:
CairoTempMatrix(cairo_t* aCtx, const Matrix& aMatrix)
: mCtx(aCtx)
{
cairo_get_matrix(aCtx, &mSaveMatrix);
cairo_matrix_t matrix;
GfxMatrixToCairoMatrix(aMatrix, matrix);
cairo_set_matrix(aCtx, &matrix);
}
~CairoTempMatrix()
{
cairo_set_matrix(mCtx, &mSaveMatrix);
}
private:
cairo_t* mCtx;
cairo_matrix_t mSaveMatrix;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_HELPERSCAIRO_H_ */

967
gfx/2d/HelpersD2D.h Normal file
View file

@ -0,0 +1,967 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_HELPERSD2D_H_
#define MOZILLA_GFX_HELPERSD2D_H_
#include <d2d1_1.h>
#include <vector>
#include <dwrite.h>
#include <versionhelpers.h>
#include "2D.h"
#include "Logging.h"
#include "Tools.h"
#include "ImageScaling.h"
#include "ScaledFontDWrite.h"
#undef min
#undef max
namespace mozilla {
namespace gfx {
ID2D1Factory1* D2DFactory1();
static ID2D1Factory* D2DFactory() { return D2DFactory1(); }
static inline D2D1_POINT_2F D2DPoint(const Point &aPoint)
{
return D2D1::Point2F(aPoint.x, aPoint.y);
}
static inline D2D1_SIZE_U D2DIntSize(const IntSize &aSize)
{
return D2D1::SizeU(aSize.width, aSize.height);
}
template <typename T>
static inline D2D1_RECT_F D2DRect(const T &aRect)
{
return D2D1::RectF(aRect.x, aRect.y, aRect.XMost(), aRect.YMost());
}
static inline D2D1_EXTEND_MODE D2DExtend(ExtendMode aExtendMode, Axis aAxis)
{
D2D1_EXTEND_MODE extend;
switch (aExtendMode) {
case ExtendMode::REPEAT:
extend = D2D1_EXTEND_MODE_WRAP;
break;
case ExtendMode::REPEAT_X:
{
extend = aAxis == Axis::X_AXIS
? D2D1_EXTEND_MODE_WRAP
: D2D1_EXTEND_MODE_CLAMP;
break;
}
case ExtendMode::REPEAT_Y:
{
extend = aAxis == Axis::Y_AXIS
? D2D1_EXTEND_MODE_WRAP
: D2D1_EXTEND_MODE_CLAMP;
break;
}
case ExtendMode::REFLECT:
extend = D2D1_EXTEND_MODE_MIRROR;
break;
default:
extend = D2D1_EXTEND_MODE_CLAMP;
}
return extend;
}
static inline D2D1_BITMAP_INTERPOLATION_MODE D2DFilter(const SamplingFilter aSamplingFilter)
{
switch (aSamplingFilter) {
case SamplingFilter::POINT:
return D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
default:
return D2D1_BITMAP_INTERPOLATION_MODE_LINEAR;
}
}
static inline D2D1_INTERPOLATION_MODE D2DInterpolationMode(const SamplingFilter aSamplingFilter)
{
switch (aSamplingFilter) {
case SamplingFilter::POINT:
return D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
default:
return D2D1_INTERPOLATION_MODE_LINEAR;
}
}
static inline D2D1_MATRIX_5X4_F D2DMatrix5x4(const Matrix5x4 &aMatrix)
{
return D2D1::Matrix5x4F(aMatrix._11, aMatrix._12, aMatrix._13, aMatrix._14,
aMatrix._21, aMatrix._22, aMatrix._23, aMatrix._24,
aMatrix._31, aMatrix._32, aMatrix._33, aMatrix._34,
aMatrix._41, aMatrix._42, aMatrix._43, aMatrix._44,
aMatrix._51, aMatrix._52, aMatrix._53, aMatrix._54);
}
static inline D2D1_VECTOR_3F D2DVector3D(const Point3D &aPoint)
{
return D2D1::Vector3F(aPoint.x, aPoint.y, aPoint.z);
}
static inline D2D1_ANTIALIAS_MODE D2DAAMode(AntialiasMode aMode)
{
switch (aMode) {
case AntialiasMode::NONE:
return D2D1_ANTIALIAS_MODE_ALIASED;
default:
return D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
}
}
static inline D2D1_MATRIX_3X2_F D2DMatrix(const Matrix &aTransform)
{
return D2D1::Matrix3x2F(aTransform._11, aTransform._12,
aTransform._21, aTransform._22,
aTransform._31, aTransform._32);
}
static inline D2D1_COLOR_F D2DColor(const Color &aColor)
{
return D2D1::ColorF(aColor.r, aColor.g, aColor.b, aColor.a);
}
static inline IntSize ToIntSize(const D2D1_SIZE_U &aSize)
{
return IntSize(aSize.width, aSize.height);
}
static inline SurfaceFormat ToPixelFormat(const D2D1_PIXEL_FORMAT &aFormat)
{
switch(aFormat.format) {
case DXGI_FORMAT_A8_UNORM:
case DXGI_FORMAT_R8_UNORM:
return SurfaceFormat::A8;
case DXGI_FORMAT_B8G8R8A8_UNORM:
if (aFormat.alphaMode == D2D1_ALPHA_MODE_IGNORE) {
return SurfaceFormat::B8G8R8X8;
} else {
return SurfaceFormat::B8G8R8A8;
}
default:
return SurfaceFormat::B8G8R8A8;
}
}
static inline Rect ToRect(const D2D1_RECT_F &aRect)
{
return Rect(aRect.left, aRect.top, aRect.right - aRect.left, aRect.bottom - aRect.top);
}
static inline Matrix ToMatrix(const D2D1_MATRIX_3X2_F &aTransform)
{
return Matrix(aTransform._11, aTransform._12,
aTransform._21, aTransform._22,
aTransform._31, aTransform._32);
}
static inline Point ToPoint(const D2D1_POINT_2F &aPoint)
{
return Point(aPoint.x, aPoint.y);
}
static inline DXGI_FORMAT DXGIFormat(SurfaceFormat aFormat)
{
switch (aFormat) {
case SurfaceFormat::B8G8R8A8:
return DXGI_FORMAT_B8G8R8A8_UNORM;
case SurfaceFormat::B8G8R8X8:
return DXGI_FORMAT_B8G8R8A8_UNORM;
case SurfaceFormat::A8:
return DXGI_FORMAT_A8_UNORM;
default:
return DXGI_FORMAT_UNKNOWN;
}
}
static inline D2D1_ALPHA_MODE D2DAlphaModeForFormat(SurfaceFormat aFormat)
{
switch (aFormat) {
case SurfaceFormat::B8G8R8X8:
return D2D1_ALPHA_MODE_IGNORE;
default:
return D2D1_ALPHA_MODE_PREMULTIPLIED;
}
}
static inline D2D1_PIXEL_FORMAT D2DPixelFormat(SurfaceFormat aFormat)
{
return D2D1::PixelFormat(DXGIFormat(aFormat), D2DAlphaModeForFormat(aFormat));
}
static inline bool D2DSupportsCompositeMode(CompositionOp aOp)
{
switch(aOp) {
case CompositionOp::OP_OVER:
case CompositionOp::OP_ADD:
case CompositionOp::OP_ATOP:
case CompositionOp::OP_OUT:
case CompositionOp::OP_IN:
case CompositionOp::OP_SOURCE:
case CompositionOp::OP_DEST_IN:
case CompositionOp::OP_DEST_OUT:
case CompositionOp::OP_DEST_OVER:
case CompositionOp::OP_DEST_ATOP:
case CompositionOp::OP_XOR:
return true;
default:
return false;
}
}
static inline D2D1_COMPOSITE_MODE D2DCompositionMode(CompositionOp aOp)
{
switch(aOp) {
case CompositionOp::OP_OVER:
return D2D1_COMPOSITE_MODE_SOURCE_OVER;
case CompositionOp::OP_ADD:
return D2D1_COMPOSITE_MODE_PLUS;
case CompositionOp::OP_ATOP:
return D2D1_COMPOSITE_MODE_SOURCE_ATOP;
case CompositionOp::OP_OUT:
return D2D1_COMPOSITE_MODE_SOURCE_OUT;
case CompositionOp::OP_IN:
return D2D1_COMPOSITE_MODE_SOURCE_IN;
case CompositionOp::OP_SOURCE:
return D2D1_COMPOSITE_MODE_SOURCE_COPY;
case CompositionOp::OP_DEST_IN:
return D2D1_COMPOSITE_MODE_DESTINATION_IN;
case CompositionOp::OP_DEST_OUT:
return D2D1_COMPOSITE_MODE_DESTINATION_OUT;
case CompositionOp::OP_DEST_OVER:
return D2D1_COMPOSITE_MODE_DESTINATION_OVER;
case CompositionOp::OP_DEST_ATOP:
return D2D1_COMPOSITE_MODE_DESTINATION_ATOP;
case CompositionOp::OP_XOR:
return D2D1_COMPOSITE_MODE_XOR;
default:
return D2D1_COMPOSITE_MODE_SOURCE_OVER;
}
}
static inline D2D1_BLEND_MODE D2DBlendMode(CompositionOp aOp)
{
switch (aOp) {
case CompositionOp::OP_MULTIPLY:
return D2D1_BLEND_MODE_MULTIPLY;
case CompositionOp::OP_SCREEN:
return D2D1_BLEND_MODE_SCREEN;
case CompositionOp::OP_OVERLAY:
return D2D1_BLEND_MODE_OVERLAY;
case CompositionOp::OP_DARKEN:
return D2D1_BLEND_MODE_DARKEN;
case CompositionOp::OP_LIGHTEN:
return D2D1_BLEND_MODE_LIGHTEN;
case CompositionOp::OP_COLOR_DODGE:
return D2D1_BLEND_MODE_COLOR_DODGE;
case CompositionOp::OP_COLOR_BURN:
return D2D1_BLEND_MODE_COLOR_BURN;
case CompositionOp::OP_HARD_LIGHT:
return D2D1_BLEND_MODE_HARD_LIGHT;
case CompositionOp::OP_SOFT_LIGHT:
return D2D1_BLEND_MODE_SOFT_LIGHT;
case CompositionOp::OP_DIFFERENCE:
return D2D1_BLEND_MODE_DIFFERENCE;
case CompositionOp::OP_EXCLUSION:
return D2D1_BLEND_MODE_EXCLUSION;
case CompositionOp::OP_HUE:
return D2D1_BLEND_MODE_HUE;
case CompositionOp::OP_SATURATION:
return D2D1_BLEND_MODE_SATURATION;
case CompositionOp::OP_COLOR:
return D2D1_BLEND_MODE_COLOR;
case CompositionOp::OP_LUMINOSITY:
return D2D1_BLEND_MODE_LUMINOSITY;
default:
return D2D1_BLEND_MODE_MULTIPLY;
}
}
static inline bool D2DSupportsPrimitiveBlendMode(CompositionOp aOp)
{
switch (aOp) {
case CompositionOp::OP_OVER:
// case CompositionOp::OP_SOURCE:
return true;
// case CompositionOp::OP_DARKEN:
case CompositionOp::OP_ADD:
return IsWindows8Point1OrGreater();
default:
return false;
}
}
static inline D2D1_PRIMITIVE_BLEND D2DPrimitiveBlendMode(CompositionOp aOp)
{
switch (aOp) {
case CompositionOp::OP_OVER:
return D2D1_PRIMITIVE_BLEND_SOURCE_OVER;
// D2D1_PRIMITIVE_BLEND_COPY should leave pixels out of the source's
// bounds unchanged, but doesn't- breaking unbounded ops.
// D2D1_PRIMITIVE_BLEND_MIN doesn't quite work like darken either, as it
// accounts for the source alpha.
//
// case CompositionOp::OP_SOURCE:
// return D2D1_PRIMITIVE_BLEND_COPY;
// case CompositionOp::OP_DARKEN:
// return D2D1_PRIMITIVE_BLEND_MIN;
case CompositionOp::OP_ADD:
return D2D1_PRIMITIVE_BLEND_ADD;
default:
return D2D1_PRIMITIVE_BLEND_SOURCE_OVER;
}
}
static inline bool IsPatternSupportedByD2D(const Pattern &aPattern)
{
if (aPattern.GetType() != PatternType::RADIAL_GRADIENT) {
return true;
}
const RadialGradientPattern *pat =
static_cast<const RadialGradientPattern*>(&aPattern);
if (pat->mRadius1 != 0) {
return false;
}
Point diff = pat->mCenter2 - pat->mCenter1;
if (sqrt(diff.x * diff.x + diff.y * diff.y) >= pat->mRadius2) {
// Inner point lies outside the circle.
return false;
}
return true;
}
/**
* This structure is used to pass rectangles to our shader constant. We can use
* this for passing rectangular areas to SetVertexShaderConstant. In the format
* of a 4 component float(x,y,width,height). Our vertex shader can then use
* this to construct rectangular positions from the 0,0-1,1 quad that we source
* it with.
*/
struct ShaderConstantRectD3D10
{
float mX, mY, mWidth, mHeight;
ShaderConstantRectD3D10(float aX, float aY, float aWidth, float aHeight)
: mX(aX), mY(aY), mWidth(aWidth), mHeight(aHeight)
{ }
// For easy passing to SetVertexShaderConstantF.
operator float* () { return &mX; }
};
static inline DWRITE_MATRIX
DWriteMatrixFromMatrix(Matrix &aMatrix)
{
DWRITE_MATRIX mat;
mat.m11 = aMatrix._11;
mat.m12 = aMatrix._12;
mat.m21 = aMatrix._21;
mat.m22 = aMatrix._22;
mat.dx = aMatrix._31;
mat.dy = aMatrix._32;
return mat;
}
class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN
{
static const unsigned kNumAutoGlyphs = 256;
public:
AutoDWriteGlyphRun() {
glyphCount = 0;
}
~AutoDWriteGlyphRun() {
if (glyphCount > kNumAutoGlyphs) {
delete[] glyphIndices;
delete[] glyphAdvances;
delete[] glyphOffsets;
}
}
void allocate(unsigned aNumGlyphs) {
glyphCount = aNumGlyphs;
if (aNumGlyphs <= kNumAutoGlyphs) {
glyphIndices = &mAutoIndices[0];
glyphAdvances = &mAutoAdvances[0];
glyphOffsets = &mAutoOffsets[0];
} else {
glyphIndices = new UINT16[aNumGlyphs];
glyphAdvances = new FLOAT[aNumGlyphs];
glyphOffsets = new DWRITE_GLYPH_OFFSET[aNumGlyphs];
}
}
private:
DWRITE_GLYPH_OFFSET mAutoOffsets[kNumAutoGlyphs];
FLOAT mAutoAdvances[kNumAutoGlyphs];
UINT16 mAutoIndices[kNumAutoGlyphs];
};
static inline void
DWriteGlyphRunFromGlyphs(const GlyphBuffer &aGlyphs, ScaledFontDWrite *aFont, AutoDWriteGlyphRun *run)
{
run->allocate(aGlyphs.mNumGlyphs);
FLOAT *advances = const_cast<FLOAT*>(run->glyphAdvances);
UINT16 *indices = const_cast<UINT16*>(run->glyphIndices);
DWRITE_GLYPH_OFFSET *offsets = const_cast<DWRITE_GLYPH_OFFSET*>(run->glyphOffsets);
memset(advances, 0, sizeof(FLOAT) * aGlyphs.mNumGlyphs);
for (unsigned int i = 0; i < aGlyphs.mNumGlyphs; i++) {
indices[i] = aGlyphs.mGlyphs[i].mIndex;
offsets[i].advanceOffset = aGlyphs.mGlyphs[i].mPosition.x;
offsets[i].ascenderOffset = -aGlyphs.mGlyphs[i].mPosition.y;
}
run->bidiLevel = 0;
run->fontFace = aFont->mFontFace;
run->fontEmSize = aFont->GetSize();
run->glyphCount = aGlyphs.mNumGlyphs;
run->isSideways = FALSE;
}
static inline already_AddRefed<ID2D1Geometry>
ConvertRectToGeometry(const D2D1_RECT_F& aRect)
{
RefPtr<ID2D1RectangleGeometry> rectGeom;
D2DFactory()->CreateRectangleGeometry(&aRect, getter_AddRefs(rectGeom));
return rectGeom.forget();
}
static inline already_AddRefed<ID2D1Geometry>
GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform)
{
RefPtr<ID2D1PathGeometry> tmpGeometry;
D2DFactory()->CreatePathGeometry(getter_AddRefs(tmpGeometry));
RefPtr<ID2D1GeometrySink> currentSink;
tmpGeometry->Open(getter_AddRefs(currentSink));
aGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
aTransform, currentSink);
currentSink->Close();
return tmpGeometry.forget();
}
static inline already_AddRefed<ID2D1Geometry>
IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB)
{
RefPtr<ID2D1PathGeometry> pathGeom;
D2DFactory()->CreatePathGeometry(getter_AddRefs(pathGeom));
RefPtr<ID2D1GeometrySink> sink;
pathGeom->Open(getter_AddRefs(sink));
aGeometryA->CombineWithGeometry(aGeometryB, D2D1_COMBINE_MODE_INTERSECT, nullptr, sink);
sink->Close();
return pathGeom.forget();
}
static inline already_AddRefed<ID2D1StrokeStyle>
CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions)
{
RefPtr<ID2D1StrokeStyle> style;
D2D1_CAP_STYLE capStyle;
D2D1_LINE_JOIN joinStyle;
switch (aStrokeOptions.mLineCap) {
case CapStyle::BUTT:
capStyle = D2D1_CAP_STYLE_FLAT;
break;
case CapStyle::ROUND:
capStyle = D2D1_CAP_STYLE_ROUND;
break;
case CapStyle::SQUARE:
capStyle = D2D1_CAP_STYLE_SQUARE;
break;
}
switch (aStrokeOptions.mLineJoin) {
case JoinStyle::MITER:
joinStyle = D2D1_LINE_JOIN_MITER;
break;
case JoinStyle::MITER_OR_BEVEL:
joinStyle = D2D1_LINE_JOIN_MITER_OR_BEVEL;
break;
case JoinStyle::ROUND:
joinStyle = D2D1_LINE_JOIN_ROUND;
break;
case JoinStyle::BEVEL:
joinStyle = D2D1_LINE_JOIN_BEVEL;
break;
}
HRESULT hr;
// We need to check mDashLength in addition to mDashPattern here since if
// mDashPattern is set but mDashLength is zero then the stroke will fail to
// paint.
if (aStrokeOptions.mDashLength > 0 && aStrokeOptions.mDashPattern) {
typedef std::vector<Float> FloatVector;
// D2D "helpfully" multiplies the dash pattern by the line width.
// That's not what cairo does, or is what <canvas>'s dash wants.
// So fix the multiplication in advance.
Float lineWidth = aStrokeOptions.mLineWidth;
FloatVector dash(aStrokeOptions.mDashPattern,
aStrokeOptions.mDashPattern + aStrokeOptions.mDashLength);
for (FloatVector::iterator it = dash.begin(); it != dash.end(); ++it) {
*it /= lineWidth;
}
hr = D2DFactory()->CreateStrokeStyle(
D2D1::StrokeStyleProperties(capStyle, capStyle,
capStyle, joinStyle,
aStrokeOptions.mMiterLimit,
D2D1_DASH_STYLE_CUSTOM,
aStrokeOptions.mDashOffset / lineWidth),
&dash[0], // data() is not C++98, although it's in recent gcc
// and VC10's STL
dash.size(),
getter_AddRefs(style));
} else {
hr = D2DFactory()->CreateStrokeStyle(
D2D1::StrokeStyleProperties(capStyle, capStyle,
capStyle, joinStyle,
aStrokeOptions.mMiterLimit),
nullptr, 0, getter_AddRefs(style));
}
if (FAILED(hr)) {
gfxWarning() << "Failed to create Direct2D stroke style.";
}
return style.forget();
}
// This creates a (partially) uploaded bitmap for a DataSourceSurface. It
// uploads the minimum requirement and possibly downscales. It adjusts the
// input Matrix to compensate.
static inline already_AddRefed<ID2D1Bitmap>
CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestinationTransform,
const IntSize &aDestinationSize, ExtendMode aExtendMode,
Matrix &aSourceTransform, ID2D1RenderTarget *aRT,
const IntRect* aSourceRect = nullptr)
{
RefPtr<ID2D1Bitmap> bitmap;
// This is where things get complicated. The source surface was
// created for a surface that was too large to fit in a texture.
// We'll need to figure out if we can work with a partial upload
// or downsample in software.
Matrix transform = aDestinationTransform;
Matrix invTransform = transform = aSourceTransform * transform;
if (!invTransform.Invert()) {
// Singular transform, nothing to be drawn.
return nullptr;
}
Rect rect(0, 0, Float(aDestinationSize.width), Float(aDestinationSize.height));
// Calculate the rectangle of the source mapped to our surface.
rect = invTransform.TransformBounds(rect);
rect.RoundOut();
IntSize size = aSurface->GetSize();
Rect uploadRect(0, 0, Float(size.width), Float(size.height));
if (aSourceRect) {
uploadRect = Rect(aSourceRect->x, aSourceRect->y, aSourceRect->width, aSourceRect->height);
}
// Limit the uploadRect as much as possible without supporting discontiguous uploads
//
// region we will paint from
// uploadRect
// .---------------. .---------------. resulting uploadRect
// | |rect | |
// | .---------. .----. .----. .---------------.
// | | | ----> | | | | ----> | |
// | '---------' '----' '----' '---------------'
// '---------------' '---------------'
//
//
if (uploadRect.Contains(rect)) {
// Extend mode is irrelevant, the displayed rect is completely contained
// by the source bitmap.
uploadRect = rect;
} else if (aExtendMode == ExtendMode::CLAMP && uploadRect.Intersects(rect)) {
// Calculate the rectangle on the source bitmap that touches our
// surface, and upload that, for ExtendMode::CLAMP we can actually guarantee
// correct behaviour in this case.
uploadRect = uploadRect.Intersect(rect);
// We now proceed to check if we can limit at least one dimension of the
// upload rect safely without looking at extend mode.
} else if (rect.x >= 0 && rect.XMost() < size.width) {
uploadRect.x = rect.x;
uploadRect.width = rect.width;
} else if (rect.y >= 0 && rect.YMost() < size.height) {
uploadRect.y = rect.y;
uploadRect.height = rect.height;
}
if (uploadRect.IsEmpty()) {
// Nothing to be drawn.
return nullptr;
}
if (uploadRect.width <= aRT->GetMaximumBitmapSize() &&
uploadRect.height <= aRT->GetMaximumBitmapSize()) {
{
// Scope to auto-Unmap() |mapping|.
DataSourceSurface::ScopedMap mapping(aSurface, DataSourceSurface::READ);
if (MOZ2D_WARN_IF(!mapping.IsMapped())) {
return nullptr;
}
// A partial upload will suffice.
aRT->CreateBitmap(D2D1::SizeU(uint32_t(uploadRect.width), uint32_t(uploadRect.height)),
mapping.GetData() + int(uploadRect.x) * 4 + int(uploadRect.y) * mapping.GetStride(),
mapping.GetStride(),
D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())),
getter_AddRefs(bitmap));
}
aSourceTransform.PreTranslate(uploadRect.x, uploadRect.y);
return bitmap.forget();
} else {
int Bpp = BytesPerPixel(aSurface->GetFormat());
if (Bpp != 4) {
// This shouldn't actually happen in practice!
MOZ_ASSERT(false);
return nullptr;
}
{
// Scope to auto-Unmap() |mapping|.
DataSourceSurface::ScopedMap mapping(aSurface, DataSourceSurface::READ);
if (MOZ2D_WARN_IF(!mapping.IsMapped())) {
return nullptr;
}
ImageHalfScaler scaler(mapping.GetData(), mapping.GetStride(), size);
// Calculate the maximum width/height of the image post transform.
Point topRight = transform.TransformPoint(Point(Float(size.width), 0));
Point topLeft = transform.TransformPoint(Point(0, 0));
Point bottomRight = transform.TransformPoint(Point(Float(size.width), Float(size.height)));
Point bottomLeft = transform.TransformPoint(Point(0, Float(size.height)));
IntSize scaleSize;
scaleSize.width = int32_t(std::max(Distance(topRight, topLeft),
Distance(bottomRight, bottomLeft)));
scaleSize.height = int32_t(std::max(Distance(topRight, bottomRight),
Distance(topLeft, bottomLeft)));
if (unsigned(scaleSize.width) > aRT->GetMaximumBitmapSize()) {
// Ok, in this case we'd really want a downscale of a part of the bitmap,
// perhaps we can do this later but for simplicity let's do something
// different here and assume it's good enough, this should be rare!
scaleSize.width = 4095;
}
if (unsigned(scaleSize.height) > aRT->GetMaximumBitmapSize()) {
scaleSize.height = 4095;
}
scaler.ScaleForSize(scaleSize);
IntSize newSize = scaler.GetSize();
if (newSize.IsEmpty()) {
return nullptr;
}
aRT->CreateBitmap(D2D1::SizeU(newSize.width, newSize.height),
scaler.GetScaledData(), scaler.GetStride(),
D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())),
getter_AddRefs(bitmap));
aSourceTransform.PreScale(Float(size.width) / newSize.width,
Float(size.height) / newSize.height);
}
return bitmap.forget();
}
}
static inline void AddRectToSink(ID2D1GeometrySink* aSink, const D2D1_RECT_F& aRect)
{
aSink->BeginFigure(D2D1::Point2F(aRect.left, aRect.top), D2D1_FIGURE_BEGIN_FILLED);
aSink->AddLine(D2D1::Point2F(aRect.right, aRect.top));
aSink->AddLine(D2D1::Point2F(aRect.right, aRect.bottom));
aSink->AddLine(D2D1::Point2F(aRect.left, aRect.bottom));
aSink->EndFigure(D2D1_FIGURE_END_CLOSED);
}
class DCCommandSink : public ID2D1CommandSink
{
public:
DCCommandSink(ID2D1DeviceContext* aCtx) : mCtx(aCtx)
{
}
HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
{
if (!aPtr) {
return E_POINTER;
}
if (aIID == IID_IUnknown) {
*aPtr = static_cast<IUnknown*>(this);
return S_OK;
} else if (aIID == IID_ID2D1CommandSink) {
*aPtr = static_cast<ID2D1CommandSink*>(this);
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
return 1;
}
ULONG STDMETHODCALLTYPE Release()
{
return 1;
}
STDMETHODIMP BeginDraw()
{
// We don't want to do anything here!
return S_OK;
}
STDMETHODIMP EndDraw()
{
// We don't want to do anything here!
return S_OK;
}
STDMETHODIMP SetAntialiasMode(
D2D1_ANTIALIAS_MODE antialiasMode
)
{
mCtx->SetAntialiasMode(antialiasMode);
return S_OK;
}
STDMETHODIMP SetTags(D2D1_TAG tag1, D2D1_TAG tag2)
{
mCtx->SetTags(tag1, tag2);
return S_OK;
}
STDMETHODIMP SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE textAntialiasMode)
{
mCtx->SetTextAntialiasMode(textAntialiasMode);
return S_OK;
}
STDMETHODIMP SetTextRenderingParams(_In_opt_ IDWriteRenderingParams *textRenderingParams)
{
mCtx->SetTextRenderingParams(textRenderingParams);
return S_OK;
}
STDMETHODIMP SetTransform(_In_ CONST D2D1_MATRIX_3X2_F *transform)
{
mCtx->SetTransform(transform);
return S_OK;
}
STDMETHODIMP SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND primitiveBlend)
{
mCtx->SetPrimitiveBlend(primitiveBlend);
return S_OK;
}
STDMETHODIMP SetUnitMode(D2D1_UNIT_MODE unitMode)
{
mCtx->SetUnitMode(unitMode);
return S_OK;
}
STDMETHODIMP Clear(_In_opt_ CONST D2D1_COLOR_F *color)
{
mCtx->Clear(color);
return S_OK;
}
STDMETHODIMP DrawGlyphRun(
D2D1_POINT_2F baselineOrigin,
_In_ CONST DWRITE_GLYPH_RUN *glyphRun,
_In_opt_ CONST DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription,
_In_ ID2D1Brush *foregroundBrush,
DWRITE_MEASURING_MODE measuringMode
)
{
mCtx->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription,
foregroundBrush, measuringMode);
return S_OK;
}
STDMETHODIMP DrawLine(
D2D1_POINT_2F point0,
D2D1_POINT_2F point1,
_In_ ID2D1Brush *brush,
FLOAT strokeWidth,
_In_opt_ ID2D1StrokeStyle *strokeStyle
)
{
mCtx->DrawLine(point0, point1, brush, strokeWidth, strokeStyle);
return S_OK;
}
STDMETHODIMP DrawGeometry(
_In_ ID2D1Geometry *geometry,
_In_ ID2D1Brush *brush,
FLOAT strokeWidth,
_In_opt_ ID2D1StrokeStyle *strokeStyle
)
{
mCtx->DrawGeometry(geometry, brush, strokeWidth, strokeStyle);
return S_OK;
}
STDMETHODIMP DrawRectangle(
_In_ CONST D2D1_RECT_F *rect,
_In_ ID2D1Brush *brush,
FLOAT strokeWidth,
_In_opt_ ID2D1StrokeStyle *strokeStyle
)
{
mCtx->DrawRectangle(rect, brush, strokeWidth, strokeStyle);
return S_OK;
}
STDMETHODIMP DrawBitmap(
_In_ ID2D1Bitmap *bitmap,
_In_opt_ CONST D2D1_RECT_F *destinationRectangle,
FLOAT opacity,
D2D1_INTERPOLATION_MODE interpolationMode,
_In_opt_ CONST D2D1_RECT_F *sourceRectangle,
_In_opt_ CONST D2D1_MATRIX_4X4_F *perspectiveTransform
)
{
mCtx->DrawBitmap(bitmap, destinationRectangle, opacity,
interpolationMode, sourceRectangle,
perspectiveTransform);
return S_OK;
}
STDMETHODIMP DrawImage(
_In_ ID2D1Image *image,
_In_opt_ CONST D2D1_POINT_2F *targetOffset,
_In_opt_ CONST D2D1_RECT_F *imageRectangle,
D2D1_INTERPOLATION_MODE interpolationMode,
D2D1_COMPOSITE_MODE compositeMode
)
{
mCtx->DrawImage(image, targetOffset, imageRectangle,
interpolationMode, compositeMode);
return S_OK;
}
STDMETHODIMP DrawGdiMetafile(
_In_ ID2D1GdiMetafile *gdiMetafile,
_In_opt_ CONST D2D1_POINT_2F *targetOffset
)
{
mCtx->DrawGdiMetafile(gdiMetafile, targetOffset);
return S_OK;
}
STDMETHODIMP FillMesh(
_In_ ID2D1Mesh *mesh,
_In_ ID2D1Brush *brush
)
{
mCtx->FillMesh(mesh, brush);
return S_OK;
}
STDMETHODIMP FillOpacityMask(
_In_ ID2D1Bitmap *opacityMask,
_In_ ID2D1Brush *brush,
_In_opt_ CONST D2D1_RECT_F *destinationRectangle,
_In_opt_ CONST D2D1_RECT_F *sourceRectangle
)
{
mCtx->FillOpacityMask(opacityMask, brush, destinationRectangle,
sourceRectangle);
return S_OK;
}
STDMETHODIMP FillGeometry(
_In_ ID2D1Geometry *geometry,
_In_ ID2D1Brush *brush,
_In_opt_ ID2D1Brush *opacityBrush
)
{
mCtx->FillGeometry(geometry, brush, opacityBrush);
return S_OK;
}
STDMETHODIMP FillRectangle(
_In_ CONST D2D1_RECT_F *rect,
_In_ ID2D1Brush *brush
)
{
mCtx->FillRectangle(rect, brush);
return S_OK;
}
STDMETHODIMP PushAxisAlignedClip(
_In_ CONST D2D1_RECT_F *clipRect,
D2D1_ANTIALIAS_MODE antialiasMode
)
{
mCtx->PushAxisAlignedClip(clipRect, antialiasMode);
return S_OK;
}
STDMETHODIMP PushLayer(
_In_ CONST D2D1_LAYER_PARAMETERS1 *layerParameters1,
_In_opt_ ID2D1Layer *layer
)
{
mCtx->PushLayer(layerParameters1, layer);
return S_OK;
}
STDMETHODIMP PopAxisAlignedClip()
{
mCtx->PopAxisAlignedClip();
return S_OK;
}
STDMETHODIMP PopLayer()
{
mCtx->PopLayer();
return S_OK;
}
ID2D1DeviceContext* mCtx;
};
}
}
#endif /* MOZILLA_GFX_HELPERSD2D_H_ */

396
gfx/2d/HelpersSkia.h Normal file
View file

@ -0,0 +1,396 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_HELPERSSKIA_H_
#define MOZILLA_GFX_HELPERSSKIA_H_
#include "2D.h"
#include "skia/include/core/SkCanvas.h"
#include "skia/include/effects/SkDashPathEffect.h"
#include "skia/include/core/SkShader.h"
#ifdef USE_SKIA_GPU
#include "skia/include/gpu/GrTypes.h"
#endif
#include "mozilla/Assertions.h"
#include <vector>
#include "nsDebug.h"
namespace mozilla {
namespace gfx {
static inline SkColorType
GfxFormatToSkiaColorType(SurfaceFormat format)
{
switch (format)
{
case SurfaceFormat::B8G8R8A8:
return kBGRA_8888_SkColorType;
case SurfaceFormat::B8G8R8X8:
// We probably need to do something here.
return kBGRA_8888_SkColorType;
case SurfaceFormat::R5G6B5_UINT16:
return kRGB_565_SkColorType;
case SurfaceFormat::A8:
return kAlpha_8_SkColorType;
default:
return kRGBA_8888_SkColorType;
}
}
static inline SurfaceFormat
SkiaColorTypeToGfxFormat(SkColorType aColorType, SkAlphaType aAlphaType = kPremul_SkAlphaType)
{
switch (aColorType)
{
case kBGRA_8888_SkColorType:
return aAlphaType == kOpaque_SkAlphaType ?
SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
case kRGB_565_SkColorType:
return SurfaceFormat::R5G6B5_UINT16;
case kAlpha_8_SkColorType:
return SurfaceFormat::A8;
default:
return SurfaceFormat::B8G8R8A8;
}
}
static inline SkAlphaType
GfxFormatToSkiaAlphaType(SurfaceFormat format)
{
switch (format)
{
case SurfaceFormat::B8G8R8X8:
case SurfaceFormat::R5G6B5_UINT16:
return kOpaque_SkAlphaType;
default:
return kPremul_SkAlphaType;
}
}
static inline SkImageInfo
MakeSkiaImageInfo(const IntSize& aSize, SurfaceFormat aFormat)
{
return SkImageInfo::Make(aSize.width, aSize.height,
GfxFormatToSkiaColorType(aFormat),
GfxFormatToSkiaAlphaType(aFormat));
}
#ifdef USE_SKIA_GPU
static inline GrPixelConfig
GfxFormatToGrConfig(SurfaceFormat format)
{
switch (format)
{
case SurfaceFormat::B8G8R8A8:
return kBGRA_8888_GrPixelConfig;
case SurfaceFormat::B8G8R8X8:
// We probably need to do something here.
return kBGRA_8888_GrPixelConfig;
case SurfaceFormat::R5G6B5_UINT16:
return kRGB_565_GrPixelConfig;
case SurfaceFormat::A8:
return kAlpha_8_GrPixelConfig;
default:
return kRGBA_8888_GrPixelConfig;
}
}
#endif
static inline void
GfxMatrixToSkiaMatrix(const Matrix& mat, SkMatrix& retval)
{
retval.setAll(SkFloatToScalar(mat._11), SkFloatToScalar(mat._21), SkFloatToScalar(mat._31),
SkFloatToScalar(mat._12), SkFloatToScalar(mat._22), SkFloatToScalar(mat._32),
0, 0, SK_Scalar1);
}
static inline void
GfxMatrixToSkiaMatrix(const Matrix4x4& aMatrix, SkMatrix& aResult)
{
aResult.setAll(SkFloatToScalar(aMatrix._11), SkFloatToScalar(aMatrix._21), SkFloatToScalar(aMatrix._41),
SkFloatToScalar(aMatrix._12), SkFloatToScalar(aMatrix._22), SkFloatToScalar(aMatrix._42),
SkFloatToScalar(aMatrix._14), SkFloatToScalar(aMatrix._24), SkFloatToScalar(aMatrix._44));
}
static inline SkPaint::Cap
CapStyleToSkiaCap(CapStyle aCap)
{
switch (aCap)
{
case CapStyle::BUTT:
return SkPaint::kButt_Cap;
case CapStyle::ROUND:
return SkPaint::kRound_Cap;
case CapStyle::SQUARE:
return SkPaint::kSquare_Cap;
}
return SkPaint::kDefault_Cap;
}
static inline SkPaint::Join
JoinStyleToSkiaJoin(JoinStyle aJoin)
{
switch (aJoin)
{
case JoinStyle::BEVEL:
return SkPaint::kBevel_Join;
case JoinStyle::ROUND:
return SkPaint::kRound_Join;
case JoinStyle::MITER:
case JoinStyle::MITER_OR_BEVEL:
return SkPaint::kMiter_Join;
}
return SkPaint::kDefault_Join;
}
static inline bool
StrokeOptionsToPaint(SkPaint& aPaint, const StrokeOptions &aOptions)
{
// Skia renders 0 width strokes with a width of 1 (and in black),
// so we should just skip the draw call entirely.
// Skia does not handle non-finite line widths.
if (!aOptions.mLineWidth || !IsFinite(aOptions.mLineWidth)) {
return false;
}
aPaint.setStrokeWidth(SkFloatToScalar(aOptions.mLineWidth));
aPaint.setStrokeMiter(SkFloatToScalar(aOptions.mMiterLimit));
aPaint.setStrokeCap(CapStyleToSkiaCap(aOptions.mLineCap));
aPaint.setStrokeJoin(JoinStyleToSkiaJoin(aOptions.mLineJoin));
if (aOptions.mDashLength > 0) {
// Skia only supports dash arrays that are multiples of 2.
uint32_t dashCount;
if (aOptions.mDashLength % 2 == 0) {
dashCount = aOptions.mDashLength;
} else {
dashCount = aOptions.mDashLength * 2;
}
std::vector<SkScalar> pattern;
pattern.resize(dashCount);
for (uint32_t i = 0; i < dashCount; i++) {
pattern[i] = SkFloatToScalar(aOptions.mDashPattern[i % aOptions.mDashLength]);
}
sk_sp<SkPathEffect> dash = SkDashPathEffect::Make(&pattern.front(),
dashCount,
SkFloatToScalar(aOptions.mDashOffset));
aPaint.setPathEffect(dash);
}
aPaint.setStyle(SkPaint::kStroke_Style);
return true;
}
static inline SkBlendMode
GfxOpToSkiaOp(CompositionOp op)
{
switch (op)
{
case CompositionOp::OP_OVER:
return SkBlendMode::kSrcOver;
case CompositionOp::OP_ADD:
return SkBlendMode::kPlus;
case CompositionOp::OP_ATOP:
return SkBlendMode::kSrcATop;
case CompositionOp::OP_OUT:
return SkBlendMode::kSrcOut;
case CompositionOp::OP_IN:
return SkBlendMode::kSrcIn;
case CompositionOp::OP_SOURCE:
return SkBlendMode::kSrc;
case CompositionOp::OP_DEST_IN:
return SkBlendMode::kDstIn;
case CompositionOp::OP_DEST_OUT:
return SkBlendMode::kDstOut;
case CompositionOp::OP_DEST_OVER:
return SkBlendMode::kDstOver;
case CompositionOp::OP_DEST_ATOP:
return SkBlendMode::kDstATop;
case CompositionOp::OP_XOR:
return SkBlendMode::kXor;
case CompositionOp::OP_MULTIPLY:
return SkBlendMode::kMultiply;
case CompositionOp::OP_SCREEN:
return SkBlendMode::kScreen;
case CompositionOp::OP_OVERLAY:
return SkBlendMode::kOverlay;
case CompositionOp::OP_DARKEN:
return SkBlendMode::kDarken;
case CompositionOp::OP_LIGHTEN:
return SkBlendMode::kLighten;
case CompositionOp::OP_COLOR_DODGE:
return SkBlendMode::kColorDodge;
case CompositionOp::OP_COLOR_BURN:
return SkBlendMode::kColorBurn;
case CompositionOp::OP_HARD_LIGHT:
return SkBlendMode::kHardLight;
case CompositionOp::OP_SOFT_LIGHT:
return SkBlendMode::kSoftLight;
case CompositionOp::OP_DIFFERENCE:
return SkBlendMode::kDifference;
case CompositionOp::OP_EXCLUSION:
return SkBlendMode::kExclusion;
case CompositionOp::OP_HUE:
return SkBlendMode::kHue;
case CompositionOp::OP_SATURATION:
return SkBlendMode::kSaturation;
case CompositionOp::OP_COLOR:
return SkBlendMode::kColor;
case CompositionOp::OP_LUMINOSITY:
return SkBlendMode::kLuminosity;
default:
return SkBlendMode::kSrcOver;
}
}
/* There's quite a bit of inconsistency about
* whether float colors should be rounded with .5f.
* We choose to do it to match cairo which also
* happens to match the Direct3D specs */
static inline U8CPU ColorFloatToByte(Float color)
{
//XXX: do a better job converting to int
return U8CPU(color*255.f + .5f);
};
static inline SkColor ColorToSkColor(const Color &color, Float aAlpha)
{
return SkColorSetARGB(ColorFloatToByte(color.a*aAlpha), ColorFloatToByte(color.r),
ColorFloatToByte(color.g), ColorFloatToByte(color.b));
}
static inline SkPoint
PointToSkPoint(const Point &aPoint)
{
return SkPoint::Make(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y));
}
static inline SkRect
RectToSkRect(const Rect& aRect)
{
return SkRect::MakeXYWH(SkFloatToScalar(aRect.x), SkFloatToScalar(aRect.y),
SkFloatToScalar(aRect.width), SkFloatToScalar(aRect.height));
}
static inline SkRect
IntRectToSkRect(const IntRect& aRect)
{
return SkRect::MakeXYWH(SkIntToScalar(aRect.x), SkIntToScalar(aRect.y),
SkIntToScalar(aRect.width), SkIntToScalar(aRect.height));
}
static inline SkIRect
RectToSkIRect(const Rect& aRect)
{
return SkIRect::MakeXYWH(int32_t(aRect.x), int32_t(aRect.y),
int32_t(aRect.width), int32_t(aRect.height));
}
static inline SkIRect
IntRectToSkIRect(const IntRect& aRect)
{
return SkIRect::MakeXYWH(aRect.x, aRect.y, aRect.width, aRect.height);
}
static inline Point
SkPointToPoint(const SkPoint &aPoint)
{
return Point(SkScalarToFloat(aPoint.x()), SkScalarToFloat(aPoint.y()));
}
static inline Rect
SkRectToRect(const SkRect &aRect)
{
return Rect(SkScalarToFloat(aRect.x()), SkScalarToFloat(aRect.y()),
SkScalarToFloat(aRect.width()), SkScalarToFloat(aRect.height()));
}
static inline SkShader::TileMode
ExtendModeToTileMode(ExtendMode aMode, Axis aAxis)
{
switch (aMode)
{
case ExtendMode::CLAMP:
return SkShader::kClamp_TileMode;
case ExtendMode::REPEAT:
return SkShader::kRepeat_TileMode;
case ExtendMode::REFLECT:
return SkShader::kMirror_TileMode;
case ExtendMode::REPEAT_X:
{
return aAxis == Axis::X_AXIS
? SkShader::kRepeat_TileMode
: SkShader::kClamp_TileMode;
}
case ExtendMode::REPEAT_Y:
{
return aAxis == Axis::Y_AXIS
? SkShader::kRepeat_TileMode
: SkShader::kClamp_TileMode;
}
}
return SkShader::kClamp_TileMode;
}
static inline SkPaint::Hinting
GfxHintingToSkiaHinting(FontHinting aHinting)
{
switch (aHinting) {
case FontHinting::NONE:
return SkPaint::kNo_Hinting;
case FontHinting::LIGHT:
return SkPaint::kSlight_Hinting;
case FontHinting::NORMAL:
return SkPaint::kNormal_Hinting;
case FontHinting::FULL:
return SkPaint::kFull_Hinting;
}
return SkPaint::kNormal_Hinting;
}
static inline FillRule GetFillRule(SkPath::FillType aFillType)
{
switch (aFillType)
{
case SkPath::kWinding_FillType:
return FillRule::FILL_WINDING;
case SkPath::kEvenOdd_FillType:
return FillRule::FILL_EVEN_ODD;
case SkPath::kInverseWinding_FillType:
case SkPath::kInverseEvenOdd_FillType:
default:
NS_WARNING("Unsupported fill type\n");
break;
}
return FillRule::FILL_EVEN_ODD;
}
/**
* Returns true if the canvas is backed by pixels. Returns false if the canvas
* wraps an SkPDFDocument, for example.
*
* Note: It is not clear whether the test used to implement this function may
* result in it returning false in some circumstances even when the canvas
* _is_ pixel backed. In other words maybe it is possible for such a canvas to
* have kUnknown_SkPixelGeometry?
*/
static inline bool IsBackedByPixels(const SkCanvas* aCanvas)
{
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
if (!aCanvas->getProps(&props) ||
props.pixelGeometry() == kUnknown_SkPixelGeometry) {
return false;
}
return true;
}
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_HELPERSSKIA_H_ */

61
gfx/2d/HelpersWinFonts.h Normal file
View file

@ -0,0 +1,61 @@
/* -*- 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/. */
namespace mozilla {
namespace gfx {
// Cleartype can be dynamically enabled/disabled, so we have to check it
// everytime we want to render some text.
static BYTE
GetSystemTextQuality()
{
BOOL font_smoothing;
UINT smoothing_type;
if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) {
return DEFAULT_QUALITY;
}
if (font_smoothing) {
if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE,
0, &smoothing_type, 0)) {
return DEFAULT_QUALITY;
}
if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) {
return CLEARTYPE_QUALITY;
}
return ANTIALIASED_QUALITY;
}
return DEFAULT_QUALITY;
}
static AntialiasMode
GetSystemDefaultAAMode()
{
AntialiasMode defaultMode = AntialiasMode::SUBPIXEL;
if (gfxPrefs::DisableAllTextAA()) {
return AntialiasMode::NONE;
}
switch (GetSystemTextQuality()) {
case CLEARTYPE_QUALITY:
defaultMode = AntialiasMode::SUBPIXEL;
break;
case ANTIALIASED_QUALITY:
defaultMode = AntialiasMode::GRAY;
break;
case DEFAULT_QUALITY:
defaultMode = AntialiasMode::NONE;
break;
}
return defaultMode;
}
} // namespace gfx
} // namespace mozilla

251
gfx/2d/ImageScaling.cpp Normal file
View file

@ -0,0 +1,251 @@
/* -*- 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 "ImageScaling.h"
#include "2D.h"
#include "DataSurfaceHelpers.h"
#include <math.h>
#include <algorithm>
using namespace std;
namespace mozilla {
namespace gfx {
inline uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
{
// Prepare half-adder work
uint32_t sum = a ^ b ^ c;
uint32_t carry = (a & b) | (a & c) | (b & c);
// Before shifting, mask lower order bits of each byte to avoid underflow.
uint32_t mask = 0xfefefefe;
// Add d to sum and divide by 2.
sum = (((sum ^ d) & mask) >> 1) + (sum & d);
// Sum is now shifted into place relative to carry, add them together.
return (((sum ^ carry) & mask) >> 1) + (sum & carry);
}
inline uint32_t Avg2(uint32_t a, uint32_t b)
{
// Prepare half-adder work
uint32_t sum = a ^ b;
uint32_t carry = (a & b);
// Before shifting, mask lower order bits of each byte to avoid underflow.
uint32_t mask = 0xfefefefe;
// Add d to sum and divide by 2.
return ((sum & mask) >> 1) + carry;
}
void
ImageHalfScaler::ScaleForSize(const IntSize &aSize)
{
uint32_t horizontalDownscales = 0;
uint32_t verticalDownscales = 0;
IntSize scaleSize = mOrigSize;
while ((scaleSize.height / 2) > aSize.height) {
verticalDownscales++;
scaleSize.height /= 2;
}
while ((scaleSize.width / 2) > aSize.width) {
horizontalDownscales++;
scaleSize.width /= 2;
}
if (scaleSize == mOrigSize) {
return;
}
delete [] mDataStorage;
IntSize internalSurfSize;
internalSurfSize.width = max(scaleSize.width, mOrigSize.width / 2);
internalSurfSize.height = max(scaleSize.height, mOrigSize.height / 2);
size_t bufLen = 0;
mStride = GetAlignedStride<16>(internalSurfSize.width, 4);
if (mStride > 0) {
// Allocate 15 bytes extra to make sure we can get 16 byte alignment. We
// should add tools for this, see bug 751696.
bufLen = BufferSizeFromStrideAndHeight(mStride, internalSurfSize.height, 15);
}
if (bufLen == 0) {
mSize.SizeTo(0, 0);
mDataStorage = nullptr;
return;
}
mDataStorage = new uint8_t[bufLen];
if (uintptr_t(mDataStorage) % 16) {
// Our storage does not start at a 16-byte boundary. Make sure mData does!
mData = (uint8_t*)(uintptr_t(mDataStorage) +
(16 - (uintptr_t(mDataStorage) % 16)));
} else {
mData = mDataStorage;
}
mSize = scaleSize;
/* The surface we sample from might not be even sized, if it's not we will
* ignore the last row/column. This means we lose some data but it keeps the
* code very simple. There's also no perfect answer that provides a better
* solution.
*/
IntSize currentSampledSize = mOrigSize;
uint32_t currentSampledStride = mOrigStride;
uint8_t *currentSampledData = mOrigData;
while (verticalDownscales && horizontalDownscales) {
if (currentSampledSize.width % 2) {
currentSampledSize.width -= 1;
}
if (currentSampledSize.height % 2) {
currentSampledSize.height -= 1;
}
HalfImage2D(currentSampledData, currentSampledStride, currentSampledSize,
mData, mStride);
verticalDownscales--;
horizontalDownscales--;
currentSampledSize.width /= 2;
currentSampledSize.height /= 2;
currentSampledData = mData;
currentSampledStride = mStride;
}
while (verticalDownscales) {
if (currentSampledSize.height % 2) {
currentSampledSize.height -= 1;
}
HalfImageVertical(currentSampledData, currentSampledStride, currentSampledSize,
mData, mStride);
verticalDownscales--;
currentSampledSize.height /= 2;
currentSampledData = mData;
currentSampledStride = mStride;
}
while (horizontalDownscales) {
if (currentSampledSize.width % 2) {
currentSampledSize.width -= 1;
}
HalfImageHorizontal(currentSampledData, currentSampledStride, currentSampledSize,
mData, mStride);
horizontalDownscales--;
currentSampledSize.width /= 2;
currentSampledData = mData;
currentSampledStride = mStride;
}
}
void
ImageHalfScaler::HalfImage2D(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
#ifdef USE_SSE2
if (Factory::HasSSE2()) {
HalfImage2D_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride);
} else
#endif
{
HalfImage2D_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride);
}
}
void
ImageHalfScaler::HalfImageVertical(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
#ifdef USE_SSE2
if (Factory::HasSSE2()) {
HalfImageVertical_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride);
} else
#endif
{
HalfImageVertical_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride);
}
}
void
ImageHalfScaler::HalfImageHorizontal(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
#ifdef USE_SSE2
if (Factory::HasSSE2()) {
HalfImageHorizontal_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride);
} else
#endif
{
HalfImageHorizontal_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride);
}
}
void
ImageHalfScaler::HalfImage2D_C(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
for (int y = 0; y < aSourceSize.height; y += 2) {
uint32_t *storage = (uint32_t*)(aDest + (y / 2) * aDestStride);
for (int x = 0; x < aSourceSize.width; x += 2) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * 4);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4);
*storage++ = Avg2x2(*(uint32_t*)upperRow, *((uint32_t*)upperRow + 1),
*(uint32_t*)lowerRow, *((uint32_t*)lowerRow + 1));
}
}
}
void
ImageHalfScaler::HalfImageVertical_C(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
for (int y = 0; y < aSourceSize.height; y += 2) {
uint32_t *storage = (uint32_t*)(aDest + (y / 2) * aDestStride);
for (int x = 0; x < aSourceSize.width; x++) {
uint32_t *upperRow = (uint32_t*)(aSource + (y * aSourceStride + x * 4));
uint32_t *lowerRow = (uint32_t*)(aSource + ((y + 1) * aSourceStride + x * 4));
*storage++ = Avg2(*upperRow, *lowerRow);
}
}
}
void
ImageHalfScaler::HalfImageHorizontal_C(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
for (int y = 0; y < aSourceSize.height; y++) {
uint32_t *storage = (uint32_t*)(aDest + y * aDestStride);
for (int x = 0; x < aSourceSize.width; x+= 2) {
uint32_t *pixels = (uint32_t*)(aSource + (y * aSourceStride + x * 4));
*storage++ = Avg2(*pixels, *(pixels + 1));
}
}
}
} // namespace gfx
} // namespace mozilla

76
gfx/2d/ImageScaling.h Normal file
View file

@ -0,0 +1,76 @@
/* -*- 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/. */
#ifndef _MOZILLA_GFX_IMAGESCALING_H
#define _MOZILLA_GFX_IMAGESCALING_H
#include "Types.h"
#include <vector>
#include "Point.h"
namespace mozilla {
namespace gfx {
class ImageHalfScaler
{
public:
ImageHalfScaler(uint8_t *aData, int32_t aStride, const IntSize &aSize)
: mOrigData(aData), mOrigStride(aStride), mOrigSize(aSize)
, mDataStorage(nullptr)
{
}
~ImageHalfScaler()
{
delete [] mDataStorage;
}
void ScaleForSize(const IntSize &aSize);
uint8_t *GetScaledData() const { return mData; }
IntSize GetSize() const { return mSize; }
uint32_t GetStride() const { return mStride; }
private:
void HalfImage2D(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImageVertical(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImageHorizontal(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
// This is our SSE2 scaling function. Our destination must always be 16-byte
// aligned and use a 16-byte aligned stride.
void HalfImage2D_SSE2(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImageVertical_SSE2(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImageHorizontal_SSE2(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImage2D_C(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImageVertical_C(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
void HalfImageHorizontal_C(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize,
uint8_t *aDest, uint32_t aDestStride);
uint8_t *mOrigData;
int32_t mOrigStride;
IntSize mOrigSize;
uint8_t *mDataStorage;
// Guaranteed 16-byte aligned
uint8_t *mData;
IntSize mSize;
// Guaranteed 16-byte aligned
uint32_t mStride;
};
} // namespace gfx
} // namespace mozilla
#endif

330
gfx/2d/ImageScalingSSE2.cpp Normal file
View file

@ -0,0 +1,330 @@
/* -*- 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 "ImageScaling.h"
#include "mozilla/Attributes.h"
#include "SSEHelpers.h"
/* The functions below use the following system for averaging 4 pixels:
*
* The first observation is that a half-adder is implemented as follows:
* R = S + 2C or in the case of a and b (a ^ b) + ((a & b) << 1);
*
* This can be trivially extended to three pixels by observaring that when
* doing (a ^ b ^ c) as the sum, the carry is simply the bitwise-or of the
* carries of the individual numbers, since the sum of 3 bits can only ever
* have a carry of one.
*
* We then observe that the average is then ((carry << 1) + sum) >> 1, or,
* assuming eliminating overflows and underflows, carry + (sum >> 1).
*
* We now average our existing sum with the fourth number, so we get:
* sum2 = (sum + d) >> 1 or (sum >> 1) + (d >> 1).
*
* We now observe that our sum has been moved into place relative to the
* carry, so we can now average with the carry to get the final 4 input
* average: avg = (sum2 + carry) >> 1;
*
* Or to reverse the proof:
* avg = ((sum >> 1) + carry + d >> 1) >> 1
* avg = ((a + b + c) >> 1 + d >> 1) >> 1
* avg = ((a + b + c + d) >> 2)
*
* An additional fact used in the SSE versions is the concept that we can
* trivially convert a rounded average to a truncated average:
*
* We have:
* f(a, b) = (a + b + 1) >> 1
*
* And want:
* g(a, b) = (a + b) >> 1
*
* Observe:
* ~f(~a, ~b) == ~((~a + ~b + 1) >> 1)
* == ~((-a - 1 + -b - 1 + 1) >> 1)
* == ~((-a - 1 + -b) >> 1)
* == ~((-(a + b) - 1) >> 1)
* == ~((~(a + b)) >> 1)
* == (a + b) >> 1
* == g(a, b)
*/
MOZ_ALWAYS_INLINE __m128i _mm_not_si128(__m128i arg)
{
__m128i minusone = _mm_set1_epi32(0xffffffff);
return _mm_xor_si128(arg, minusone);
}
/* We have to pass pointers here, MSVC does not allow passing more than 3
* __m128i arguments on the stack. And it does not allow 16-byte aligned
* stack variables. This inlines properly on MSVC 2010. It does -not- inline
* with just the inline directive.
*/
MOZ_ALWAYS_INLINE __m128i avg_sse2_8x2(__m128i *a, __m128i *b, __m128i *c, __m128i *d)
{
#define shuf1 _MM_SHUFFLE(2, 0, 2, 0)
#define shuf2 _MM_SHUFFLE(3, 1, 3, 1)
// This cannot be an inline function as the __Imm argument to _mm_shuffle_ps
// needs to be a compile time constant.
#define shuffle_si128(arga, argb, imm) \
_mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps((arga)), _mm_castsi128_ps((argb)), (imm)));
__m128i t = shuffle_si128(*a, *b, shuf1);
*b = shuffle_si128(*a, *b, shuf2);
*a = t;
t = shuffle_si128(*c, *d, shuf1);
*d = shuffle_si128(*c, *d, shuf2);
*c = t;
#undef shuf1
#undef shuf2
#undef shuffle_si128
__m128i sum = _mm_xor_si128(*a, _mm_xor_si128(*b, *c));
__m128i carry = _mm_or_si128(_mm_and_si128(*a, *b), _mm_or_si128(_mm_and_si128(*a, *c), _mm_and_si128(*b, *c)));
sum = _mm_avg_epu8(_mm_not_si128(sum), _mm_not_si128(*d));
return _mm_not_si128(_mm_avg_epu8(sum, _mm_not_si128(carry)));
}
MOZ_ALWAYS_INLINE __m128i avg_sse2_4x2_4x1(__m128i a, __m128i b)
{
return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b)));
}
MOZ_ALWAYS_INLINE __m128i avg_sse2_8x1_4x1(__m128i a, __m128i b)
{
__m128i t = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(3, 1, 3, 1)));
b = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(2, 0, 2, 0)));
a = t;
return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b)));
}
MOZ_ALWAYS_INLINE uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
{
uint32_t sum = a ^ b ^ c;
uint32_t carry = (a & b) | (a & c) | (b & c);
uint32_t mask = 0xfefefefe;
// Not having a byte based average instruction means we should mask to avoid
// underflow.
sum = (((sum ^ d) & mask) >> 1) + (sum & d);
return (((sum ^ carry) & mask) >> 1) + (sum & carry);
}
// Simple 2 pixel average version of the function above.
MOZ_ALWAYS_INLINE uint32_t Avg2(uint32_t a, uint32_t b)
{
uint32_t sum = a ^ b;
uint32_t carry = (a & b);
uint32_t mask = 0xfefefefe;
return ((sum & mask) >> 1) + carry;
}
namespace mozilla {
namespace gfx {
void
ImageHalfScaler::HalfImage2D_SSE2(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
const int Bpp = 4;
for (int y = 0; y < aSourceSize.height; y += 2) {
__m128i *storage = (__m128i*)(aDest + (y / 2) * aDestStride);
int x = 0;
// Run a loop depending on alignment.
if (!(uintptr_t(aSource + (y * aSourceStride)) % 16) &&
!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) {
for (; x < (aSourceSize.width - 7); x += 8) {
__m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp));
__m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp));
__m128i a = _mm_load_si128(upperRow);
__m128i b = _mm_load_si128(upperRow + 1);
__m128i c = _mm_load_si128(lowerRow);
__m128i d = _mm_load_si128(lowerRow + 1);
*storage++ = avg_sse2_8x2(&a, &b, &c, &d);
}
} else if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) {
for (; x < (aSourceSize.width - 7); x += 8) {
__m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp));
__m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp));
__m128i a = _mm_load_si128(upperRow);
__m128i b = _mm_load_si128(upperRow + 1);
__m128i c = loadUnaligned128(lowerRow);
__m128i d = loadUnaligned128(lowerRow + 1);
*storage++ = avg_sse2_8x2(&a, &b, &c, &d);
}
} else if (!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) {
for (; x < (aSourceSize.width - 7); x += 8) {
__m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp));
__m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp));
__m128i a = loadUnaligned128((__m128i*)upperRow);
__m128i b = loadUnaligned128((__m128i*)upperRow + 1);
__m128i c = _mm_load_si128((__m128i*)lowerRow);
__m128i d = _mm_load_si128((__m128i*)lowerRow + 1);
*storage++ = avg_sse2_8x2(&a, &b, &c, &d);
}
} else {
for (; x < (aSourceSize.width - 7); x += 8) {
__m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp));
__m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp));
__m128i a = loadUnaligned128(upperRow);
__m128i b = loadUnaligned128(upperRow + 1);
__m128i c = loadUnaligned128(lowerRow);
__m128i d = loadUnaligned128(lowerRow + 1);
*storage++ = avg_sse2_8x2(&a, &b, &c, &d);
}
}
uint32_t *unalignedStorage = (uint32_t*)storage;
// Take care of the final pixels, we know there's an even number of pixels
// in the source rectangle. We use a 2x2 'simd' implementation for this.
//
// Potentially we only have to do this in the last row since overflowing
// 8 pixels in an earlier row would appear to be harmless as it doesn't
// touch invalid memory. Even when reading and writing to the same surface.
// in practice we only do this when doing an additional downscale pass, and
// in this situation we have unused stride to write into harmlessly.
// I do not believe the additional code complexity would be worth it though.
for (; x < aSourceSize.width; x += 2) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * Bpp);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * Bpp);
*unalignedStorage++ = Avg2x2(*(uint32_t*)upperRow, *((uint32_t*)upperRow + 1),
*(uint32_t*)lowerRow, *((uint32_t*)lowerRow + 1));
}
}
}
void
ImageHalfScaler::HalfImageVertical_SSE2(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
for (int y = 0; y < aSourceSize.height; y += 2) {
__m128i *storage = (__m128i*)(aDest + (y / 2) * aDestStride);
int x = 0;
// Run a loop depending on alignment.
if (!(uintptr_t(aSource + (y * aSourceStride)) % 16) &&
!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) {
for (; x < (aSourceSize.width - 3); x += 4) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * 4);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4);
__m128i a = _mm_load_si128((__m128i*)upperRow);
__m128i b = _mm_load_si128((__m128i*)lowerRow);
*storage++ = avg_sse2_4x2_4x1(a, b);
}
} else if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) {
// This line doesn't align well.
for (; x < (aSourceSize.width - 3); x += 4) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * 4);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4);
__m128i a = _mm_load_si128((__m128i*)upperRow);
__m128i b = loadUnaligned128((__m128i*)lowerRow);
*storage++ = avg_sse2_4x2_4x1(a, b);
}
} else if (!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) {
for (; x < (aSourceSize.width - 3); x += 4) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * 4);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4);
__m128i a = loadUnaligned128((__m128i*)upperRow);
__m128i b = _mm_load_si128((__m128i*)lowerRow);
*storage++ = avg_sse2_4x2_4x1(a, b);
}
} else {
for (; x < (aSourceSize.width - 3); x += 4) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * 4);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4);
__m128i a = loadUnaligned128((__m128i*)upperRow);
__m128i b = loadUnaligned128((__m128i*)lowerRow);
*storage++ = avg_sse2_4x2_4x1(a, b);
}
}
uint32_t *unalignedStorage = (uint32_t*)storage;
// Take care of the final pixels, we know there's an even number of pixels
// in the source rectangle.
//
// Similar overflow considerations are valid as in the previous function.
for (; x < aSourceSize.width; x++) {
uint8_t *upperRow = aSource + (y * aSourceStride + x * 4);
uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4);
*unalignedStorage++ = Avg2(*(uint32_t*)upperRow, *(uint32_t*)lowerRow);
}
}
}
void
ImageHalfScaler::HalfImageHorizontal_SSE2(uint8_t *aSource, int32_t aSourceStride,
const IntSize &aSourceSize, uint8_t *aDest,
uint32_t aDestStride)
{
for (int y = 0; y < aSourceSize.height; y++) {
__m128i *storage = (__m128i*)(aDest + (y * aDestStride));
int x = 0;
// Run a loop depending on alignment.
if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) {
for (; x < (aSourceSize.width - 7); x += 8) {
__m128i* pixels = (__m128i*)(aSource + (y * aSourceStride + x * 4));
__m128i a = _mm_load_si128(pixels);
__m128i b = _mm_load_si128(pixels + 1);
*storage++ = avg_sse2_8x1_4x1(a, b);
}
} else {
for (; x < (aSourceSize.width - 7); x += 8) {
__m128i* pixels = (__m128i*)(aSource + (y * aSourceStride + x * 4));
__m128i a = loadUnaligned128(pixels);
__m128i b = loadUnaligned128(pixels + 1);
*storage++ = avg_sse2_8x1_4x1(a, b);
}
}
uint32_t *unalignedStorage = (uint32_t*)storage;
// Take care of the final pixels, we know there's an even number of pixels
// in the source rectangle.
//
// Similar overflow considerations are valid as in the previous function.
for (; x < aSourceSize.width; x += 2) {
uint32_t *pixels = (uint32_t*)(aSource + (y * aSourceStride + x * 4));
*unalignedStorage++ = Avg2(*pixels, *(pixels + 1));
}
}
}
} // namespace gfx
} // namespace mozilla

193
gfx/2d/IterableArena.h Normal file
View file

@ -0,0 +1,193 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_ITERABLEARENA_H_
#define MOZILLA_GFX_ITERABLEARENA_H_
#include "mozilla/Move.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/Logging.h"
#include <string.h>
#include <vector>
#include <stdint.h>
#include <stdio.h>
namespace mozilla {
namespace gfx {
/// A simple pool allocator for plain data structures.
///
/// Beware that the pool will not attempt to run the destructors. It is the
/// responsibility of the user of this class to either use objects with no
/// destructor or to manually call the allocated objects destructors.
/// If the pool is growable, its allocated objects must be safely moveable in
/// in memory (through memcpy).
class IterableArena {
protected:
struct Header
{
size_t mBlocSize;
};
public:
enum ArenaType {
FIXED_SIZE,
GROWABLE
};
IterableArena(ArenaType aType, size_t aStorageSize)
: mSize(aStorageSize)
, mCursor(0)
, mIsGrowable(aType == GROWABLE)
{
if (mSize == 0) {
mSize = 128;
}
mStorage = (uint8_t*)malloc(mSize);
if (mStorage == nullptr) {
gfxCriticalError() << "Not enough Memory allocate a memory pool of size " << aStorageSize;
MOZ_CRASH("GFX: Out of memory IterableArena");
}
}
~IterableArena()
{
free(mStorage);
}
/// Constructs a new item in the pool and returns a positive offset in case of
/// success.
///
/// The offset never changes even if the storage is reallocated, so users
/// of this class should prefer storing offsets rather than direct pointers
/// to the allocated objects.
/// Alloc can cause the storage to be reallocated if the pool was initialized
/// with IterableArena::GROWABLE.
/// If for any reason the pool fails to allocate enough space for the new item
/// Alloc returns a negative offset and the object's constructor is not called.
template<typename T, typename... Args>
ptrdiff_t
Alloc(Args&&... aArgs)
{
void* storage = nullptr;
auto offset = AllocRaw(sizeof(T), &storage);
if (offset < 0) {
return offset;
}
new (storage) T(Forward<Args>(aArgs)...);
return offset;
}
ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr)
{
const size_t blocSize = AlignedSize(sizeof(Header) + aSize);
if (AlignedSize(mCursor + blocSize) > mSize) {
if (!mIsGrowable) {
return -1;
}
size_t newSize = mSize * 2;
while (AlignedSize(mCursor + blocSize) > newSize) {
newSize *= 2;
}
uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize);
if (!newStorage) {
gfxCriticalError() << "Not enough Memory to grow the memory pool, size: " << newSize;
return -1;
}
mStorage = newStorage;
mSize = newSize;
}
ptrdiff_t offset = mCursor;
GetHeader(offset)->mBlocSize = blocSize;
mCursor += blocSize;
if (aOutPtr) {
*aOutPtr = GetStorage(offset);
}
return offset;
}
/// Get access to an allocated item at a given offset (only use offsets returned
/// by Alloc or AllocRaw).
///
/// If the pool is growable, the returned pointer is only valid temporarily. The
/// underlying storage can be reallocated in Alloc or AllocRaw, so do not keep
/// these pointers around and store the offset instead.
void* GetStorage(ptrdiff_t offset = 0)
{
MOZ_ASSERT(offset >= 0);
MOZ_ASSERT(offset < mCursor);
return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr;
}
/// Clears the storage without running any destructor and without deallocating it.
void Clear()
{
mCursor = 0;
}
/// Iterate over the elements allocated in this pool.
///
/// Takes a lambda or function object accepting a void* as parameter.
template<typename Func>
void ForEach(Func cb)
{
Iterator it;
while (void* ptr = it.Next(this)) {
cb(ptr);
}
}
/// A simple iterator over an arena.
class Iterator {
public:
Iterator()
: mCursor(0)
{}
void* Next(IterableArena* aArena)
{
if (mCursor >= aArena->mCursor) {
return nullptr;
}
void* result = aArena->GetStorage(mCursor);
const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize;
MOZ_ASSERT(blocSize != 0);
mCursor += blocSize;
return result;
}
private:
ptrdiff_t mCursor;
};
protected:
Header* GetHeader(ptrdiff_t offset)
{
return (Header*) (mStorage + offset);
}
size_t AlignedSize(size_t aSize) const
{
const size_t alignment = sizeof(uintptr_t);
return aSize + (alignment - (aSize % alignment)) % alignment;
}
uint8_t* mStorage;
uint32_t mSize;
ptrdiff_t mCursor;
bool mIsGrowable;
friend class Iterator;
};
} // namespace
} // namespace
#endif

288
gfx/2d/JobScheduler.cpp Normal file
View file

@ -0,0 +1,288 @@
/* -*- 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 "JobScheduler.h"
#include "Logging.h"
namespace mozilla {
namespace gfx {
JobScheduler* JobScheduler::sSingleton = nullptr;
bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues)
{
MOZ_ASSERT(!sSingleton);
MOZ_ASSERT(aNumThreads >= aNumQueues);
sSingleton = new JobScheduler();
sSingleton->mNextQueue = 0;
for (uint32_t i = 0; i < aNumQueues; ++i) {
sSingleton->mDrawingQueues.push_back(new MultiThreadedJobQueue());
}
for (uint32_t i = 0; i < aNumThreads; ++i) {
sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues]));
}
return true;
}
void JobScheduler::ShutDown()
{
MOZ_ASSERT(IsEnabled());
if (!IsEnabled()) {
return;
}
for (auto queue : sSingleton->mDrawingQueues) {
queue->ShutDown();
delete queue;
}
for (WorkerThread* thread : sSingleton->mWorkerThreads) {
// this will block until the thread is joined.
delete thread;
}
sSingleton->mWorkerThreads.clear();
delete sSingleton;
sSingleton = nullptr;
}
JobStatus
JobScheduler::ProcessJob(Job* aJob)
{
MOZ_ASSERT(aJob);
auto status = aJob->Run();
if (status == JobStatus::Error || status == JobStatus::Complete) {
delete aJob;
}
return status;
}
void
JobScheduler::SubmitJob(Job* aJob)
{
MOZ_ASSERT(aJob);
RefPtr<SyncObject> start = aJob->GetStartSync();
if (start && start->Register(aJob)) {
// The Job buffer starts with a non-signaled sync object, it
// is now registered in the list of task buffers waiting on the
// sync object, so we should not place it in the queue.
return;
}
GetQueueForJob(aJob)->SubmitJob(aJob);
}
void
JobScheduler::Join(SyncObject* aCompletion)
{
RefPtr<EventObject> waitForCompletion = new EventObject();
JobScheduler::SubmitJob(new SetEventJob(waitForCompletion, aCompletion));
waitForCompletion->Wait();
}
MultiThreadedJobQueue*
JobScheduler::GetQueueForJob(Job* aJob)
{
return aJob->IsPinnedToAThread() ? aJob->GetWorkerThread()->GetJobQueue()
: GetDrawingQueue();
}
Job::Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread)
: mNextWaitingJob(nullptr)
, mStartSync(aStart)
, mCompletionSync(aCompletion)
, mPinToThread(aThread)
{
if (mStartSync) {
mStartSync->AddSubsequent(this);
}
if (mCompletionSync) {
mCompletionSync->AddPrerequisite(this);
}
}
Job::~Job()
{
if (mCompletionSync) {
//printf(" -- Job %p dtor completion %p\n", this, mCompletionSync);
mCompletionSync->Signal();
mCompletionSync = nullptr;
}
}
JobStatus
SetEventJob::Run()
{
mEvent->Set();
return JobStatus::Complete;
}
SetEventJob::SetEventJob(EventObject* aEvent,
SyncObject* aStart, SyncObject* aCompletion,
WorkerThread* aWorker)
: Job(aStart, aCompletion, aWorker)
, mEvent(aEvent)
{}
SetEventJob::~SetEventJob()
{}
SyncObject::SyncObject(uint32_t aNumPrerequisites)
: mSignals(aNumPrerequisites)
, mFirstWaitingJob(nullptr)
#ifdef DEBUG
, mNumPrerequisites(aNumPrerequisites)
, mAddedPrerequisites(0)
#endif
{}
SyncObject::~SyncObject()
{
MOZ_ASSERT(mFirstWaitingJob == nullptr);
}
bool
SyncObject::Register(Job* aJob)
{
MOZ_ASSERT(aJob);
// For now, ensure that when we schedule the first subsequent, we have already
// created all of the prerequisites. This is an arbitrary restriction because
// we specify the number of prerequisites in the constructor, but in the typical
// scenario, if the assertion FreezePrerequisite blows up here it probably means
// we got the initial nmber of prerequisites wrong. We can decide to remove
// this restriction if needed.
FreezePrerequisites();
int32_t signals = mSignals;
if (signals > 0) {
AddWaitingJob(aJob);
// Since Register and Signal can be called concurrently, it can happen that
// reading mSignals in Register happens before decrementing mSignals in Signal,
// but SubmitWaitingJobs happens before AddWaitingJob. This ordering means
// the SyncObject ends up in the signaled state with a task sitting in the
// waiting list. To prevent that we check mSignals a second time and submit
// again if signals reached zero in the mean time.
// We do this instead of holding a mutex around mSignals+mJobs to reduce
// lock contention.
int32_t signals2 = mSignals;
if (signals2 == 0) {
SubmitWaitingJobs();
}
return true;
}
return false;
}
void
SyncObject::Signal()
{
int32_t signals = --mSignals;
MOZ_ASSERT(signals >= 0);
if (signals == 0) {
SubmitWaitingJobs();
}
}
void
SyncObject::AddWaitingJob(Job* aJob)
{
// Push (using atomics) the task into the list of waiting tasks.
for (;;) {
Job* first = mFirstWaitingJob;
aJob->mNextWaitingJob = first;
if (mFirstWaitingJob.compareExchange(first, aJob)) {
break;
}
}
}
void SyncObject::SubmitWaitingJobs()
{
// Scheduling the tasks can cause code that modifies <this>'s reference
// count to run concurrently, and cause the caller of this function to
// be owned by another thread. We need to make sure the reference count
// does not reach 0 on another thread before the end of this method, so
// hold a strong ref to prevent that!
RefPtr<SyncObject> kungFuDeathGrip(this);
// First atomically swap mFirstWaitingJob and waitingJobs...
Job* waitingJobs = nullptr;
for (;;) {
waitingJobs = mFirstWaitingJob;
if (mFirstWaitingJob.compareExchange(waitingJobs, nullptr)) {
break;
}
}
// ... and submit all of the waiting tasks in waitingJob now that they belong
// to this thread.
while (waitingJobs) {
Job* next = waitingJobs->mNextWaitingJob;
waitingJobs->mNextWaitingJob = nullptr;
JobScheduler::GetQueueForJob(waitingJobs)->SubmitJob(waitingJobs);
waitingJobs = next;
}
}
bool
SyncObject::IsSignaled()
{
return mSignals == 0;
}
void
SyncObject::FreezePrerequisites()
{
MOZ_ASSERT(mAddedPrerequisites == mNumPrerequisites);
}
void
SyncObject::AddPrerequisite(Job* aJob)
{
MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites);
}
void
SyncObject::AddSubsequent(Job* aJob)
{
}
WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue)
: mQueue(aJobQueue)
{
aJobQueue->RegisterThread();
}
void
WorkerThread::Run()
{
SetName("gfx worker");
for (;;) {
Job* commands = nullptr;
if (!mQueue->WaitForJob(commands)) {
mQueue->UnregisterThread();
return;
}
JobStatus status = JobScheduler::ProcessJob(commands);
if (status == JobStatus::Error) {
// Don't try to handle errors for now, but that's open to discussions.
// I expect errors to be mostly OOM issues.
gfxDevCrash(LogReason::JobStatusError) << "Invalid job status " << (int)status;
}
}
}
} //namespace
} //namespace

257
gfx/2d/JobScheduler.h Normal file
View file

@ -0,0 +1,257 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_TASKSCHEDULER_H_
#define MOZILLA_GFX_TASKSCHEDULER_H_
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/RefCounted.h"
#ifdef WIN32
#include "mozilla/gfx/JobScheduler_win32.h"
#else
#include "mozilla/gfx/JobScheduler_posix.h"
#endif
#include <vector>
namespace mozilla {
namespace gfx {
class MultiThreadedJobQueue;
class SyncObject;
class WorkerThread;
class JobScheduler {
public:
/// Return one of the queues that the drawing worker threads pull from, chosen
/// pseudo-randomly.
static MultiThreadedJobQueue* GetDrawingQueue()
{
return sSingleton->mDrawingQueues[
sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size()
];
}
/// Return one of the queues that the drawing worker threads pull from with a
/// hash to choose the queue.
///
/// Calling this function several times with the same hash will yield the same queue.
static MultiThreadedJobQueue* GetDrawingQueue(uint32_t aHash)
{
return sSingleton->mDrawingQueues[
aHash % sSingleton->mDrawingQueues.size()
];
}
/// Return the task queue associated to the worker the task is pinned to if
/// the task is pinned to a worker, or a random queue.
static MultiThreadedJobQueue* GetQueueForJob(Job* aJob);
/// Initialize the task scheduler with aNumThreads worker threads for drawing
/// and aNumQueues task queues.
///
/// The number of threads must be superior or equal to the number of queues
/// (since for now a worker thread only pulls from one queue).
static bool Init(uint32_t aNumThreads, uint32_t aNumQueues);
/// Shut the scheduler down.
///
/// This will block until worker threads are joined and deleted.
static void ShutDown();
/// Returns true if there is a successfully initialized JobScheduler singleton.
static bool IsEnabled() { return !!sSingleton; }
/// Submit a task buffer to its associated queue.
///
/// The caller looses ownership of the task buffer.
static void SubmitJob(Job* aJobs);
/// Convenience function to block the current thread until a given SyncObject
/// is in the signaled state.
///
/// The current thread will first try to steal jobs before blocking.
static void Join(SyncObject* aCompletionSync);
/// Process commands until the command buffer needs to block on a sync object,
/// completes, yields, or encounters an error.
///
/// Can be used on any thread. Worker threads basically loop over this, but the
/// main thread can also dequeue pending task buffers and process them alongside
/// the worker threads if it is about to block until completion anyway.
///
/// The caller looses ownership of the task buffer.
static JobStatus ProcessJob(Job* aJobs);
protected:
static JobScheduler* sSingleton;
// queues of Job that are ready to be processed
std::vector<MultiThreadedJobQueue*> mDrawingQueues;
std::vector<WorkerThread*> mWorkerThreads;
Atomic<uint32_t> mNextQueue;
};
/// Jobs are not reference-counted because they don't have shared ownership.
/// The ownership of tasks can change when they are passed to certain methods
/// of JobScheduler and SyncObject. See the docuumentaion of these classes.
class Job {
public:
Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread = nullptr);
virtual ~Job();
virtual JobStatus Run() = 0;
/// For use in JobScheduler::SubmitJob. Don't use it anywhere else.
//already_AddRefed<SyncObject> GetAndResetStartSync();
SyncObject* GetStartSync() { return mStartSync; }
bool IsPinnedToAThread() const { return !!mPinToThread; }
WorkerThread* GetWorkerThread() { return mPinToThread; }
protected:
// An intrusive linked list of tasks waiting for a sync object to enter the
// signaled state. When the task is not waiting for a sync object, mNextWaitingJob
// should be null. This is only accessed from the thread that owns the task.
Job* mNextWaitingJob;
RefPtr<SyncObject> mStartSync;
RefPtr<SyncObject> mCompletionSync;
WorkerThread* mPinToThread;
friend class SyncObject;
};
class EventObject;
/// This task will set an EventObject.
///
/// Typically used as the final task, so that the main thread can block on the
/// corresponfing EventObject until all of the tasks are processed.
class SetEventJob : public Job
{
public:
explicit SetEventJob(EventObject* aEvent,
SyncObject* aStart, SyncObject* aCompletion = nullptr,
WorkerThread* aPinToWorker = nullptr);
~SetEventJob();
JobStatus Run() override;
EventObject* GetEvent() { return mEvent; }
protected:
RefPtr<EventObject> mEvent;
};
/// A synchronization object that can be used to express dependencies and ordering between
/// tasks.
///
/// Jobs can register to SyncObjects in order to asynchronously wait for a signal.
/// In practice, Job objects usually start with a sync object (startSyc) and end
/// with another one (completionSync).
/// a Job never gets processed before its startSync is in the signaled state, and
/// signals its completionSync as soon as it finishes. This is how dependencies
/// between tasks is expressed.
class SyncObject final : public external::AtomicRefCounted<SyncObject> {
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject)
/// Create a synchronization object.
///
/// aNumPrerequisites represents the number of times the object must be signaled
/// before actually entering the signaled state (in other words, it means the
/// number of dependencies of this sync object).
///
/// Explicitly specifying the number of prerequisites when creating sync objects
/// makes it easy to start scheduling some of the prerequisite tasks while
/// creating the others, which is how we typically use the task scheduler.
/// Automatically determining the number of prerequisites using Job's constructor
/// brings the risk that the sync object enters the signaled state while we
/// are still adding prerequisites which is hard to fix without using muteces.
explicit SyncObject(uint32_t aNumPrerequisites = 1);
~SyncObject();
/// Attempt to register a task.
///
/// If the sync object is already in the signaled state, the buffer is *not*
/// registered and the sync object does not take ownership of the task.
/// If the object is not yet in the signaled state, it takes ownership of
/// the task and places it in a list of pending tasks.
/// Pending tasks will not be processed by the worker thread.
/// When the SyncObject reaches the signaled state, it places the pending
/// tasks back in the available buffer queue, so that they can be
/// scheduled again.
///
/// Returns true if the SyncOject is not already in the signaled state.
/// This means that if this method returns true, the SyncObject has taken
/// ownership of the Job.
bool Register(Job* aJob);
/// Signal the SyncObject.
///
/// This decrements an internal counter. The sync object reaches the signaled
/// state when the counter gets to zero.
void Signal();
/// Returns true if mSignals is equal to zero. In other words, returns true
/// if all prerequisite tasks have already signaled the sync object.
bool IsSignaled();
/// Asserts that the number of added prerequisites is equal to the number
/// specified in the constructor (does nothin in release builds).
void FreezePrerequisites();
private:
// Called by Job's constructor
void AddSubsequent(Job* aJob);
void AddPrerequisite(Job* aJob);
void AddWaitingJob(Job* aJob);
void SubmitWaitingJobs();
Atomic<int32_t> mSignals;
Atomic<Job*> mFirstWaitingJob;
#ifdef DEBUG
uint32_t mNumPrerequisites;
Atomic<uint32_t> mAddedPrerequisites;
#endif
friend class Job;
friend class JobScheduler;
};
/// Base class for worker threads.
class WorkerThread
{
public:
static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue);
virtual ~WorkerThread() {}
void Run();
MultiThreadedJobQueue* GetJobQueue() { return mQueue; }
protected:
explicit WorkerThread(MultiThreadedJobQueue* aJobQueue);
virtual void SetName(const char* aName) {}
MultiThreadedJobQueue* mQueue;
};
} // namespace
} // namespace
#endif

View file

@ -0,0 +1,194 @@
/* -*- 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 "JobScheduler.h"
#include "mozilla/gfx/Logging.h"
using namespace std;
namespace mozilla {
namespace gfx {
void* ThreadCallback(void* threadData);
class WorkerThreadPosix : public WorkerThread {
public:
explicit WorkerThreadPosix(MultiThreadedJobQueue* aJobQueue)
: WorkerThread(aJobQueue)
{
pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this));
}
~WorkerThreadPosix()
{
pthread_join(mThread, nullptr);
}
virtual void SetName(const char*) override
{
// XXX - temporarily disabled, see bug 1209039
//
// // Call this from the thread itself because of Mac.
//#ifdef XP_MACOSX
// pthread_setname_np(aName);
//#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
// pthread_set_name_np(mThread, aName);
//#elif defined(__NetBSD__)
// pthread_setname_np(mThread, "%s", (void*)aName);
//#else
// pthread_setname_np(mThread, aName);
//#endif
}
protected:
pthread_t mThread;
};
void* ThreadCallback(void* threadData)
{
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
thread->Run();
return nullptr;
}
WorkerThread*
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
{
return new WorkerThreadPosix(aJobQueue);
}
MultiThreadedJobQueue::MultiThreadedJobQueue()
: mThreadsCount(0)
, mShuttingDown(false)
{}
MultiThreadedJobQueue::~MultiThreadedJobQueue()
{
MOZ_ASSERT(mJobs.empty());
}
bool
MultiThreadedJobQueue::WaitForJob(Job*& aOutJob)
{
return PopJob(aOutJob, BLOCKING);
}
bool
MultiThreadedJobQueue::PopJob(Job*& aOutJobs, AccessType aAccess)
{
for (;;) {
CriticalSectionAutoEnter lock(&mMutex);
while (aAccess == BLOCKING && !mShuttingDown && mJobs.empty()) {
mAvailableCondvar.Wait(&mMutex);
}
if (mShuttingDown) {
return false;
}
if (mJobs.empty()) {
if (aAccess == NON_BLOCKING) {
return false;
}
continue;
}
Job* task = mJobs.front();
MOZ_ASSERT(task);
mJobs.pop_front();
aOutJobs = task;
return true;
}
}
void
MultiThreadedJobQueue::SubmitJob(Job* aJobs)
{
MOZ_ASSERT(aJobs);
CriticalSectionAutoEnter lock(&mMutex);
mJobs.push_back(aJobs);
mAvailableCondvar.Broadcast();
}
size_t
MultiThreadedJobQueue::NumJobs()
{
CriticalSectionAutoEnter lock(&mMutex);
return mJobs.size();
}
bool
MultiThreadedJobQueue::IsEmpty()
{
CriticalSectionAutoEnter lock(&mMutex);
return mJobs.empty();
}
void
MultiThreadedJobQueue::ShutDown()
{
CriticalSectionAutoEnter lock(&mMutex);
mShuttingDown = true;
while (mThreadsCount) {
mAvailableCondvar.Broadcast();
mShutdownCondvar.Wait(&mMutex);
}
}
void
MultiThreadedJobQueue::RegisterThread()
{
mThreadsCount += 1;
}
void
MultiThreadedJobQueue::UnregisterThread()
{
CriticalSectionAutoEnter lock(&mMutex);
mThreadsCount -= 1;
if (mThreadsCount == 0) {
mShutdownCondvar.Broadcast();
}
}
EventObject::EventObject()
: mIsSet(false)
{}
EventObject::~EventObject()
{}
bool
EventObject::Peak()
{
CriticalSectionAutoEnter lock(&mMutex);
return mIsSet;
}
void
EventObject::Set()
{
CriticalSectionAutoEnter lock(&mMutex);
if (!mIsSet) {
mIsSet = true;
mCond.Broadcast();
}
}
void
EventObject::Wait()
{
CriticalSectionAutoEnter lock(&mMutex);
if (mIsSet) {
return;
}
mCond.Wait(&mMutex);
}
} // namespce
} // namespce

142
gfx/2d/JobScheduler_posix.h Normal file
View file

@ -0,0 +1,142 @@
/* -*- 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/. */
#ifndef WIN32
#ifndef MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
#define MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
#include <string>
#include <vector>
#include <list>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include "mozilla/RefPtr.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/gfx/CriticalSection.h"
#include "mozilla/RefCounted.h"
namespace mozilla {
namespace gfx {
class Job;
class PosixCondVar;
class WorkerThread;
// posix platforms only!
class PosixCondVar {
public:
PosixCondVar() {
DebugOnly<int> err = pthread_cond_init(&mCond, nullptr);
MOZ_ASSERT(!err);
}
~PosixCondVar() {
DebugOnly<int> err = pthread_cond_destroy(&mCond);
MOZ_ASSERT(!err);
}
void Wait(CriticalSection* aMutex) {
DebugOnly<int> err = pthread_cond_wait(&mCond, &aMutex->mMutex);
MOZ_ASSERT(!err);
}
void Broadcast() {
DebugOnly<int> err = pthread_cond_broadcast(&mCond);
MOZ_ASSERT(!err);
}
protected:
pthread_cond_t mCond;
};
/// A simple and naive multithreaded task queue
///
/// The public interface of this class must remain identical to its equivalent
/// in JobScheduler_win32.h
class MultiThreadedJobQueue {
public:
enum AccessType {
BLOCKING,
NON_BLOCKING
};
// Producer thread
MultiThreadedJobQueue();
// Producer thread
~MultiThreadedJobQueue();
// Worker threads
bool WaitForJob(Job*& aOutJob);
// Any thread
bool PopJob(Job*& aOutJob, AccessType aAccess);
// Any threads
void SubmitJob(Job* aJob);
// Producer thread
void ShutDown();
// Any thread
size_t NumJobs();
// Any thread
bool IsEmpty();
// Producer thread
void RegisterThread();
// Worker threads
void UnregisterThread();
protected:
std::list<Job*> mJobs;
CriticalSection mMutex;
PosixCondVar mAvailableCondvar;
PosixCondVar mShutdownCondvar;
int32_t mThreadsCount;
bool mShuttingDown;
friend class WorkerThread;
};
/// An object that a thread can synchronously wait on.
/// Usually set by a SetEventJob.
class EventObject : public external::AtomicRefCounted<EventObject>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
EventObject();
~EventObject();
/// Synchronously wait until the event is set.
void Wait();
/// Return true if the event is set, without blocking.
bool Peak();
/// Set the event.
void Set();
protected:
CriticalSection mMutex;
PosixCondVar mCond;
bool mIsSet;
};
} // namespace
} // namespace
#include "JobScheduler.h"
#endif
#endif

View file

@ -0,0 +1,148 @@
/* -*- 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 "JobScheduler.h"
#include "mozilla/gfx/Logging.h"
using namespace std;
namespace mozilla {
namespace gfx {
DWORD __stdcall ThreadCallback(void* threadData);
class WorkerThreadWin32 : public WorkerThread {
public:
explicit WorkerThreadWin32(MultiThreadedJobQueue* aJobQueue)
: WorkerThread(aJobQueue)
{
mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr);
}
~WorkerThreadWin32()
{
::WaitForSingleObject(mThread, INFINITE);
::CloseHandle(mThread);
}
protected:
HANDLE mThread;
};
DWORD __stdcall ThreadCallback(void* threadData)
{
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
thread->Run();
return 0;
}
WorkerThread*
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
{
return new WorkerThreadWin32(aJobQueue);
}
bool
MultiThreadedJobQueue::PopJob(Job*& aOutJob, AccessType aAccess)
{
for (;;) {
while (aAccess == BLOCKING && mJobs.empty()) {
{
CriticalSectionAutoEnter lock(&mSection);
if (mShuttingDown) {
return false;
}
}
HANDLE handles[] = { mAvailableEvent, mShutdownEvent };
::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
}
CriticalSectionAutoEnter lock(&mSection);
if (mShuttingDown) {
return false;
}
if (mJobs.empty()) {
if (aAccess == NON_BLOCKING) {
return false;
}
continue;
}
Job* task = mJobs.front();
MOZ_ASSERT(task);
mJobs.pop_front();
if (mJobs.empty()) {
::ResetEvent(mAvailableEvent);
}
aOutJob = task;
return true;
}
}
void
MultiThreadedJobQueue::SubmitJob(Job* aJob)
{
MOZ_ASSERT(aJob);
CriticalSectionAutoEnter lock(&mSection);
mJobs.push_back(aJob);
::SetEvent(mAvailableEvent);
}
void
MultiThreadedJobQueue::ShutDown()
{
{
CriticalSectionAutoEnter lock(&mSection);
mShuttingDown = true;
}
while (mThreadsCount) {
::SetEvent(mAvailableEvent);
::WaitForSingleObject(mShutdownEvent, INFINITE);
}
}
size_t
MultiThreadedJobQueue::NumJobs()
{
CriticalSectionAutoEnter lock(&mSection);
return mJobs.size();
}
bool
MultiThreadedJobQueue::IsEmpty()
{
CriticalSectionAutoEnter lock(&mSection);
return mJobs.empty();
}
void
MultiThreadedJobQueue::RegisterThread()
{
mThreadsCount += 1;
}
void
MultiThreadedJobQueue::UnregisterThread()
{
mSection.Enter();
mThreadsCount -= 1;
bool finishShutdown = mThreadsCount == 0;
mSection.Leave();
if (finishShutdown) {
// Can't touch mSection or any other member from now on because this object
// may get deleted on the main thread after mShutdownEvent is set.
::SetEvent(mShutdownEvent);
}
}
} // namespace
} // namespace

View file

@ -0,0 +1,99 @@
/* -*- 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/. */
#ifdef WIN32
#ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
#define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
#include <windows.h>
#include <list>
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/CriticalSection.h"
#include "mozilla/RefCounted.h"
namespace mozilla {
namespace gfx {
class WorkerThread;
class Job;
// The public interface of this class must remain identical to its equivalent
// in JobScheduler_posix.h
class MultiThreadedJobQueue {
public:
enum AccessType {
BLOCKING,
NON_BLOCKING
};
MultiThreadedJobQueue()
: mThreadsCount(0)
, mShuttingDown(false)
{
mAvailableEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
mShutdownEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
}
~MultiThreadedJobQueue()
{
::CloseHandle(mAvailableEvent);
::CloseHandle(mShutdownEvent);
}
bool WaitForJob(Job*& aOutJob) { return PopJob(aOutJob, BLOCKING); }
bool PopJob(Job*& aOutJob, AccessType aAccess);
void SubmitJob(Job* aJob);
void ShutDown();
size_t NumJobs();
bool IsEmpty();
void RegisterThread();
void UnregisterThread();
protected:
std::list<Job*> mJobs;
CriticalSection mSection;
HANDLE mAvailableEvent;
HANDLE mShutdownEvent;
int32_t mThreadsCount;
bool mShuttingDown;
friend class WorkerThread;
};
// The public interface of this class must remain identical to its equivalent
// in JobScheduler_posix.h
class EventObject : public external::AtomicRefCounted<EventObject>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
EventObject() { mEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); }
~EventObject() { ::CloseHandle(mEvent); }
void Wait() { ::WaitForSingleObject(mEvent, INFINITE); }
bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; }
void Set() { ::SetEvent(mEvent); }
protected:
// TODO: it's expensive to create events so we should try to reuse them
HANDLE mEvent;
};
} // namespace
} // namespace
#endif
#endif

706
gfx/2d/Logging.h Normal file
View file

@ -0,0 +1,706 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_LOGGING_H_
#define MOZILLA_GFX_LOGGING_H_
#include <string>
#include <sstream>
#include <stdio.h>
#include <vector>
#ifdef MOZ_LOGGING
#include "mozilla/Logging.h"
#endif
#include "mozilla/Tuple.h"
#if defined(MOZ_WIDGET_ANDROID)
#include "nsDebug.h"
#endif
#include "Point.h"
#include "BaseRect.h"
#include "Matrix.h"
#include "LoggingConstants.h"
#if defined(MOZ_LOGGING)
extern GFX2D_API mozilla::LogModule* GetGFX2DLog();
#endif
namespace mozilla {
namespace gfx {
#if defined(MOZ_LOGGING)
inline mozilla::LogLevel PRLogLevelForLevel(int aLevel) {
switch (aLevel) {
case LOG_CRITICAL:
return LogLevel::Error;
case LOG_WARNING:
return LogLevel::Warning;
case LOG_DEBUG:
return LogLevel::Debug;
case LOG_DEBUG_PRLOG:
return LogLevel::Debug;
case LOG_EVERYTHING:
return LogLevel::Error;
}
return LogLevel::Debug;
}
#endif
class LoggingPrefs
{
public:
// Used to choose the level of logging we get. The higher the number,
// the more logging we get. Value of zero will give you no logging,
// 1 just errors, 2 adds warnings and 3 or 4 add debug logging.
// In addition to setting the value to 4, you will need to set the
// environment variable MOZ_LOG to gfx:4. See mozilla/Logging.h for details.
static int32_t sGfxLogLevel;
};
/// Graphics logging is available in both debug and release builds and is
/// controlled with a gfx.logging.level preference. If not set, the default
/// for the preference is 5 in the debug builds, 1 in the release builds.
///
/// gfxDebug only works in the debug builds, and is used for information
/// level messages, helping with debugging. In addition to only working
/// in the debug builds, the value of the above preference of 3 or higher
/// is required.
///
/// gfxWarning messages are available in both debug and release builds,
/// on by default in the debug builds, and off by default in the release builds.
/// Setting the preference gfx.logging.level to a value of 2 or higher will
/// show the warnings.
///
/// gfxCriticalError is available in debug and release builds by default.
/// It is only unavailable if gfx.logging.level is set to 0 (or less.)
/// It outputs the message to stderr or equivalent, like gfxWarning.
/// In the event of a crash, the crash report is annotated with first and
/// the last few of these errors, under the key GraphicsCriticalError.
/// The total number of errors stored in the crash report is controlled
/// by preference gfx.logging.crash.length.
///
/// On platforms that support MOZ_LOGGING, the story is slightly more involved.
/// In that case, unless gfx.logging.level is set to 4 or higher, the output
/// is further controlled by the "gfx2d" logging module. However, in the case
/// where such module would disable the output, in all but gfxDebug cases,
/// we will still send a printf.
// The range is due to the values set in Histograms.json
enum class LogReason : int {
MustBeMoreThanThis = -1,
// Start. Do not insert, always add at end. If you remove items,
// make sure the other items retain their values.
D3D11InvalidCallDeviceRemoved = 0,
D3D11InvalidCall,
D3DLockTimeout,
D3D10FinalizeFrame,
D3D11FinalizeFrame,
D3D10SyncLock,
D3D11SyncLock,
D2D1NoWriteMap,
JobStatusError,
FilterInputError,
FilterInputData, // 10
FilterInputRect,
FilterInputSet,
FilterInputFormat,
FilterNodeD2D1Target,
FilterNodeD2D1Backend,
SourceSurfaceIncompatible,
GlyphAllocFailedCairo,
GlyphAllocFailedCG,
InvalidRect,
CannotDraw3D, // 20
IncompatibleBasicTexturedEffect,
InvalidFont,
PAllocTextureBackendMismatch,
GetFontFileDataFailed,
MessageChannelCloseFailure,
MessageChannelInvalidHandle,
TextureAliveAfterShutdown,
InvalidContext,
InvalidCommandList,
AsyncTransactionTimeout, // 30
TextureCreation,
InvalidCacheSurface,
AlphaWithBasicClient,
UnbalancedClipStack,
ProcessingError,
NativeFontResourceNotFound,
// End
MustBeLessThanThis = 101,
};
struct BasicLogger
{
// For efficiency, this method exists and copies the logic of the
// OutputMessage below. If making any changes here, also make it
// in the appropriate places in that method.
static bool ShouldOutputMessage(int aLevel) {
if (LoggingPrefs::sGfxLogLevel >= aLevel) {
#if defined(MOZ_WIDGET_ANDROID)
return true;
#else
#if defined(MOZ_LOGGING)
if (MOZ_LOG_TEST(GetGFX2DLog(), PRLogLevelForLevel(aLevel))) {
return true;
} else
#endif
if ((LoggingPrefs::sGfxLogLevel >= LOG_DEBUG_PRLOG) ||
(aLevel < LOG_DEBUG)) {
return true;
}
#endif
}
return false;
}
// Only for really critical errors.
static void CrashAction(LogReason aReason) {}
static void OutputMessage(const std::string &aString,
int aLevel,
bool aNoNewline) {
// This behavior (the higher the preference, the more we log)
// is consistent with what prlog does in general. Note that if prlog
// is in the build, but disabled, we will printf if the preferences
// requires us to log something (see sGfxLogLevel for the special
// treatment of LOG_DEBUG and LOG_DEBUG_PRLOG)
//
// If making any logic changes to this method, you should probably
// make the corresponding change in the ShouldOutputMessage method
// above.
if (LoggingPrefs::sGfxLogLevel >= aLevel) {
#if defined(MOZ_WIDGET_ANDROID)
printf_stderr("%s%s", aString.c_str(), aNoNewline ? "" : "\n");
#else
#if defined(MOZ_LOGGING)
if (MOZ_LOG_TEST(GetGFX2DLog(), PRLogLevelForLevel(aLevel))) {
PR_LogPrint("%s%s", aString.c_str(), aNoNewline ? "" : "\n");
} else
#endif
if ((LoggingPrefs::sGfxLogLevel >= LOG_DEBUG_PRLOG) ||
(aLevel < LOG_DEBUG)) {
printf("%s%s", aString.c_str(), aNoNewline ? "" : "\n");
}
#endif
}
}
};
struct CriticalLogger {
static void OutputMessage(const std::string &aString, int aLevel, bool aNoNewline);
static void CrashAction(LogReason aReason);
};
// The int is the index of the Log call; if the number of logs exceeds some preset
// capacity we may not get all of them, so the indices help figure out which
// ones we did save. The double is expected to be the "TimeDuration",
// time in seconds since the process creation.
typedef mozilla::Tuple<int32_t,std::string,double> LoggingRecordEntry;
// Implement this interface and init the Factory with an instance to
// forward critical logs.
typedef std::vector<LoggingRecordEntry> LoggingRecord;
class LogForwarder {
public:
virtual ~LogForwarder() {}
virtual void Log(const std::string &aString) = 0;
virtual void CrashAction(LogReason aReason) = 0;
virtual bool UpdateStringsVector(const std::string& aString) = 0;
// Provide a copy of the logs to the caller.
virtual LoggingRecord LoggingRecordCopy() = 0;
};
class NoLog
{
public:
NoLog() {}
~NoLog() {}
// No-op
MOZ_IMPLICIT NoLog(const NoLog&) {}
template<typename T>
NoLog &operator <<(const T &aLogText) { return *this; }
};
enum class LogOptions : int {
NoNewline = 0x01,
AutoPrefix = 0x02,
AssertOnCall = 0x04,
CrashAction = 0x08,
};
template<typename T>
struct Hexa {
explicit Hexa(T aVal) : mVal(aVal) {}
T mVal;
};
template<typename T>
Hexa<T> hexa(T val) { return Hexa<T>(val); }
template<int L, typename Logger = BasicLogger>
class Log
{
public:
// The default is to have the prefix, have the new line, and for critical
// logs assert on each call.
static int DefaultOptions(bool aWithAssert = true) {
return (int(LogOptions::AutoPrefix) |
(aWithAssert ? int(LogOptions::AssertOnCall) : 0));
}
// Note that we're calling BasicLogger::ShouldOutputMessage, rather than
// Logger::ShouldOutputMessage. Since we currently don't have a different
// version of that method for different loggers, this is OK. Once we do,
// change BasicLogger::ShouldOutputMessage to Logger::ShouldOutputMessage.
explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL),
LogReason aReason = LogReason::MustBeMoreThanThis)
: mOptions(0)
, mLogIt(false)
{
Init(aOptions, BasicLogger::ShouldOutputMessage(L), aReason);
}
~Log() {
Flush();
}
void Flush() {
if (MOZ_LIKELY(!LogIt())) return;
std::string str = mMessage.str();
if (!str.empty()) {
WriteLog(str);
}
mMessage.str("");
}
Log &operator <<(char aChar) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aChar;
}
return *this;
}
Log &operator <<(const std::string &aLogText) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aLogText;
}
return *this;
}
Log &operator <<(const char aStr[]) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << static_cast<const char*>(aStr);
}
return *this;
}
Log &operator <<(bool aBool) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << (aBool ? "true" : "false");
}
return *this;
}
Log &operator <<(int aInt) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aInt;
}
return *this;
}
Log &operator <<(unsigned int aInt) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aInt;
}
return *this;
}
Log &operator <<(long aLong) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aLong;
}
return *this;
}
Log &operator <<(unsigned long aLong) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aLong;
}
return *this;
}
Log &operator <<(long long aLong) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aLong;
}
return *this;
}
Log &operator <<(unsigned long long aLong) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aLong;
}
return *this;
}
Log &operator <<(Float aFloat) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aFloat;
}
return *this;
}
Log &operator <<(double aDouble) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << aDouble;
}
return *this;
}
template <typename T, typename Sub, typename Coord>
Log &operator <<(const BasePoint<T, Sub, Coord>& aPoint) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << "Point" << aPoint;
}
return *this;
}
template <typename T, typename Sub>
Log &operator <<(const BaseSize<T, Sub>& aSize) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << "Size(" << aSize.width << "," << aSize.height << ")";
}
return *this;
}
template <typename T, typename Sub, typename Point, typename SizeT, typename Margin>
Log &operator <<(const BaseRect<T, Sub, Point, SizeT, Margin>& aRect) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << "Rect" << aRect;
}
return *this;
}
Log &operator<<(const Matrix& aMatrix) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << "Matrix(" << aMatrix._11 << " " << aMatrix._12 << " ; " << aMatrix._21 << " " << aMatrix._22 << " ; " << aMatrix._31 << " " << aMatrix._32 << ")";
}
return *this;
}
template<typename T>
Log &operator<<(Hexa<T> aHex) {
if (MOZ_UNLIKELY(LogIt())) {
mMessage << std::showbase << std::hex
<< aHex.mVal
<< std::noshowbase << std::dec;
}
return *this;
}
Log& operator<<(SurfaceFormat aFormat) {
if (MOZ_UNLIKELY(LogIt())) {
switch(aFormat) {
case SurfaceFormat::B8G8R8A8:
mMessage << "SurfaceFormat::B8G8R8A8";
break;
case SurfaceFormat::B8G8R8X8:
mMessage << "SurfaceFormat::B8G8R8X8";
break;
case SurfaceFormat::R8G8B8A8:
mMessage << "SurfaceFormat::R8G8B8A8";
break;
case SurfaceFormat::R8G8B8X8:
mMessage << "SurfaceFormat::R8G8B8X8";
break;
case SurfaceFormat::R5G6B5_UINT16:
mMessage << "SurfaceFormat::R5G6B5_UINT16";
break;
case SurfaceFormat::A8:
mMessage << "SurfaceFormat::A8";
break;
case SurfaceFormat::YUV:
mMessage << "SurfaceFormat::YUV";
break;
case SurfaceFormat::UNKNOWN:
mMessage << "SurfaceFormat::UNKNOWN";
break;
default:
mMessage << "Invalid SurfaceFormat (" << (int)aFormat << ")";
break;
}
}
return *this;
}
Log& operator<<(SurfaceType aType) {
if (MOZ_UNLIKELY(LogIt())) {
switch(aType) {
case SurfaceType::DATA:
mMessage << "SurfaceType::DATA";
break;
case SurfaceType::D2D1_BITMAP:
mMessage << "SurfaceType::D2D1_BITMAP";
break;
case SurfaceType::D2D1_DRAWTARGET:
mMessage << "SurfaceType::D2D1_DRAWTARGET";
break;
case SurfaceType::CAIRO:
mMessage << "SurfaceType::CAIRO";
break;
case SurfaceType::CAIRO_IMAGE:
mMessage << "SurfaceType::CAIRO_IMAGE";
break;
case SurfaceType::COREGRAPHICS_IMAGE:
mMessage << "SurfaceType::COREGRAPHICS_IMAGE";
break;
case SurfaceType::COREGRAPHICS_CGCONTEXT:
mMessage << "SurfaceType::COREGRAPHICS_CGCONTEXT";
break;
case SurfaceType::SKIA:
mMessage << "SurfaceType::SKIA";
break;
case SurfaceType::DUAL_DT:
mMessage << "SurfaceType::DUAL_DT";
break;
case SurfaceType::D2D1_1_IMAGE:
mMessage << "SurfaceType::D2D1_1_IMAGE";
break;
case SurfaceType::RECORDING:
mMessage << "SurfaceType::RECORDING";
break;
case SurfaceType::TILED:
mMessage << "SurfaceType::TILED";
break;
default:
mMessage << "Invalid SurfaceType (" << (int)aType << ")";
break;
}
}
return *this;
}
inline bool LogIt() const { return mLogIt; }
inline bool NoNewline() const { return mOptions & int(LogOptions::NoNewline); }
inline bool AutoPrefix() const { return mOptions & int(LogOptions::AutoPrefix); }
inline bool ValidReason() const { return (int)mReason > (int)LogReason::MustBeMoreThanThis && (int)mReason < (int)LogReason::MustBeLessThanThis; }
// We do not want this version to do any work, and stringstream can't be
// copied anyway. It does come in handy for the "Once" macro defined below.
MOZ_IMPLICIT Log(const Log& log) { Init(log.mOptions, false, log.mReason); }
private:
// Initialization common to two constructors
void Init(int aOptions, bool aLogIt, LogReason aReason) {
mOptions = aOptions;
mReason = aReason;
mLogIt = aLogIt;
if (mLogIt) {
if (AutoPrefix()) {
if (mOptions & int(LogOptions::AssertOnCall)) {
mMessage << "[GFX" << L;
} else {
mMessage << "[GFX" << L << "-";
}
}
if ((mOptions & int(LogOptions::CrashAction)) && ValidReason()) {
mMessage << " " << (int)mReason;
}
if (AutoPrefix()) {
mMessage << "]: ";
}
}
}
void WriteLog(const std::string &aString) {
if (MOZ_UNLIKELY(LogIt())) {
Logger::OutputMessage(aString, L, NoNewline());
// Assert if required. We don't have a three parameter MOZ_ASSERT
// so use the underlying functions instead (see bug 1281702):
#ifdef DEBUG
if (mOptions & int(LogOptions::AssertOnCall)) {
MOZ_ReportAssertionFailure(aString.c_str(), __FILE__, __LINE__);
MOZ_CRASH("GFX: An assert from the graphics logger");
}
#endif
if ((mOptions & int(LogOptions::CrashAction)) && ValidReason()) {
Logger::CrashAction(mReason);
}
}
}
std::stringstream mMessage;
int mOptions;
LogReason mReason;
bool mLogIt;
};
typedef Log<LOG_DEBUG> DebugLog;
typedef Log<LOG_WARNING> WarningLog;
typedef Log<LOG_CRITICAL, CriticalLogger> CriticalLog;
// Macro to glue names to get us less chance of name clashing.
#if defined GFX_LOGGING_GLUE1 || defined GFX_LOGGING_GLUE
#error "Clash of the macro GFX_LOGGING_GLUE1 or GFX_LOGGING_GLUE"
#endif
#define GFX_LOGGING_GLUE1(x, y) x##y
#define GFX_LOGGING_GLUE(x, y) GFX_LOGGING_GLUE1(x, y)
// This log goes into crash reports, use with care.
#define gfxCriticalError mozilla::gfx::CriticalLog
#define gfxCriticalErrorOnce static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxCriticalError
// This is a shortcut for errors we want logged in crash reports/about support
// but we do not want asserting. These are available in all builds, so it is
// not worth trying to do magic to avoid matching the syntax of gfxCriticalError.
// So, this one is used as
// gfxCriticalNote << "Something to report and not assert";
// while the critical error is
// gfxCriticalError() << "Something to report and assert";
#define gfxCriticalNote gfxCriticalError(gfxCriticalError::DefaultOptions(false))
#define gfxCriticalNoteOnce static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxCriticalNote
// The "once" versions will only trigger the first time through. You can do this:
// gfxCriticalErrorOnce() << "This message only shows up once;
// instead of the usual:
// static bool firstTime = true;
// if (firstTime) {
// firstTime = false;
// gfxCriticalError() << "This message only shows up once;
// }
#if defined(DEBUG)
#define gfxDebug mozilla::gfx::DebugLog
#define gfxDebugOnce static gfxDebug GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxDebug
#else
#define gfxDebug if (1) ; else mozilla::gfx::NoLog
#define gfxDebugOnce if (1) ; else mozilla::gfx::NoLog
#endif
// Have gfxWarning available (behind a runtime preference)
#define gfxWarning mozilla::gfx::WarningLog
#define gfxWarningOnce static gfxWarning GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxWarning
// In the debug build, this is equivalent to the default gfxCriticalError.
// In the non-debug build, on nightly and dev edition, it will MOZ_CRASH.
// On beta and release versions, it will telemetry count, but proceed.
//
// You should create a (new) enum in the LogReason and use it for the reason
// parameter to ensure uniqueness.
#define gfxDevCrash(reason) gfxCriticalError(int(gfx::LogOptions::AutoPrefix) | int(gfx::LogOptions::AssertOnCall) | int(gfx::LogOptions::CrashAction), (reason))
// See nsDebug.h and the NS_WARN_IF macro
#ifdef __cplusplus
// For now, have MOZ2D_ERROR_IF available in debug and non-debug builds
inline bool MOZ2D_error_if_impl(bool aCondition, const char* aExpr,
const char* aFile, int32_t aLine)
{
if (MOZ_UNLIKELY(aCondition)) {
gfxCriticalError() << aExpr << " at " << aFile << ":" << aLine;
}
return aCondition;
}
#define MOZ2D_ERROR_IF(condition) \
MOZ2D_error_if_impl(condition, #condition, __FILE__, __LINE__)
#ifdef DEBUG
inline bool MOZ2D_warn_if_impl(bool aCondition, const char* aExpr,
const char* aFile, int32_t aLine)
{
if (MOZ_UNLIKELY(aCondition)) {
gfxWarning() << aExpr << " at " << aFile << ":" << aLine;
}
return aCondition;
}
#define MOZ2D_WARN_IF(condition) \
MOZ2D_warn_if_impl(condition, #condition, __FILE__, __LINE__)
#else
#define MOZ2D_WARN_IF(condition) (bool)(condition)
#endif
#endif
const int INDENT_PER_LEVEL = 2;
class TreeLog
{
public:
explicit TreeLog(const std::string& aPrefix = "")
: mLog(int(LogOptions::NoNewline)),
mPrefix(aPrefix),
mDepth(0),
mStartOfLine(true),
mConditionedOnPref(false),
mPrefFunction(nullptr) {}
template <typename T>
TreeLog& operator<<(const T& aObject) {
if (mConditionedOnPref && !mPrefFunction()) {
return *this;
}
if (mStartOfLine) {
mLog << '[' << mPrefix << "] " << std::string(mDepth * INDENT_PER_LEVEL, ' ');
mStartOfLine = false;
}
mLog << aObject;
if (EndsInNewline(aObject)) {
// Don't indent right here as the user may change the indent
// between now and the first output to the next line.
mLog.Flush();
mStartOfLine = true;
}
return *this;
}
void IncreaseIndent() { ++mDepth; }
void DecreaseIndent() {
MOZ_ASSERT(mDepth > 0);
--mDepth;
}
void ConditionOnPrefFunction(bool(*aPrefFunction)()) {
mConditionedOnPref = true;
mPrefFunction = aPrefFunction;
}
private:
Log<LOG_DEBUG> mLog;
std::string mPrefix;
uint32_t mDepth;
bool mStartOfLine;
bool mConditionedOnPref;
bool (*mPrefFunction)();
template <typename T>
static bool EndsInNewline(const T& aObject) {
return false;
}
static bool EndsInNewline(const std::string& aString) {
return !aString.empty() && aString[aString.length() - 1] == '\n';
}
static bool EndsInNewline(char aChar) {
return aChar == '\n';
}
static bool EndsInNewline(const char* aString) {
return EndsInNewline(std::string(aString));
}
};
class TreeAutoIndent
{
public:
explicit TreeAutoIndent(TreeLog& aTreeLog) : mTreeLog(aTreeLog) {
mTreeLog.IncreaseIndent();
}
TreeAutoIndent(const TreeAutoIndent& aTreeAutoIndent) :
TreeAutoIndent(aTreeAutoIndent.mTreeLog) {
mTreeLog.IncreaseIndent();
}
TreeAutoIndent& operator=(const TreeAutoIndent& aTreeAutoIndent) = delete;
~TreeAutoIndent() {
mTreeLog.DecreaseIndent();
}
private:
TreeLog& mTreeLog;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_LOGGING_H_ */

30
gfx/2d/LoggingConstants.h Normal file
View file

@ -0,0 +1,30 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_LOGGING_CONSTANTS_H_
#define MOZILLA_GFX_LOGGING_CONSTANTS_H_
namespace mozilla {
namespace gfx {
// Attempting to be consistent with prlog values, but that isn't critical
// (and note that 5 has a special meaning - see the description
// with LoggingPrefs::sGfxLogLevel)
const int LOG_CRITICAL = 1;
const int LOG_WARNING = 2;
const int LOG_DEBUG = 3;
const int LOG_DEBUG_PRLOG = 4;
const int LOG_EVERYTHING = 5; // This needs to be the highest value
#if defined(DEBUG)
const int LOG_DEFAULT = LOG_EVERYTHING;
#else
const int LOG_DEFAULT = LOG_CRITICAL;
#endif
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_LOGGING_CONSTANTS_H_ */

196
gfx/2d/MMIHelpers.h Normal file
View file

@ -0,0 +1,196 @@
/*
============================================================================
Name : MMIHelpers.h
Author : Heiher <r@hev.cc>
Version : 0.0.1
Copyright : Copyright (c) 2015 everyone.
Description : The helpers for x86 SSE to Loongson MMI.
============================================================================
*/
#ifndef __MMI_HELPERS_H__
#define __MMI_HELPERS_H__
#define __mm_packxxxx(_f, _D, _d, _s, _t) \
#_f" %["#_t"], %["#_d"h], %["#_s"h] \n\t" \
#_f" %["#_D"l], %["#_d"l], %["#_s"l] \n\t" \
"punpckhwd %["#_D"h], %["#_D"l], %["#_t"] \n\t" \
"punpcklwd %["#_D"l], %["#_D"l], %["#_t"] \n\t"
#define _mm_or(_D, _d, _s) \
"or %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"or %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
#define _mm_xor(_D, _d, _s) \
"xor %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"xor %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
#define _mm_and(_D, _d, _s) \
"and %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"and %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: pandn */
#define _mm_pandn(_D, _d, _s) \
"pandn %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"pandn %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: pshuflw */
#define _mm_pshuflh(_D, _d, _s) \
"mov.d %["#_D"h], %["#_d"h] \n\t" \
"pshufh %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: psllw (bits) */
#define _mm_psllh(_D, _d, _s) \
"psllh %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"psllh %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: pslld (bits) */
#define _mm_psllw(_D, _d, _s) \
"psllw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"psllw %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: psllq (bits) */
#define _mm_pslld(_D, _d, _s) \
"dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: pslldq (bytes) */
#define _mm_psllq(_D, _d, _s, _s64, _tf) \
"subu %["#_tf"], %["#_s64"], %["#_s"] \n\t" \
"dsrl %["#_tf"], %["#_d"l], %["#_tf"] \n\t" \
"dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t" \
"or %["#_D"h], %["#_D"h], %["#_tf"] \n\t"
/* SSE: psrlw (bits) */
#define _mm_psrlh(_D, _d, _s) \
"psrlh %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"psrlh %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: psrld (bits) */
#define _mm_psrlw(_D, _d, _s) \
"psrlw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"psrlw %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: psrlq (bits) */
#define _mm_psrld(_D, _d, _s) \
"dsrl %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"dsrl %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: psrldq (bytes) */
#define _mm_psrlq(_D, _d, _s, _s64, _tf) \
"subu %["#_tf"], %["#_s64"], %["#_s"] \n\t" \
"dsll %["#_tf"], %["#_d"h], %["#_tf"] \n\t" \
"dsrl %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"dsrl %["#_D"l], %["#_d"l], %["#_s"] \n\t" \
"or %["#_D"l], %["#_D"l], %["#_tf"] \n\t"
/* SSE: psrad */
#define _mm_psraw(_D, _d, _s) \
"psraw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \
"psraw %["#_D"l], %["#_d"l], %["#_s"] \n\t"
/* SSE: paddb */
#define _mm_paddb(_D, _d, _s) \
"paddb %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"paddb %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: paddw */
#define _mm_paddh(_D, _d, _s) \
"paddh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"paddh %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: paddd */
#define _mm_paddw(_D, _d, _s) \
"paddw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"paddw %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: paddq */
#define _mm_paddd(_D, _d, _s) \
"dadd %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"dadd %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: psubw */
#define _mm_psubh(_D, _d, _s) \
"psubh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"psubh %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: psubd */
#define _mm_psubw(_D, _d, _s) \
"psubw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"psubw %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: pmaxub */
#define _mm_pmaxub(_D, _d, _s) \
"pmaxub %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"pmaxub %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: pmullw */
#define _mm_pmullh(_D, _d, _s) \
"pmullh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"pmullh %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: pmulhw */
#define _mm_pmulhh(_D, _d, _s) \
"pmulhh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"pmulhh %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: pmuludq */
#define _mm_pmuluw(_D, _d, _s) \
"pmuluw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \
"pmuluw %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: packsswb */
#define _mm_packsshb(_D, _d, _s, _t) \
__mm_packxxxx(packsshb, _D, _d, _s, _t)
/* SSE: packssdw */
#define _mm_packsswh(_D, _d, _s, _t) \
__mm_packxxxx(packsswh, _D, _d, _s, _t)
/* SSE: packuswb */
#define _mm_packushb(_D, _d, _s, _t) \
__mm_packxxxx(packushb, _D, _d, _s, _t)
/* SSE: punpcklbw */
#define _mm_punpcklbh(_D, _d, _s) \
"punpckhbh %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \
"punpcklbh %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: punpcklwd */
#define _mm_punpcklhw(_D, _d, _s) \
"punpckhhw %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \
"punpcklhw %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: punpckldq */
#define _mm_punpcklwd(_D, _d, _s) \
"punpckhwd %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \
"punpcklwd %["#_D"l], %["#_d"l], %["#_s"l] \n\t"
/* SSE: punpcklqdq */
#define _mm_punpckldq(_D, _d, _s) \
"mov.d %["#_D"h], %["#_s"l] \n\t" \
"mov.d %["#_D"l], %["#_d"l] \n\t"
/* SSE: punpckhbw */
#define _mm_punpckhbh(_D, _d, _s) \
"punpcklbh %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \
"punpckhbh %["#_D"h], %["#_d"h], %["#_s"h] \n\t"
/* SSE: punpckhwd */
#define _mm_punpckhhw(_D, _d, _s) \
"punpcklhw %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \
"punpckhhw %["#_D"h], %["#_d"h], %["#_s"h] \n\t"
/* SSE: punpckhdq */
#define _mm_punpckhwd(_D, _d, _s) \
"punpcklwd %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \
"punpckhwd %["#_D"h], %["#_d"h], %["#_s"h] \n\t"
/* SSE: punpckhqdq */
#define _mm_punpckhdq(_D, _d, _s) \
"mov.d %["#_D"l], %["#_d"h] \n\t" \
"mov.d %["#_D"h], %["#_s"h] \n\t"
#endif /* __MMI_HELPERS_H__ */

615
gfx/2d/MacIOSurface.cpp Normal file
View file

@ -0,0 +1,615 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:set ts=2 sts=2 sw=2 et cin:
/* 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 "MacIOSurface.h"
#include <OpenGL/gl.h>
#include <QuartzCore/QuartzCore.h>
#include <dlfcn.h>
#include "mozilla/RefPtr.h"
#include "mozilla/Assertions.h"
#include "GLConsts.h"
using namespace mozilla;
// IOSurface signatures
#define IOSURFACE_FRAMEWORK_PATH \
"/System/Library/Frameworks/IOSurface.framework/IOSurface"
#define OPENGL_FRAMEWORK_PATH \
"/System/Library/Frameworks/OpenGL.framework/OpenGL"
#define COREGRAPHICS_FRAMEWORK_PATH \
"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" \
"CoreGraphics.framework/CoreGraphics"
#define COREVIDEO_FRAMEWORK_PATH \
"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" \
"CoreVideo.framework/CoreVideo"
#define GET_CONST(const_name) \
((CFStringRef*) dlsym(sIOSurfaceFramework, const_name))
#define GET_IOSYM(dest,sym_name) \
(typeof(dest)) dlsym(sIOSurfaceFramework, sym_name)
#define GET_CGLSYM(dest,sym_name) \
(typeof(dest)) dlsym(sOpenGLFramework, sym_name)
#define GET_CGSYM(dest,sym_name) \
(typeof(dest)) dlsym(sCoreGraphicsFramework, sym_name)
#define GET_CVSYM(dest, sym_name) \
(typeof(dest)) dlsym(sCoreVideoFramework, sym_name)
MacIOSurfaceLib::LibraryUnloader MacIOSurfaceLib::sLibraryUnloader;
bool MacIOSurfaceLib::isLoaded = false;
void* MacIOSurfaceLib::sIOSurfaceFramework;
void* MacIOSurfaceLib::sOpenGLFramework;
void* MacIOSurfaceLib::sCoreGraphicsFramework;
void* MacIOSurfaceLib::sCoreVideoFramework;
IOSurfaceCreateFunc MacIOSurfaceLib::sCreate;
IOSurfaceGetIDFunc MacIOSurfaceLib::sGetID;
IOSurfaceLookupFunc MacIOSurfaceLib::sLookup;
IOSurfaceGetBaseAddressFunc MacIOSurfaceLib::sGetBaseAddress;
IOSurfaceGetBaseAddressOfPlaneFunc MacIOSurfaceLib::sGetBaseAddressOfPlane;
IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sWidth;
IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sHeight;
IOSurfaceSizeTFunc MacIOSurfaceLib::sPlaneCount;
IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sBytesPerRow;
IOSurfaceGetPropertyMaximumFunc MacIOSurfaceLib::sGetPropertyMaximum;
IOSurfaceVoidFunc MacIOSurfaceLib::sIncrementUseCount;
IOSurfaceVoidFunc MacIOSurfaceLib::sDecrementUseCount;
IOSurfaceLockFunc MacIOSurfaceLib::sLock;
IOSurfaceUnlockFunc MacIOSurfaceLib::sUnlock;
CGLTexImageIOSurface2DFunc MacIOSurfaceLib::sTexImage;
IOSurfaceContextCreateFunc MacIOSurfaceLib::sIOSurfaceContextCreate;
IOSurfaceContextCreateImageFunc MacIOSurfaceLib::sIOSurfaceContextCreateImage;
IOSurfaceContextGetSurfaceFunc MacIOSurfaceLib::sIOSurfaceContextGetSurface;
CVPixelBufferGetIOSurfaceFunc MacIOSurfaceLib::sCVPixelBufferGetIOSurface;
unsigned int (*MacIOSurfaceLib::sCGContextGetTypePtr) (CGContextRef) = nullptr;
IOSurfacePixelFormatFunc MacIOSurfaceLib::sPixelFormat;
CFStringRef MacIOSurfaceLib::kPropWidth;
CFStringRef MacIOSurfaceLib::kPropHeight;
CFStringRef MacIOSurfaceLib::kPropBytesPerElem;
CFStringRef MacIOSurfaceLib::kPropBytesPerRow;
CFStringRef MacIOSurfaceLib::kPropIsGlobal;
bool MacIOSurfaceLib::isInit() {
// Guard against trying to reload the library
// if it is not available.
if (!isLoaded)
LoadLibrary();
MOZ_ASSERT(sIOSurfaceFramework);
return sIOSurfaceFramework;
}
IOSurfacePtr MacIOSurfaceLib::IOSurfaceCreate(CFDictionaryRef properties) {
return sCreate(properties);
}
IOSurfacePtr MacIOSurfaceLib::IOSurfaceLookup(IOSurfaceID aIOSurfaceID) {
return sLookup(aIOSurfaceID);
}
IOSurfaceID MacIOSurfaceLib::IOSurfaceGetID(IOSurfacePtr aIOSurfacePtr) {
return sGetID(aIOSurfacePtr);
}
void* MacIOSurfaceLib::IOSurfaceGetBaseAddress(IOSurfacePtr aIOSurfacePtr) {
return sGetBaseAddress(aIOSurfacePtr);
}
void* MacIOSurfaceLib::IOSurfaceGetBaseAddressOfPlane(IOSurfacePtr aIOSurfacePtr,
size_t planeIndex) {
return sGetBaseAddressOfPlane(aIOSurfacePtr, planeIndex);
}
size_t MacIOSurfaceLib::IOSurfaceGetPlaneCount(IOSurfacePtr aIOSurfacePtr) {
return sPlaneCount(aIOSurfacePtr);
}
size_t MacIOSurfaceLib::IOSurfaceGetWidth(IOSurfacePtr aIOSurfacePtr, size_t plane) {
return sWidth(aIOSurfacePtr, plane);
}
size_t MacIOSurfaceLib::IOSurfaceGetHeight(IOSurfacePtr aIOSurfacePtr, size_t plane) {
return sHeight(aIOSurfacePtr, plane);
}
size_t MacIOSurfaceLib::IOSurfaceGetBytesPerRow(IOSurfacePtr aIOSurfacePtr, size_t plane) {
return sBytesPerRow(aIOSurfacePtr, plane);
}
size_t MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(CFStringRef property) {
return sGetPropertyMaximum(property);
}
OSType MacIOSurfaceLib::IOSurfaceGetPixelFormat(IOSurfacePtr aIOSurfacePtr) {
return sPixelFormat(aIOSurfacePtr);
}
IOReturn MacIOSurfaceLib::IOSurfaceLock(IOSurfacePtr aIOSurfacePtr,
uint32_t options, uint32_t* seed) {
return sLock(aIOSurfacePtr, options, seed);
}
IOReturn MacIOSurfaceLib::IOSurfaceUnlock(IOSurfacePtr aIOSurfacePtr,
uint32_t options, uint32_t *seed) {
return sUnlock(aIOSurfacePtr, options, seed);
}
void MacIOSurfaceLib::IOSurfaceIncrementUseCount(IOSurfacePtr aIOSurfacePtr) {
sIncrementUseCount(aIOSurfacePtr);
}
void MacIOSurfaceLib::IOSurfaceDecrementUseCount(IOSurfacePtr aIOSurfacePtr) {
sDecrementUseCount(aIOSurfacePtr);
}
CGLError MacIOSurfaceLib::CGLTexImageIOSurface2D(CGLContextObj ctxt,
GLenum target, GLenum internalFormat,
GLsizei width, GLsizei height,
GLenum format, GLenum type,
IOSurfacePtr ioSurface, GLuint plane) {
return sTexImage(ctxt, target, internalFormat, width, height,
format, type, ioSurface, plane);
}
IOSurfacePtr MacIOSurfaceLib::CVPixelBufferGetIOSurface(CVPixelBufferRef aPixelBuffer) {
return sCVPixelBufferGetIOSurface(aPixelBuffer);
}
CGContextRef MacIOSurfaceLib::IOSurfaceContextCreate(IOSurfacePtr aIOSurfacePtr,
unsigned aWidth, unsigned aHeight,
unsigned aBitsPerComponent, unsigned aBytes,
CGColorSpaceRef aColorSpace, CGBitmapInfo bitmapInfo) {
if (!sIOSurfaceContextCreate)
return nullptr;
return sIOSurfaceContextCreate(aIOSurfacePtr, aWidth, aHeight, aBitsPerComponent, aBytes, aColorSpace, bitmapInfo);
}
CGImageRef MacIOSurfaceLib::IOSurfaceContextCreateImage(CGContextRef aContext) {
if (!sIOSurfaceContextCreateImage)
return nullptr;
return sIOSurfaceContextCreateImage(aContext);
}
IOSurfacePtr MacIOSurfaceLib::IOSurfaceContextGetSurface(CGContextRef aContext) {
if (!sIOSurfaceContextGetSurface)
return nullptr;
return sIOSurfaceContextGetSurface(aContext);
}
CFStringRef MacIOSurfaceLib::GetIOConst(const char* symbole) {
CFStringRef *address = (CFStringRef*)dlsym(sIOSurfaceFramework, symbole);
if (!address)
return nullptr;
return *address;
}
void MacIOSurfaceLib::LoadLibrary() {
if (isLoaded) {
return;
}
isLoaded = true;
sIOSurfaceFramework = dlopen(IOSURFACE_FRAMEWORK_PATH,
RTLD_LAZY | RTLD_LOCAL);
sOpenGLFramework = dlopen(OPENGL_FRAMEWORK_PATH,
RTLD_LAZY | RTLD_LOCAL);
sCoreGraphicsFramework = dlopen(COREGRAPHICS_FRAMEWORK_PATH,
RTLD_LAZY | RTLD_LOCAL);
sCoreVideoFramework = dlopen(COREVIDEO_FRAMEWORK_PATH,
RTLD_LAZY | RTLD_LOCAL);
if (!sIOSurfaceFramework || !sOpenGLFramework || !sCoreGraphicsFramework ||
!sCoreVideoFramework) {
if (sIOSurfaceFramework)
dlclose(sIOSurfaceFramework);
if (sOpenGLFramework)
dlclose(sOpenGLFramework);
if (sCoreGraphicsFramework)
dlclose(sCoreGraphicsFramework);
if (sCoreVideoFramework)
dlclose(sCoreVideoFramework);
sIOSurfaceFramework = nullptr;
sOpenGLFramework = nullptr;
sCoreGraphicsFramework = nullptr;
sCoreVideoFramework = nullptr;
return;
}
kPropWidth = GetIOConst("kIOSurfaceWidth");
kPropHeight = GetIOConst("kIOSurfaceHeight");
kPropBytesPerElem = GetIOConst("kIOSurfaceBytesPerElement");
kPropBytesPerRow = GetIOConst("kIOSurfaceBytesPerRow");
kPropIsGlobal = GetIOConst("kIOSurfaceIsGlobal");
sCreate = GET_IOSYM(sCreate, "IOSurfaceCreate");
sGetID = GET_IOSYM(sGetID, "IOSurfaceGetID");
sWidth = GET_IOSYM(sWidth, "IOSurfaceGetWidthOfPlane");
sHeight = GET_IOSYM(sHeight, "IOSurfaceGetHeightOfPlane");
sBytesPerRow = GET_IOSYM(sBytesPerRow, "IOSurfaceGetBytesPerRowOfPlane");
sGetPropertyMaximum = GET_IOSYM(sGetPropertyMaximum, "IOSurfaceGetPropertyMaximum");
sLookup = GET_IOSYM(sLookup, "IOSurfaceLookup");
sLock = GET_IOSYM(sLock, "IOSurfaceLock");
sUnlock = GET_IOSYM(sUnlock, "IOSurfaceUnlock");
sIncrementUseCount =
GET_IOSYM(sIncrementUseCount, "IOSurfaceIncrementUseCount");
sDecrementUseCount =
GET_IOSYM(sDecrementUseCount, "IOSurfaceDecrementUseCount");
sGetBaseAddress = GET_IOSYM(sGetBaseAddress, "IOSurfaceGetBaseAddress");
sGetBaseAddressOfPlane =
GET_IOSYM(sGetBaseAddressOfPlane, "IOSurfaceGetBaseAddressOfPlane");
sPlaneCount = GET_IOSYM(sPlaneCount, "IOSurfaceGetPlaneCount");
sPixelFormat = GET_IOSYM(sPixelFormat, "IOSurfaceGetPixelFormat");
sTexImage = GET_CGLSYM(sTexImage, "CGLTexImageIOSurface2D");
sCGContextGetTypePtr = (unsigned int (*)(CGContext*))dlsym(RTLD_DEFAULT, "CGContextGetType");
sCVPixelBufferGetIOSurface =
GET_CVSYM(sCVPixelBufferGetIOSurface, "CVPixelBufferGetIOSurface");
// Optional symbols
sIOSurfaceContextCreate = GET_CGSYM(sIOSurfaceContextCreate, "CGIOSurfaceContextCreate");
sIOSurfaceContextCreateImage = GET_CGSYM(sIOSurfaceContextCreateImage, "CGIOSurfaceContextCreateImage");
sIOSurfaceContextGetSurface = GET_CGSYM(sIOSurfaceContextGetSurface, "CGIOSurfaceContextGetSurface");
if (!sCreate || !sGetID || !sLookup || !sTexImage || !sGetBaseAddress ||
!sGetBaseAddressOfPlane || !sPlaneCount ||
!kPropWidth || !kPropHeight || !kPropBytesPerElem || !kPropIsGlobal ||
!sLock || !sUnlock || !sIncrementUseCount || !sDecrementUseCount ||
!sWidth || !sHeight || !kPropBytesPerRow ||
!sBytesPerRow || !sGetPropertyMaximum || !sCVPixelBufferGetIOSurface) {
CloseLibrary();
}
}
void MacIOSurfaceLib::CloseLibrary() {
if (sIOSurfaceFramework) {
dlclose(sIOSurfaceFramework);
}
if (sOpenGLFramework) {
dlclose(sOpenGLFramework);
}
if (sCoreVideoFramework) {
dlclose(sCoreVideoFramework);
}
sIOSurfaceFramework = nullptr;
sOpenGLFramework = nullptr;
sCoreVideoFramework = nullptr;
}
MacIOSurface::MacIOSurface(const void* aIOSurfacePtr,
double aContentsScaleFactor, bool aHasAlpha)
: mIOSurfacePtr(aIOSurfacePtr)
, mContentsScaleFactor(aContentsScaleFactor)
, mHasAlpha(aHasAlpha)
{
CFRetain(mIOSurfacePtr);
IncrementUseCount();
}
MacIOSurface::~MacIOSurface() {
DecrementUseCount();
CFRelease(mIOSurfacePtr);
}
already_AddRefed<MacIOSurface> MacIOSurface::CreateIOSurface(int aWidth, int aHeight,
double aContentsScaleFactor,
bool aHasAlpha) {
if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0)
return nullptr;
CFMutableDictionaryRef props = ::CFDictionaryCreateMutable(
kCFAllocatorDefault, 4,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!props)
return nullptr;
MOZ_ASSERT((size_t)aWidth <= GetMaxWidth());
MOZ_ASSERT((size_t)aHeight <= GetMaxHeight());
int32_t bytesPerElem = 4;
size_t intScaleFactor = ceil(aContentsScaleFactor);
aWidth *= intScaleFactor;
aHeight *= intScaleFactor;
CFNumberRef cfWidth = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aWidth);
CFNumberRef cfHeight = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aHeight);
CFNumberRef cfBytesPerElem = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &bytesPerElem);
::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropWidth,
cfWidth);
::CFRelease(cfWidth);
::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropHeight,
cfHeight);
::CFRelease(cfHeight);
::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropBytesPerElem,
cfBytesPerElem);
::CFRelease(cfBytesPerElem);
::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropIsGlobal,
kCFBooleanTrue);
IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceCreate(props);
::CFRelease(props);
if (!surfaceRef)
return nullptr;
RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
if (!ioSurface) {
::CFRelease(surfaceRef);
return nullptr;
}
// Release the IOSurface because MacIOSurface retained it
CFRelease(surfaceRef);
return ioSurface.forget();
}
already_AddRefed<MacIOSurface> MacIOSurface::LookupSurface(IOSurfaceID aIOSurfaceID,
double aContentsScaleFactor,
bool aHasAlpha) {
if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0)
return nullptr;
IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceLookup(aIOSurfaceID);
if (!surfaceRef)
return nullptr;
RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
if (!ioSurface) {
::CFRelease(surfaceRef);
return nullptr;
}
// Release the IOSurface because MacIOSurface retained it
CFRelease(surfaceRef);
return ioSurface.forget();
}
IOSurfaceID MacIOSurface::GetIOSurfaceID() {
return MacIOSurfaceLib::IOSurfaceGetID(mIOSurfacePtr);
}
void* MacIOSurface::GetBaseAddress() {
return MacIOSurfaceLib::IOSurfaceGetBaseAddress(mIOSurfacePtr);
}
void* MacIOSurface::GetBaseAddressOfPlane(size_t aPlaneIndex)
{
return MacIOSurfaceLib::IOSurfaceGetBaseAddressOfPlane(mIOSurfacePtr,
aPlaneIndex);
}
size_t MacIOSurface::GetWidth(size_t plane) {
size_t intScaleFactor = ceil(mContentsScaleFactor);
return GetDevicePixelWidth(plane) / intScaleFactor;
}
size_t MacIOSurface::GetHeight(size_t plane) {
size_t intScaleFactor = ceil(mContentsScaleFactor);
return GetDevicePixelHeight(plane) / intScaleFactor;
}
size_t MacIOSurface::GetPlaneCount() {
return MacIOSurfaceLib::IOSurfaceGetPlaneCount(mIOSurfacePtr);
}
/*static*/ size_t MacIOSurface::GetMaxWidth() {
if (!MacIOSurfaceLib::isInit())
return -1;
return MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(MacIOSurfaceLib::kPropWidth);
}
/*static*/ size_t MacIOSurface::GetMaxHeight() {
if (!MacIOSurfaceLib::isInit())
return -1;
return MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(MacIOSurfaceLib::kPropHeight);
}
size_t MacIOSurface::GetDevicePixelWidth(size_t plane) {
return MacIOSurfaceLib::IOSurfaceGetWidth(mIOSurfacePtr, plane);
}
size_t MacIOSurface::GetDevicePixelHeight(size_t plane) {
return MacIOSurfaceLib::IOSurfaceGetHeight(mIOSurfacePtr, plane);
}
size_t MacIOSurface::GetBytesPerRow(size_t plane) {
return MacIOSurfaceLib::IOSurfaceGetBytesPerRow(mIOSurfacePtr, plane);
}
OSType MacIOSurface::GetPixelFormat() {
return MacIOSurfaceLib::IOSurfaceGetPixelFormat(mIOSurfacePtr);
}
void MacIOSurface::IncrementUseCount() {
MacIOSurfaceLib::IOSurfaceIncrementUseCount(mIOSurfacePtr);
}
void MacIOSurface::DecrementUseCount() {
MacIOSurfaceLib::IOSurfaceDecrementUseCount(mIOSurfacePtr);
}
#define READ_ONLY 0x1
void MacIOSurface::Lock(bool aReadOnly) {
MacIOSurfaceLib::IOSurfaceLock(mIOSurfacePtr, aReadOnly ? READ_ONLY : 0, nullptr);
}
void MacIOSurface::Unlock(bool aReadOnly) {
MacIOSurfaceLib::IOSurfaceUnlock(mIOSurfacePtr, aReadOnly ? READ_ONLY : 0, nullptr);
}
using mozilla::gfx::SourceSurface;
using mozilla::gfx::IntSize;
using mozilla::gfx::SurfaceFormat;
void
MacIOSurfaceBufferDeallocator(void* aClosure)
{
MOZ_ASSERT(aClosure);
delete [] static_cast<unsigned char*>(aClosure);
}
already_AddRefed<SourceSurface>
MacIOSurface::GetAsSurface() {
Lock();
size_t bytesPerRow = GetBytesPerRow();
size_t ioWidth = GetDevicePixelWidth();
size_t ioHeight = GetDevicePixelHeight();
unsigned char* ioData = (unsigned char*)GetBaseAddress();
unsigned char* dataCpy =
new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)];
for (size_t i = 0; i < ioHeight; i++) {
memcpy(dataCpy + i * bytesPerRow,
ioData + i * bytesPerRow, ioWidth * 4);
}
Unlock();
SurfaceFormat format = HasAlpha() ? mozilla::gfx::SurfaceFormat::B8G8R8A8 :
mozilla::gfx::SurfaceFormat::B8G8R8X8;
RefPtr<mozilla::gfx::DataSourceSurface> surf =
mozilla::gfx::Factory::CreateWrappingDataSourceSurface(dataCpy,
bytesPerRow,
IntSize(ioWidth, ioHeight),
format,
&MacIOSurfaceBufferDeallocator,
static_cast<void*>(dataCpy));
return surf.forget();
}
SurfaceFormat
MacIOSurface::GetFormat()
{
OSType pixelFormat = GetPixelFormat();
if (pixelFormat == '420v') {
return SurfaceFormat::NV12;
} else if (pixelFormat == '2vuy') {
return SurfaceFormat::YUV422;
} else {
return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
}
}
SurfaceFormat
MacIOSurface::GetReadFormat()
{
OSType pixelFormat = GetPixelFormat();
if (pixelFormat == '420v') {
return SurfaceFormat::NV12;
} else if (pixelFormat == '2vuy') {
return SurfaceFormat::R8G8B8X8;
} else {
return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
}
}
CGLError
MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, size_t plane)
{
MOZ_ASSERT(plane >= 0);
OSType pixelFormat = GetPixelFormat();
GLenum internalFormat;
GLenum format;
GLenum type;
if (pixelFormat == '420v') {
MOZ_ASSERT(GetPlaneCount() == 2);
MOZ_ASSERT(plane < 2);
if (plane == 0) {
internalFormat = format = GL_LUMINANCE;
} else {
internalFormat = format = GL_LUMINANCE_ALPHA;
}
type = GL_UNSIGNED_BYTE;
} else if (pixelFormat == '2vuy') {
MOZ_ASSERT(plane == 0);
internalFormat = GL_RGB;
format = LOCAL_GL_YCBCR_422_APPLE;
type = GL_UNSIGNED_SHORT_8_8_APPLE;
} else {
MOZ_ASSERT(plane == 0);
internalFormat = HasAlpha() ? GL_RGBA : GL_RGB;
format = GL_BGRA;
type = GL_UNSIGNED_INT_8_8_8_8_REV;
}
CGLError temp = MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx,
GL_TEXTURE_RECTANGLE_ARB,
internalFormat,
GetDevicePixelWidth(plane),
GetDevicePixelHeight(plane),
format,
type,
mIOSurfacePtr, plane);
return temp;
}
static
CGColorSpaceRef CreateSystemColorSpace() {
CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
if (!cspace) {
cspace = ::CGColorSpaceCreateDeviceRGB();
}
return cspace;
}
CGContextRef MacIOSurface::CreateIOSurfaceContext() {
CGColorSpaceRef cspace = CreateSystemColorSpace();
CGContextRef ref = MacIOSurfaceLib::IOSurfaceContextCreate(mIOSurfacePtr,
GetDevicePixelWidth(),
GetDevicePixelHeight(),
8, 32, cspace, 0x2002);
::CGColorSpaceRelease(cspace);
return ref;
}
CGImageRef MacIOSurface::CreateImageFromIOSurfaceContext(CGContextRef aContext) {
if (!MacIOSurfaceLib::isInit())
return nullptr;
return MacIOSurfaceLib::IOSurfaceContextCreateImage(aContext);
}
already_AddRefed<MacIOSurface> MacIOSurface::IOSurfaceContextGetSurface(CGContextRef aContext,
double aContentsScaleFactor,
bool aHasAlpha) {
if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0)
return nullptr;
IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceContextGetSurface(aContext);
if (!surfaceRef)
return nullptr;
RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
if (!ioSurface) {
::CFRelease(surfaceRef);
return nullptr;
}
return ioSurface.forget();
}
CGContextType GetContextType(CGContextRef ref)
{
if (!MacIOSurfaceLib::isInit() || !MacIOSurfaceLib::sCGContextGetTypePtr)
return CG_CONTEXT_TYPE_UNKNOWN;
unsigned int type = MacIOSurfaceLib::sCGContextGetTypePtr(ref);
if (type == CG_CONTEXT_TYPE_BITMAP) {
return CG_CONTEXT_TYPE_BITMAP;
} else if (type == CG_CONTEXT_TYPE_IOSURFACE) {
return CG_CONTEXT_TYPE_IOSURFACE;
} else {
return CG_CONTEXT_TYPE_UNKNOWN;
}
}

222
gfx/2d/MacIOSurface.h Normal file
View file

@ -0,0 +1,222 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:set ts=2 sts=2 sw=2 et cin:
/* 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/. */
#ifndef MacIOSurface_h__
#define MacIOSurface_h__
#ifdef XP_DARWIN
#include <QuartzCore/QuartzCore.h>
#include <CoreVideo/CoreVideo.h>
#include <dlfcn.h>
struct _CGLContextObject;
typedef _CGLContextObject* CGLContextObj;
typedef struct CGContext* CGContextRef;
typedef struct CGImage* CGImageRef;
typedef uint32_t IOSurfaceID;
#ifdef XP_IOS
typedef kern_return_t IOReturn;
typedef int CGLError;
#endif
typedef CFTypeRef IOSurfacePtr;
typedef IOSurfacePtr (*IOSurfaceCreateFunc) (CFDictionaryRef properties);
typedef IOSurfacePtr (*IOSurfaceLookupFunc) (uint32_t io_surface_id);
typedef IOSurfaceID (*IOSurfaceGetIDFunc)(IOSurfacePtr io_surface);
typedef void (*IOSurfaceVoidFunc)(IOSurfacePtr io_surface);
typedef IOReturn (*IOSurfaceLockFunc)(IOSurfacePtr io_surface, uint32_t options,
uint32_t *seed);
typedef IOReturn (*IOSurfaceUnlockFunc)(IOSurfacePtr io_surface,
uint32_t options, uint32_t *seed);
typedef void* (*IOSurfaceGetBaseAddressFunc)(IOSurfacePtr io_surface);
typedef void* (*IOSurfaceGetBaseAddressOfPlaneFunc)(IOSurfacePtr io_surface,
size_t planeIndex);
typedef size_t (*IOSurfaceSizeTFunc)(IOSurfacePtr io_surface);
typedef size_t (*IOSurfaceSizePlaneTFunc)(IOSurfacePtr io_surface, size_t plane);
typedef size_t (*IOSurfaceGetPropertyMaximumFunc) (CFStringRef property);
typedef CGLError (*CGLTexImageIOSurface2DFunc) (CGLContextObj ctxt,
GLenum target, GLenum internalFormat,
GLsizei width, GLsizei height,
GLenum format, GLenum type,
IOSurfacePtr ioSurface, GLuint plane);
typedef CGContextRef (*IOSurfaceContextCreateFunc)(CFTypeRef io_surface,
unsigned width, unsigned height,
unsigned bitsPerComponent, unsigned bytes,
CGColorSpaceRef colorSpace, CGBitmapInfo bitmapInfo);
typedef CGImageRef (*IOSurfaceContextCreateImageFunc)(CGContextRef ref);
typedef IOSurfacePtr (*IOSurfaceContextGetSurfaceFunc)(CGContextRef ref);
typedef IOSurfacePtr (*CVPixelBufferGetIOSurfaceFunc)(
CVPixelBufferRef pixelBuffer);
typedef OSType (*IOSurfacePixelFormatFunc)(IOSurfacePtr io_surface);
#ifdef XP_MACOSX
#import <OpenGL/OpenGL.h>
#else
#import <OpenGLES/ES2/gl.h>
#endif
#include "2D.h"
#include "mozilla/RefPtr.h"
#include "mozilla/RefCounted.h"
enum CGContextType {
CG_CONTEXT_TYPE_UNKNOWN = 0,
// These are found by inspection, it's possible they could be changed
CG_CONTEXT_TYPE_BITMAP = 4,
CG_CONTEXT_TYPE_IOSURFACE = 8
};
CGContextType GetContextType(CGContextRef ref);
class MacIOSurface final : public mozilla::external::AtomicRefCounted<MacIOSurface> {
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(MacIOSurface)
typedef mozilla::gfx::SourceSurface SourceSurface;
// The usage count of the IOSurface is increased by 1 during the lifetime
// of the MacIOSurface instance.
// MacIOSurface holds a reference to the corresponding IOSurface.
static already_AddRefed<MacIOSurface> CreateIOSurface(int aWidth, int aHeight,
double aContentsScaleFactor = 1.0,
bool aHasAlpha = true);
static void ReleaseIOSurface(MacIOSurface *aIOSurface);
static already_AddRefed<MacIOSurface> LookupSurface(IOSurfaceID aSurfaceID,
double aContentsScaleFactor = 1.0,
bool aHasAlpha = true);
explicit MacIOSurface(const void *aIOSurfacePtr,
double aContentsScaleFactor = 1.0,
bool aHasAlpha = true);
~MacIOSurface();
IOSurfaceID GetIOSurfaceID();
void *GetBaseAddress();
void *GetBaseAddressOfPlane(size_t planeIndex);
size_t GetPlaneCount();
OSType GetPixelFormat();
// GetWidth() and GetHeight() return values in "display pixels". A
// "display pixel" is the smallest fully addressable part of a display.
// But in HiDPI modes each "display pixel" corresponds to more than one
// device pixel. Use GetDevicePixel**() to get device pixels.
size_t GetWidth(size_t plane = 0);
size_t GetHeight(size_t plane = 0);
double GetContentsScaleFactor() { return mContentsScaleFactor; }
size_t GetDevicePixelWidth(size_t plane = 0);
size_t GetDevicePixelHeight(size_t plane = 0);
size_t GetBytesPerRow(size_t plane = 0);
void Lock(bool aReadOnly = true);
void Unlock(bool aReadOnly = true);
void IncrementUseCount();
void DecrementUseCount();
bool HasAlpha() { return mHasAlpha; }
mozilla::gfx::SurfaceFormat GetFormat();
mozilla::gfx::SurfaceFormat GetReadFormat();
// We would like to forward declare NSOpenGLContext, but it is an @interface
// and this file is also used from c++, so we use a void *.
CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, size_t plane = 0);
already_AddRefed<SourceSurface> GetAsSurface();
CGContextRef CreateIOSurfaceContext();
// FIXME This doesn't really belong here
static CGImageRef CreateImageFromIOSurfaceContext(CGContextRef aContext);
static already_AddRefed<MacIOSurface> IOSurfaceContextGetSurface(CGContextRef aContext,
double aContentsScaleFactor = 1.0,
bool aHasAlpha = true);
static size_t GetMaxWidth();
static size_t GetMaxHeight();
private:
friend class nsCARenderer;
const void* mIOSurfacePtr;
double mContentsScaleFactor;
bool mHasAlpha;
};
class MacIOSurfaceLib {
public:
MacIOSurfaceLib() = delete;
static void *sIOSurfaceFramework;
static void *sOpenGLFramework;
static void *sCoreGraphicsFramework;
static void *sCoreVideoFramework;
static bool isLoaded;
static IOSurfaceCreateFunc sCreate;
static IOSurfaceGetIDFunc sGetID;
static IOSurfaceLookupFunc sLookup;
static IOSurfaceGetBaseAddressFunc sGetBaseAddress;
static IOSurfaceGetBaseAddressOfPlaneFunc sGetBaseAddressOfPlane;
static IOSurfaceSizeTFunc sPlaneCount;
static IOSurfaceLockFunc sLock;
static IOSurfaceUnlockFunc sUnlock;
static IOSurfaceVoidFunc sIncrementUseCount;
static IOSurfaceVoidFunc sDecrementUseCount;
static IOSurfaceSizePlaneTFunc sWidth;
static IOSurfaceSizePlaneTFunc sHeight;
static IOSurfaceSizePlaneTFunc sBytesPerRow;
static IOSurfaceGetPropertyMaximumFunc sGetPropertyMaximum;
static CGLTexImageIOSurface2DFunc sTexImage;
static IOSurfaceContextCreateFunc sIOSurfaceContextCreate;
static IOSurfaceContextCreateImageFunc sIOSurfaceContextCreateImage;
static IOSurfaceContextGetSurfaceFunc sIOSurfaceContextGetSurface;
static CVPixelBufferGetIOSurfaceFunc sCVPixelBufferGetIOSurface;
static IOSurfacePixelFormatFunc sPixelFormat;
static CFStringRef kPropWidth;
static CFStringRef kPropHeight;
static CFStringRef kPropBytesPerElem;
static CFStringRef kPropBytesPerRow;
static CFStringRef kPropIsGlobal;
static bool isInit();
static CFStringRef GetIOConst(const char* symbole);
static IOSurfacePtr IOSurfaceCreate(CFDictionaryRef properties);
static IOSurfacePtr IOSurfaceLookup(IOSurfaceID aIOSurfaceID);
static IOSurfaceID IOSurfaceGetID(IOSurfacePtr aIOSurfacePtr);
static void* IOSurfaceGetBaseAddress(IOSurfacePtr aIOSurfacePtr);
static void* IOSurfaceGetBaseAddressOfPlane(IOSurfacePtr aIOSurfacePtr,
size_t aPlaneIndex);
static size_t IOSurfaceGetPlaneCount(IOSurfacePtr aIOSurfacePtr);
static size_t IOSurfaceGetWidth(IOSurfacePtr aIOSurfacePtr, size_t plane);
static size_t IOSurfaceGetHeight(IOSurfacePtr aIOSurfacePtr, size_t plane);
static size_t IOSurfaceGetBytesPerRow(IOSurfacePtr aIOSurfacePtr, size_t plane);
static size_t IOSurfaceGetPropertyMaximum(CFStringRef property);
static IOReturn IOSurfaceLock(IOSurfacePtr aIOSurfacePtr,
uint32_t options, uint32_t *seed);
static IOReturn IOSurfaceUnlock(IOSurfacePtr aIOSurfacePtr,
uint32_t options, uint32_t *seed);
static void IOSurfaceIncrementUseCount(IOSurfacePtr aIOSurfacePtr);
static void IOSurfaceDecrementUseCount(IOSurfacePtr aIOSurfacePtr);
static CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt,
GLenum target, GLenum internalFormat,
GLsizei width, GLsizei height,
GLenum format, GLenum type,
IOSurfacePtr ioSurface, GLuint plane);
static CGContextRef IOSurfaceContextCreate(IOSurfacePtr aIOSurfacePtr,
unsigned aWidth, unsigned aHeight,
unsigned aBitsPerCompoent, unsigned aBytes,
CGColorSpaceRef aColorSpace, CGBitmapInfo bitmapInfo);
static CGImageRef IOSurfaceContextCreateImage(CGContextRef ref);
static IOSurfacePtr IOSurfaceContextGetSurface(CGContextRef ref);
static IOSurfacePtr CVPixelBufferGetIOSurface(CVPixelBufferRef apixelBuffer);
static OSType IOSurfaceGetPixelFormat(IOSurfacePtr aIOSurfacePtr);
static unsigned int (*sCGContextGetTypePtr) (CGContextRef);
static void LoadLibrary();
static void CloseLibrary();
// Static deconstructor
static class LibraryUnloader {
public:
~LibraryUnloader() {
CloseLibrary();
}
} sLibraryUnloader;
};
#endif
#endif

134
gfx/2d/Matrix.cpp Normal file
View file

@ -0,0 +1,134 @@
/* -*- 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 "Matrix.h"
#include "Quaternion.h"
#include "Tools.h"
#include <algorithm>
#include <ostream>
#include <math.h>
#include <float.h> // for FLT_EPSILON
#include "mozilla/FloatingPoint.h" // for UnspecifiedNaN
using namespace std;
namespace mozilla {
namespace gfx {
/* Force small values to zero. We do this to avoid having sin(360deg)
* evaluate to a tiny but nonzero value.
*/
double
FlushToZero(double aVal)
{
// XXX Is double precision really necessary here
if (-FLT_EPSILON < aVal && aVal < FLT_EPSILON) {
return 0.0f;
} else {
return aVal;
}
}
/* Computes tan(aTheta). For values of aTheta such that tan(aTheta) is
* undefined or very large, SafeTangent returns a manageably large value
* of the correct sign.
*/
double
SafeTangent(double aTheta)
{
// XXX Is double precision really necessary here
const double kEpsilon = 0.0001;
/* tan(theta) = sin(theta)/cos(theta); problems arise when
* cos(theta) is too close to zero. Limit cos(theta) to the
* range [-1, -epsilon] U [epsilon, 1].
*/
double sinTheta = sin(aTheta);
double cosTheta = cos(aTheta);
if (cosTheta >= 0 && cosTheta < kEpsilon) {
cosTheta = kEpsilon;
} else if (cosTheta < 0 && cosTheta >= -kEpsilon) {
cosTheta = -kEpsilon;
}
return FlushToZero(sinTheta / cosTheta);
}
std::ostream&
operator<<(std::ostream& aStream, const Matrix& aMatrix)
{
return aStream << "[ " << aMatrix._11
<< " " << aMatrix._12
<< "; " << aMatrix._21
<< " " << aMatrix._22
<< "; " << aMatrix._31
<< " " << aMatrix._32
<< "; ]";
}
Matrix
Matrix::Rotation(Float aAngle)
{
Matrix newMatrix;
Float s = sinf(aAngle);
Float c = cosf(aAngle);
newMatrix._11 = c;
newMatrix._12 = s;
newMatrix._21 = -s;
newMatrix._22 = c;
return newMatrix;
}
Rect
Matrix::TransformBounds(const Rect &aRect) const
{
int i;
Point quad[4];
Float min_x, max_x;
Float min_y, max_y;
quad[0] = TransformPoint(aRect.TopLeft());
quad[1] = TransformPoint(aRect.TopRight());
quad[2] = TransformPoint(aRect.BottomLeft());
quad[3] = TransformPoint(aRect.BottomRight());
min_x = max_x = quad[0].x;
min_y = max_y = quad[0].y;
for (i = 1; i < 4; i++) {
if (quad[i].x < min_x)
min_x = quad[i].x;
if (quad[i].x > max_x)
max_x = quad[i].x;
if (quad[i].y < min_y)
min_y = quad[i].y;
if (quad[i].y > max_y)
max_y = quad[i].y;
}
return Rect(min_x, min_y, max_x - min_x, max_y - min_y);
}
Matrix&
Matrix::NudgeToIntegers()
{
NudgeToInteger(&_11);
NudgeToInteger(&_12);
NudgeToInteger(&_21);
NudgeToInteger(&_22);
NudgeToInteger(&_31);
NudgeToInteger(&_32);
return *this;
}
} // namespace gfx
} // namespace mozilla

1676
gfx/2d/Matrix.h Normal file

File diff suppressed because it is too large Load diff

26
gfx/2d/MatrixFwd.h Normal file
View file

@ -0,0 +1,26 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_MATRIX_FWD_H_
#define MOZILLA_GFX_MATRIX_FWD_H_
// Forward declare enough things to define the typedef |Matrix4x4|.
namespace mozilla {
namespace gfx {
struct UnknownUnits;
template<class SourceUnits, class TargetUnits>
class Matrix4x4Typed;
typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4;
} // namespace gfx
} // namespace mozilla
#endif

View file

@ -0,0 +1,288 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "NativeFontResourceDWrite.h"
#include <unordered_map>
#include "DrawTargetD2D1.h"
#include "Logging.h"
#include "mozilla/RefPtr.h"
namespace mozilla {
namespace gfx {
static Atomic<uint64_t> sNextFontFileKey;
static std::unordered_map<uint64_t, IDWriteFontFileStream*> sFontFileStreams;
class DWriteFontFileLoader : public IDWriteFontFileLoader
{
public:
DWriteFontFileLoader()
{
}
// IUnknown interface
IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject)
{
if (iid == __uuidof(IDWriteFontFileLoader)) {
*ppObject = static_cast<IDWriteFontFileLoader*>(this);
return S_OK;
} else if (iid == __uuidof(IUnknown)) {
*ppObject = static_cast<IUnknown*>(this);
return S_OK;
} else {
return E_NOINTERFACE;
}
}
IFACEMETHOD_(ULONG, AddRef)()
{
return 1;
}
IFACEMETHOD_(ULONG, Release)()
{
return 1;
}
// IDWriteFontFileLoader methods
/**
* Important! Note the key here has to be a uint64_t that will have been
* generated by incrementing sNextFontFileKey.
*/
virtual HRESULT STDMETHODCALLTYPE
CreateStreamFromKey(void const* fontFileReferenceKey,
UINT32 fontFileReferenceKeySize,
OUT IDWriteFontFileStream** fontFileStream);
/**
* Gets the singleton loader instance. Note that when using this font
* loader, the key must be a uint64_t that has been generated by incrementing
* sNextFontFileKey.
* Also note that this is _not_ threadsafe.
*/
static IDWriteFontFileLoader* Instance()
{
if (!mInstance) {
mInstance = new DWriteFontFileLoader();
DrawTargetD2D1::GetDWriteFactory()->
RegisterFontFileLoader(mInstance);
}
return mInstance;
}
private:
static IDWriteFontFileLoader* mInstance;
};
class DWriteFontFileStream : public IDWriteFontFileStream
{
public:
/**
* Used by the FontFileLoader to create a new font stream,
* this font stream is created from data in memory. The memory
* passed may be released after object creation, it will be
* copied internally.
*
* @param aData Font data
*/
DWriteFontFileStream(uint8_t *aData, uint32_t aSize, uint64_t aFontFileKey);
~DWriteFontFileStream();
// IUnknown interface
IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject)
{
if (iid == __uuidof(IDWriteFontFileStream)) {
*ppObject = static_cast<IDWriteFontFileStream*>(this);
return S_OK;
} else if (iid == __uuidof(IUnknown)) {
*ppObject = static_cast<IUnknown*>(this);
return S_OK;
} else {
return E_NOINTERFACE;
}
}
IFACEMETHOD_(ULONG, AddRef)()
{
++mRefCnt;
return mRefCnt;
}
IFACEMETHOD_(ULONG, Release)()
{
--mRefCnt;
if (mRefCnt == 0) {
delete this;
return 0;
}
return mRefCnt;
}
// IDWriteFontFileStream methods
virtual HRESULT STDMETHODCALLTYPE ReadFileFragment(void const** fragmentStart,
UINT64 fileOffset,
UINT64 fragmentSize,
OUT void** fragmentContext);
virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext);
virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize);
virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime);
private:
std::vector<uint8_t> mData;
uint32_t mRefCnt;
uint64_t mFontFileKey;
};
IDWriteFontFileLoader* DWriteFontFileLoader::mInstance = nullptr;
HRESULT STDMETHODCALLTYPE
DWriteFontFileLoader::CreateStreamFromKey(const void *fontFileReferenceKey,
UINT32 fontFileReferenceKeySize,
IDWriteFontFileStream **fontFileStream)
{
if (!fontFileReferenceKey || !fontFileStream) {
return E_POINTER;
}
uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey);
auto found = sFontFileStreams.find(fontFileKey);
if (found == sFontFileStreams.end()) {
*fontFileStream = nullptr;
return E_FAIL;
}
found->second->AddRef();
*fontFileStream = found->second;
return S_OK;
}
DWriteFontFileStream::DWriteFontFileStream(uint8_t *aData, uint32_t aSize,
uint64_t aFontFileKey)
: mRefCnt(0)
, mFontFileKey(aFontFileKey)
{
mData.resize(aSize);
memcpy(&mData.front(), aData, aSize);
}
DWriteFontFileStream::~DWriteFontFileStream()
{
sFontFileStreams.erase(mFontFileKey);
}
HRESULT STDMETHODCALLTYPE
DWriteFontFileStream::GetFileSize(UINT64 *fileSize)
{
*fileSize = mData.size();
return S_OK;
}
HRESULT STDMETHODCALLTYPE
DWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE
DWriteFontFileStream::ReadFileFragment(const void **fragmentStart,
UINT64 fileOffset,
UINT64 fragmentSize,
void **fragmentContext)
{
// We are required to do bounds checking.
if (fileOffset + fragmentSize > mData.size()) {
return E_FAIL;
}
// truncate the 64 bit fileOffset to size_t sized index into mData
size_t index = static_cast<size_t>(fileOffset);
// We should be alive for the duration of this.
*fragmentStart = &mData[index];
*fragmentContext = nullptr;
return S_OK;
}
void STDMETHODCALLTYPE
DWriteFontFileStream::ReleaseFileFragment(void *fragmentContext)
{
}
/* static */
already_AddRefed<NativeFontResourceDWrite>
NativeFontResourceDWrite::Create(uint8_t *aFontData, uint32_t aDataLength,
bool aNeedsCairo)
{
IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory();
if (!factory) {
gfxWarning() << "Failed to get DWrite Factory.";
return nullptr;
}
uint64_t fontFileKey = sNextFontFileKey++;
RefPtr<IDWriteFontFileStream> ffsRef =
new DWriteFontFileStream(aFontData, aDataLength, fontFileKey);
sFontFileStreams[fontFileKey] = ffsRef;
RefPtr<IDWriteFontFile> fontFile;
HRESULT hr =
factory->CreateCustomFontFileReference(&fontFileKey, sizeof(fontFileKey),
DWriteFontFileLoader::Instance(),
getter_AddRefs(fontFile));
if (FAILED(hr)) {
gfxWarning() << "Failed to load font file from data!";
return nullptr;
}
BOOL isSupported;
DWRITE_FONT_FILE_TYPE fileType;
DWRITE_FONT_FACE_TYPE faceType;
UINT32 numberOfFaces;
hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numberOfFaces);
if (FAILED(hr) || !isSupported) {
gfxWarning() << "Font file is not supported.";
return nullptr;
}
RefPtr<NativeFontResourceDWrite> fontResource =
new NativeFontResourceDWrite(factory, fontFile.forget(), faceType,
numberOfFaces, aNeedsCairo);
return fontResource.forget();
}
already_AddRefed<ScaledFont>
NativeFontResourceDWrite::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
const uint8_t* aInstanceData, uint32_t aInstanceDataLength)
{
if (aIndex >= mNumberOfFaces) {
gfxWarning() << "Font face index is too high for font resource.";
return nullptr;
}
IDWriteFontFile *fontFile = mFontFile;
RefPtr<IDWriteFontFace> fontFace;
if (FAILED(mFactory->CreateFontFace(mFaceType, 1, &fontFile, aIndex,
DWRITE_FONT_SIMULATIONS_NONE, getter_AddRefs(fontFace)))) {
gfxWarning() << "Failed to create font face from font file data.";
return nullptr;
}
RefPtr<ScaledFontBase> scaledFont = new ScaledFontDWrite(fontFace, aGlyphSize);
if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
gfxWarning() << "Unable to create cairo scaled font DWrite font.";
return nullptr;
}
return scaledFont.forget();
}
} // gfx
} // mozilla

View file

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_gfx_NativeFontResourceDWrite_h
#define mozilla_gfx_NativeFontResourceDWrite_h
#include <dwrite.h>
#include "2D.h"
#include "mozilla/AlreadyAddRefed.h"
namespace mozilla {
namespace gfx {
class NativeFontResourceDWrite final : public NativeFontResource
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceDWrite)
/**
* Creates a NativeFontResourceDWrite if data is valid. Note aFontData will be
* copied if required and so can be released after calling.
*
* @param aFontData the SFNT data.
* @param aDataLength length of data.
* @param aNeedsCairo whether the ScaledFont created needs a cairo scaled font
* @return Referenced NativeFontResourceDWrite or nullptr if invalid.
*/
static already_AddRefed<NativeFontResourceDWrite>
Create(uint8_t *aFontData, uint32_t aDataLength, bool aNeedsCairo);
already_AddRefed<ScaledFont>
CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final;
private:
NativeFontResourceDWrite(IDWriteFactory *aFactory,
already_AddRefed<IDWriteFontFile> aFontFile,
DWRITE_FONT_FACE_TYPE aFaceType,
uint32_t aNumberOfFaces, bool aNeedsCairo)
: mFactory(aFactory), mFontFile(aFontFile), mFaceType(aFaceType)
, mNumberOfFaces(aNumberOfFaces), mNeedsCairo(aNeedsCairo)
{}
IDWriteFactory *mFactory;
RefPtr<IDWriteFontFile> mFontFile;
DWRITE_FONT_FACE_TYPE mFaceType;
uint32_t mNumberOfFaces;
bool mNeedsCairo;
};
} // gfx
} // mozilla
#endif // mozilla_gfx_NativeFontResourceDWrite_h

View file

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "NativeFontResourceGDI.h"
#include "Logging.h"
#include "mozilla/RefPtr.h"
#include "ScaledFontWin.h"
namespace mozilla {
namespace gfx {
/* static */
already_AddRefed<NativeFontResourceGDI>
NativeFontResourceGDI::Create(uint8_t *aFontData, uint32_t aDataLength,
bool aNeedsCairo)
{
DWORD numberOfFontsAdded;
HANDLE fontResourceHandle = ::AddFontMemResourceEx(aFontData, aDataLength,
0, &numberOfFontsAdded);
if (!fontResourceHandle) {
gfxWarning() << "Failed to add memory font resource.";
return nullptr;
}
RefPtr<NativeFontResourceGDI> fontResouce =
new NativeFontResourceGDI(fontResourceHandle, aNeedsCairo);
return fontResouce.forget();
}
NativeFontResourceGDI::~NativeFontResourceGDI()
{
::RemoveFontMemResourceEx(mFontResourceHandle);
}
already_AddRefed<ScaledFont>
NativeFontResourceGDI::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
const uint8_t* aInstanceData, uint32_t aInstanceDataLength)
{
if (aInstanceDataLength < sizeof(LOGFONT)) {
gfxWarning() << "GDI scaled font instance data is truncated.";
return nullptr;
}
const LOGFONT* logFont = reinterpret_cast<const LOGFONT*>(aInstanceData);
// Constructor for ScaledFontWin dereferences and copies the LOGFONT, so we
// are safe to pass this reference.
RefPtr<ScaledFontBase> scaledFont = new ScaledFontWin(logFont, aGlyphSize);
if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
gfxWarning() << "Unable to create cairo scaled font GDI font.";
return nullptr;
}
return scaledFont.forget();
}
} // gfx
} // mozilla

View file

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_gfx_NativeFontResourceGDI_h
#define mozilla_gfx_NativeFontResourceGDI_h
#include <windows.h>
#include "2D.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Vector.h"
namespace mozilla {
namespace gfx {
class NativeFontResourceGDI final : public NativeFontResource
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceGDI)
/**
* Creates a NativeFontResourceGDI if data is valid. Note aFontData will be
* copied if required and so can be released after calling.
*
* @param aFontData the SFNT data.
* @param aDataLength length of data.
* @param aNeedsCairo whether the ScaledFont created need a cairo scaled font
* @return Referenced NativeFontResourceGDI or nullptr if invalid.
*/
static already_AddRefed<NativeFontResourceGDI>
Create(uint8_t *aFontData, uint32_t aDataLength, bool aNeedsCairo);
~NativeFontResourceGDI();
already_AddRefed<ScaledFont>
CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final;
private:
NativeFontResourceGDI(HANDLE aFontResourceHandle,
bool aNeedsCairo)
: mFontResourceHandle(aFontResourceHandle)
, mNeedsCairo(aNeedsCairo)
{}
HANDLE mFontResourceHandle;
bool mNeedsCairo;
};
} // gfx
} // mozilla
#endif // mozilla_gfx_NativeFontResourceGDI_h

View file

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "NativeFontResourceMac.h"
#include "Types.h"
#include "mozilla/RefPtr.h"
#ifdef MOZ_WIDGET_UIKIT
#include <CoreFoundation/CoreFoundation.h>
#endif
namespace mozilla {
namespace gfx {
/* static */
already_AddRefed<NativeFontResourceMac>
NativeFontResourceMac::Create(uint8_t *aFontData, uint32_t aDataLength)
{
// copy font data
CFDataRef data = CFDataCreate(kCFAllocatorDefault, aFontData, aDataLength);
if (!data) {
return nullptr;
}
// create a provider
CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
// release our reference to the CFData, provider keeps it alive
CFRelease(data);
// create the font object
CGFontRef fontRef = CGFontCreateWithDataProvider(provider);
// release our reference, font will keep it alive as long as needed
CGDataProviderRelease(provider);
if (!fontRef) {
return nullptr;
}
// passes ownership of fontRef to the NativeFontResourceMac instance
RefPtr<NativeFontResourceMac> fontResource =
new NativeFontResourceMac(fontRef);
return fontResource.forget();
}
already_AddRefed<ScaledFont>
NativeFontResourceMac::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
const uint8_t* aInstanceData, uint32_t aInstanceDataLength)
{
RefPtr<ScaledFontBase> scaledFont = new ScaledFontMac(mFontRef, aGlyphSize);
if (!scaledFont->PopulateCairoScaledFont()) {
gfxWarning() << "Unable to create cairo scaled Mac font.";
return nullptr;
}
return scaledFont.forget();
}
} // gfx
} // mozilla

View file

@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_gfx_NativeFontResourceMac_h
#define mozilla_gfx_NativeFontResourceMac_h
#include "2D.h"
#include "mozilla/AlreadyAddRefed.h"
#include "ScaledFontMac.h"
namespace mozilla {
namespace gfx {
class NativeFontResourceMac final : public NativeFontResource
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceMac)
static already_AddRefed<NativeFontResourceMac>
Create(uint8_t *aFontData, uint32_t aDataLength);
already_AddRefed<ScaledFont>
CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final;
~NativeFontResourceMac()
{
CFRelease(mFontRef);
}
private:
explicit NativeFontResourceMac(CGFontRef aFontRef) : mFontRef(aFontRef) {}
CGFontRef mFontRef;
};
} // gfx
} // mozilla
#endif // mozilla_gfx_NativeFontResourceMac_h

43
gfx/2d/NumericTools.h Normal file
View file

@ -0,0 +1,43 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_NUMERICTOOLS_H_
#define MOZILLA_GFX_NUMERICTOOLS_H_
namespace mozilla {
// XXX - Move these into mfbt/MathAlgorithms.h?
// Returns the largest multiple of aMultiplied that's <= x.
// Same as int32_t(floor(double(x) / aMultiplier)) * aMultiplier,
// but faster.
inline int32_t
RoundDownToMultiple(int32_t x, int32_t aMultiplier)
{
// We don't use float division + floor because that's hard for the compiler
// to optimize.
int mod = x % aMultiplier;
if (x > 0) {
return x - mod;
}
return mod ? x - aMultiplier - mod : x;
}
// Returns the smallest multiple of aMultiplied that's >= x.
// Same as int32_t(ceil(double(x) / aMultiplier)) * aMultiplier,
// but faster.
inline int32_t
RoundUpToMultiple(int32_t x, int32_t aMultiplier)
{
int mod = x % aMultiplier;
if (x > 0) {
return mod ? x + aMultiplier - mod : x;
}
return x - mod;
}
} // namespace mozilla
#endif /* MOZILLA_GFX_NUMERICTOOLS_H_ */

550
gfx/2d/Path.cpp Normal file
View file

@ -0,0 +1,550 @@
/* -*- 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 "2D.h"
#include "PathAnalysis.h"
#include "PathHelpers.h"
namespace mozilla {
namespace gfx {
static double CubicRoot(double aValue) {
if (aValue < 0.0) {
return -CubicRoot(-aValue);
}
else {
return pow(aValue, 1.0 / 3.0);
}
}
struct PointD : public BasePoint<double, PointD> {
typedef BasePoint<double, PointD> Super;
PointD() : Super() {}
PointD(double aX, double aY) : Super(aX, aY) {}
MOZ_IMPLICIT PointD(const Point& aPoint) : Super(aPoint.x, aPoint.y) {}
Point ToPoint() const {
return Point(static_cast<Float>(x), static_cast<Float>(y));
}
};
struct BezierControlPoints
{
BezierControlPoints() {}
BezierControlPoints(const PointD &aCP1, const PointD &aCP2,
const PointD &aCP3, const PointD &aCP4)
: mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4)
{
}
PointD mCP1, mCP2, mCP3, mCP4;
};
void
FlattenBezier(const BezierControlPoints &aPoints,
PathSink *aSink, double aTolerance);
Path::Path()
{
}
Path::~Path()
{
}
Float
Path::ComputeLength()
{
EnsureFlattenedPath();
return mFlattenedPath->ComputeLength();
}
Point
Path::ComputePointAtLength(Float aLength, Point* aTangent)
{
EnsureFlattenedPath();
return mFlattenedPath->ComputePointAtLength(aLength, aTangent);
}
void
Path::EnsureFlattenedPath()
{
if (!mFlattenedPath) {
mFlattenedPath = new FlattenedPath();
StreamToSink(mFlattenedPath);
}
}
// This is the maximum deviation we allow (with an additional ~20% margin of
// error) of the approximation from the actual Bezier curve.
const Float kFlatteningTolerance = 0.0001f;
void
FlattenedPath::MoveTo(const Point &aPoint)
{
MOZ_ASSERT(!mCalculatedLength);
FlatPathOp op;
op.mType = FlatPathOp::OP_MOVETO;
op.mPoint = aPoint;
mPathOps.push_back(op);
mLastMove = aPoint;
}
void
FlattenedPath::LineTo(const Point &aPoint)
{
MOZ_ASSERT(!mCalculatedLength);
FlatPathOp op;
op.mType = FlatPathOp::OP_LINETO;
op.mPoint = aPoint;
mPathOps.push_back(op);
}
void
FlattenedPath::BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3)
{
MOZ_ASSERT(!mCalculatedLength);
FlattenBezier(BezierControlPoints(CurrentPoint(), aCP1, aCP2, aCP3), this, kFlatteningTolerance);
}
void
FlattenedPath::QuadraticBezierTo(const Point &aCP1,
const Point &aCP2)
{
MOZ_ASSERT(!mCalculatedLength);
// We need to elevate the degree of this quadratic B<>zier to cubic, so we're
// going to add an intermediate control point, and recompute control point 1.
// The first and last control points remain the same.
// This formula can be found on http://fontforge.sourceforge.net/bezier.html
Point CP0 = CurrentPoint();
Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
Point CP3 = aCP2;
BezierTo(CP1, CP2, CP3);
}
void
FlattenedPath::Close()
{
MOZ_ASSERT(!mCalculatedLength);
LineTo(mLastMove);
}
void
FlattenedPath::Arc(const Point &aOrigin, float aRadius, float aStartAngle,
float aEndAngle, bool aAntiClockwise)
{
ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise);
}
Float
FlattenedPath::ComputeLength()
{
if (!mCalculatedLength) {
Point currentPoint;
for (uint32_t i = 0; i < mPathOps.size(); i++) {
if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) {
currentPoint = mPathOps[i].mPoint;
} else {
mCachedLength += Distance(currentPoint, mPathOps[i].mPoint);
currentPoint = mPathOps[i].mPoint;
}
}
mCalculatedLength = true;
}
return mCachedLength;
}
Point
FlattenedPath::ComputePointAtLength(Float aLength, Point *aTangent)
{
// We track the last point that -wasn't- in the same place as the current
// point so if we pass the edge of the path with a bunch of zero length
// paths we still get the correct tangent vector.
Point lastPointSinceMove;
Point currentPoint;
for (uint32_t i = 0; i < mPathOps.size(); i++) {
if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) {
if (Distance(currentPoint, mPathOps[i].mPoint)) {
lastPointSinceMove = currentPoint;
}
currentPoint = mPathOps[i].mPoint;
} else {
Float segmentLength = Distance(currentPoint, mPathOps[i].mPoint);
if (segmentLength) {
lastPointSinceMove = currentPoint;
if (segmentLength > aLength) {
Point currentVector = mPathOps[i].mPoint - currentPoint;
Point tangent = currentVector / segmentLength;
if (aTangent) {
*aTangent = tangent;
}
return currentPoint + tangent * aLength;
}
}
aLength -= segmentLength;
currentPoint = mPathOps[i].mPoint;
}
}
Point currentVector = currentPoint - lastPointSinceMove;
if (aTangent) {
if (hypotf(currentVector.x, currentVector.y)) {
*aTangent = currentVector / hypotf(currentVector.x, currentVector.y);
} else {
*aTangent = Point();
}
}
return currentPoint;
}
// This function explicitly permits aControlPoints to refer to the same object
// as either of the other arguments.
static void
SplitBezier(const BezierControlPoints &aControlPoints,
BezierControlPoints *aFirstSegmentControlPoints,
BezierControlPoints *aSecondSegmentControlPoints,
double t)
{
MOZ_ASSERT(aSecondSegmentControlPoints);
*aSecondSegmentControlPoints = aControlPoints;
PointD cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t;
PointD cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t;
PointD cp1aa = cp1a + (cp2a - cp1a) * t;
PointD cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t;
PointD cp2aa = cp2a + (cp3a - cp2a) * t;
PointD cp1aaa = cp1aa + (cp2aa - cp1aa) * t;
aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4;
if(aFirstSegmentControlPoints) {
aFirstSegmentControlPoints->mCP1 = aControlPoints.mCP1;
aFirstSegmentControlPoints->mCP2 = cp1a;
aFirstSegmentControlPoints->mCP3 = cp1aa;
aFirstSegmentControlPoints->mCP4 = cp1aaa;
}
aSecondSegmentControlPoints->mCP1 = cp1aaa;
aSecondSegmentControlPoints->mCP2 = cp2aa;
aSecondSegmentControlPoints->mCP3 = cp3a;
}
static void
FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints,
PathSink *aSink,
double aTolerance)
{
/* The algorithm implemented here is based on:
* http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf
*
* The basic premise is that for a small t the third order term in the
* equation of a cubic bezier curve is insignificantly small. This can
* then be approximated by a quadratic equation for which the maximum
* difference from a linear approximation can be much more easily determined.
*/
BezierControlPoints currentCP = aControlPoints;
double t = 0;
while (t < 1.0) {
PointD cp21 = currentCP.mCP2 - currentCP.mCP1;
PointD cp31 = currentCP.mCP3 - currentCP.mCP1;
/* To remove divisions and check for divide-by-zero, this is optimized from:
* Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y);
* t = 2 * Float(sqrt(aTolerance / (3. * std::abs(s3))));
*/
double cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x;
double h = hypot(cp21.x, cp21.y);
if (cp21x31 * h == 0) {
break;
}
double s3inv = h / cp21x31;
t = 2 * sqrt(aTolerance * std::abs(s3inv) / 3.);
if (t >= 1.0) {
break;
}
SplitBezier(currentCP, nullptr, &currentCP, t);
aSink->LineTo(currentCP.mCP1.ToPoint());
}
aSink->LineTo(currentCP.mCP4.ToPoint());
}
static inline void
FindInflectionApproximationRange(BezierControlPoints aControlPoints,
double *aMin, double *aMax, double aT,
double aTolerance)
{
SplitBezier(aControlPoints, nullptr, &aControlPoints, aT);
PointD cp21 = aControlPoints.mCP2 - aControlPoints.mCP1;
PointD cp41 = aControlPoints.mCP4 - aControlPoints.mCP1;
if (cp21.x == 0. && cp21.y == 0.) {
// In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = cp41.x - cp41.y.
// Use the absolute value so that Min and Max will correspond with the
// minimum and maximum of the range.
*aMin = aT - CubicRoot(std::abs(aTolerance / (cp41.x - cp41.y)));
*aMax = aT + CubicRoot(std::abs(aTolerance / (cp41.x - cp41.y)));
return;
}
double s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypot(cp21.x, cp21.y);
if (s3 == 0) {
// This means within the precision we have it can be approximated
// infinitely by a linear segment. Deal with this by specifying the
// approximation range as extending beyond the entire curve.
*aMin = -1.0;
*aMax = 2.0;
return;
}
double tf = CubicRoot(std::abs(aTolerance / s3));
*aMin = aT - tf * (1 - aT);
*aMax = aT + tf * (1 - aT);
}
/* Find the inflection points of a bezier curve. Will return false if the
* curve is degenerate in such a way that it is best approximated by a straight
* line.
*
* The below algorithm was written by Jeff Muizelaar <jmuizelaar@mozilla.com>, explanation follows:
*
* The lower inflection point is returned in aT1, the higher one in aT2. In the
* case of a single inflection point this will be in aT1.
*
* The method is inspired by the algorithm in "analysis of in?ection points for planar cubic bezier curve"
*
* Here are some differences between this algorithm and versions discussed elsewhere in the literature:
*
* zhang et. al compute a0, d0 and e0 incrementally using the follow formula:
*
* Point a0 = CP2 - CP1
* Point a1 = CP3 - CP2
* Point a2 = CP4 - CP1
*
* Point d0 = a1 - a0
* Point d1 = a2 - a1
* Point e0 = d1 - d0
*
* this avoids any multiplications and may or may not be faster than the approach take below.
*
* "fast, precise flattening of cubic bezier path and ofset curves" by hain et. al
* Point a = CP1 + 3 * CP2 - 3 * CP3 + CP4
* Point b = 3 * CP1 - 6 * CP2 + 3 * CP3
* Point c = -3 * CP1 + 3 * CP2
* Point d = CP1
* the a, b, c, d can be expressed in terms of a0, d0 and e0 defined above as:
* c = 3 * a0
* b = 3 * d0
* a = e0
*
*
* a = 3a = a.y * b.x - a.x * b.y
* b = 3b = a.y * c.x - a.x * c.y
* c = 9c = b.y * c.x - b.x * c.y
*
* The additional multiples of 3 cancel each other out as show below:
*
* x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
* x = (-3 * b + sqrt(3 * b * 3 * b - 4 * a * 3 * 9 * c / 3)) / (2 * 3 * a)
* x = 3 * (-b + sqrt(b * b - 4 * a * c)) / (2 * 3 * a)
* x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
*
* I haven't looked into whether the formulation of the quadratic formula in
* hain has any numerical advantages over the one used below.
*/
static inline void
FindInflectionPoints(const BezierControlPoints &aControlPoints,
double *aT1, double *aT2, uint32_t *aCount)
{
// Find inflection points.
// See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation
// of this approach.
PointD A = aControlPoints.mCP2 - aControlPoints.mCP1;
PointD B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1;
PointD C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1;
double a = B.x * C.y - B.y * C.x;
double b = A.x * C.y - A.y * C.x;
double c = A.x * B.y - A.y * B.x;
if (a == 0) {
// Not a quadratic equation.
if (b == 0) {
// Instead of a linear acceleration change we have a constant
// acceleration change. This means the equation has no solution
// and there are no inflection points, unless the constant is 0.
// In that case the curve is a straight line, essentially that means
// the easiest way to deal with is is by saying there's an inflection
// point at t == 0. The inflection point approximation range found will
// automatically extend into infinity.
if (c == 0) {
*aCount = 1;
*aT1 = 0;
return;
}
*aCount = 0;
return;
}
*aT1 = -c / b;
*aCount = 1;
return;
} else {
double discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
// No inflection points.
*aCount = 0;
} else if (discriminant == 0) {
*aCount = 1;
*aT1 = -b / (2 * a);
} else {
/* Use the following formula for computing the roots:
*
* q = -1/2 * (b + sign(b) * sqrt(b^2 - 4ac))
* t1 = q / a
* t2 = c / q
*/
double q = sqrt(discriminant);
if (b < 0) {
q = b - q;
} else {
q = b + q;
}
q *= -1./2;
*aT1 = q / a;
*aT2 = c / q;
if (*aT1 > *aT2) {
std::swap(*aT1, *aT2);
}
*aCount = 2;
}
}
return;
}
void
FlattenBezier(const BezierControlPoints &aControlPoints,
PathSink *aSink, double aTolerance)
{
double t1;
double t2;
uint32_t count;
FindInflectionPoints(aControlPoints, &t1, &t2, &count);
// Check that at least one of the inflection points is inside [0..1]
if (count == 0 || ((t1 < 0.0 || t1 >= 1.0) && (count == 1 || (t2 < 0.0 || t2 >= 1.0))) ) {
FlattenBezierCurveSegment(aControlPoints, aSink, aTolerance);
return;
}
double t1min = t1, t1max = t1, t2min = t2, t2max = t2;
BezierControlPoints remainingCP = aControlPoints;
// For both inflection points, calulate the range where they can be linearly
// approximated if they are positioned within [0,1]
if (count > 0 && t1 >= 0 && t1 < 1.0) {
FindInflectionApproximationRange(aControlPoints, &t1min, &t1max, t1, aTolerance);
}
if (count > 1 && t2 >= 0 && t2 < 1.0) {
FindInflectionApproximationRange(aControlPoints, &t2min, &t2max, t2, aTolerance);
}
BezierControlPoints nextCPs = aControlPoints;
BezierControlPoints prevCPs;
// Process ranges. [t1min, t1max] and [t2min, t2max] are approximated by line
// segments.
if (count == 1 && t1min <= 0 && t1max >= 1.0) {
// The whole range can be approximated by a line segment.
aSink->LineTo(aControlPoints.mCP4.ToPoint());
return;
}
if (t1min > 0) {
// Flatten the Bezier up until the first inflection point's approximation
// point.
SplitBezier(aControlPoints, &prevCPs,
&remainingCP, t1min);
FlattenBezierCurveSegment(prevCPs, aSink, aTolerance);
}
if (t1max >= 0 && t1max < 1.0 && (count == 1 || t2min > t1max)) {
// The second inflection point's approximation range begins after the end
// of the first, approximate the first inflection point by a line and
// subsequently flatten up until the end or the next inflection point.
SplitBezier(aControlPoints, nullptr, &nextCPs, t1max);
aSink->LineTo(nextCPs.mCP1.ToPoint());
if (count == 1 || (count > 1 && t2min >= 1.0)) {
// No more inflection points to deal with, flatten the rest of the curve.
FlattenBezierCurveSegment(nextCPs, aSink, aTolerance);
}
} else if (count > 1 && t2min > 1.0) {
// We've already concluded t2min <= t1max, so if this is true the
// approximation range for the first inflection point runs past the
// end of the curve, draw a line to the end and we're done.
aSink->LineTo(aControlPoints.mCP4.ToPoint());
return;
}
if (count > 1 && t2min < 1.0 && t2max > 0) {
if (t2min > 0 && t2min < t1max) {
// In this case the t2 approximation range starts inside the t1
// approximation range.
SplitBezier(aControlPoints, nullptr, &nextCPs, t1max);
aSink->LineTo(nextCPs.mCP1.ToPoint());
} else if (t2min > 0 && t1max > 0) {
SplitBezier(aControlPoints, nullptr, &nextCPs, t1max);
// Find a control points describing the portion of the curve between t1max and t2min.
double t2mina = (t2min - t1max) / (1 - t1max);
SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina);
FlattenBezierCurveSegment(prevCPs, aSink, aTolerance);
} else if (t2min > 0) {
// We have nothing interesting before t2min, find that bit and flatten it.
SplitBezier(aControlPoints, &prevCPs, &nextCPs, t2min);
FlattenBezierCurveSegment(prevCPs, aSink, aTolerance);
}
if (t2max < 1.0) {
// Flatten the portion of the curve after t2max
SplitBezier(aControlPoints, nullptr, &nextCPs, t2max);
// Draw a line to the start, this is the approximation between t2min and
// t2max.
aSink->LineTo(nextCPs.mCP1.ToPoint());
FlattenBezierCurveSegment(nextCPs, aSink, aTolerance);
} else {
// Our approximation range extends beyond the end of the curve.
aSink->LineTo(aControlPoints.mCP4.ToPoint());
return;
}
}
}
} // namespace gfx
} // namespace mozilla

57
gfx/2d/PathAnalysis.h Normal file
View file

@ -0,0 +1,57 @@
/* -*- 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 "2D.h"
#include <vector>
namespace mozilla {
namespace gfx {
struct FlatPathOp
{
enum OpType {
OP_MOVETO,
OP_LINETO,
};
OpType mType;
Point mPoint;
};
class FlattenedPath : public PathSink
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FlattenedPath)
FlattenedPath() : mCachedLength(0)
, mCalculatedLength(false)
{
}
virtual void MoveTo(const Point &aPoint);
virtual void LineTo(const Point &aPoint);
virtual void BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3);
virtual void QuadraticBezierTo(const Point &aCP1,
const Point &aCP2);
virtual void Close();
virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle,
float aEndAngle, bool aAntiClockwise = false);
virtual Point CurrentPoint() const { return mPathOps.empty() ? Point() : mPathOps[mPathOps.size() - 1].mPoint; }
Float ComputeLength();
Point ComputePointAtLength(Float aLength, Point *aTangent);
private:
Float mCachedLength;
bool mCalculatedLength;
Point mLastMove;
std::vector<FlatPathOp> mPathOps;
};
} // namespace gfx
} // namespace mozilla

435
gfx/2d/PathCG.cpp Normal file
View file

@ -0,0 +1,435 @@
/* -*- 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 "PathCG.h"
#include <math.h>
#include "Logging.h"
#include "PathHelpers.h"
namespace mozilla {
namespace gfx {
static inline Rect
CGRectToRect(CGRect rect)
{
return Rect(rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height);
}
static inline Point
CGPointToPoint(CGPoint point)
{
return Point(point.x, point.y);
}
static inline void
SetStrokeOptions(CGContextRef cg, const StrokeOptions &aStrokeOptions)
{
switch (aStrokeOptions.mLineCap)
{
case CapStyle::BUTT:
CGContextSetLineCap(cg, kCGLineCapButt);
break;
case CapStyle::ROUND:
CGContextSetLineCap(cg, kCGLineCapRound);
break;
case CapStyle::SQUARE:
CGContextSetLineCap(cg, kCGLineCapSquare);
break;
}
switch (aStrokeOptions.mLineJoin)
{
case JoinStyle::BEVEL:
CGContextSetLineJoin(cg, kCGLineJoinBevel);
break;
case JoinStyle::ROUND:
CGContextSetLineJoin(cg, kCGLineJoinRound);
break;
case JoinStyle::MITER:
case JoinStyle::MITER_OR_BEVEL:
CGContextSetLineJoin(cg, kCGLineJoinMiter);
break;
}
CGContextSetLineWidth(cg, aStrokeOptions.mLineWidth);
CGContextSetMiterLimit(cg, aStrokeOptions.mMiterLimit);
// XXX: rename mDashLength to dashLength
if (aStrokeOptions.mDashLength > 0) {
// we use a regular array instead of a std::vector here because we don't want to leak the <vector> include
CGFloat *dashes = new CGFloat[aStrokeOptions.mDashLength];
for (size_t i=0; i<aStrokeOptions.mDashLength; i++) {
dashes[i] = aStrokeOptions.mDashPattern[i];
}
CGContextSetLineDash(cg, aStrokeOptions.mDashOffset, dashes, aStrokeOptions.mDashLength);
delete[] dashes;
}
}
static inline CGAffineTransform
GfxMatrixToCGAffineTransform(const Matrix &m)
{
CGAffineTransform t;
t.a = m._11;
t.b = m._12;
t.c = m._21;
t.d = m._22;
t.tx = m._31;
t.ty = m._32;
return t;
}
PathBuilderCG::~PathBuilderCG()
{
CGPathRelease(mCGPath);
}
void
PathBuilderCG::MoveTo(const Point &aPoint)
{
if (!aPoint.IsFinite()) {
return;
}
CGPathMoveToPoint(mCGPath, nullptr, aPoint.x, aPoint.y);
}
void
PathBuilderCG::LineTo(const Point &aPoint)
{
if (!aPoint.IsFinite()) {
return;
}
if (CGPathIsEmpty(mCGPath))
MoveTo(aPoint);
else
CGPathAddLineToPoint(mCGPath, nullptr, aPoint.x, aPoint.y);
}
void
PathBuilderCG::BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3)
{
if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) {
return;
}
if (CGPathIsEmpty(mCGPath))
MoveTo(aCP1);
CGPathAddCurveToPoint(mCGPath, nullptr,
aCP1.x, aCP1.y,
aCP2.x, aCP2.y,
aCP3.x, aCP3.y);
}
void
PathBuilderCG::QuadraticBezierTo(const Point &aCP1,
const Point &aCP2)
{
if (!aCP1.IsFinite() || !aCP2.IsFinite()) {
return;
}
if (CGPathIsEmpty(mCGPath))
MoveTo(aCP1);
CGPathAddQuadCurveToPoint(mCGPath, nullptr,
aCP1.x, aCP1.y,
aCP2.x, aCP2.y);
}
void
PathBuilderCG::Close()
{
if (!CGPathIsEmpty(mCGPath))
CGPathCloseSubpath(mCGPath);
}
void
PathBuilderCG::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
Float aEndAngle, bool aAntiClockwise)
{
if (!aOrigin.IsFinite() || !IsFinite(aRadius) ||
!IsFinite(aStartAngle) || !IsFinite(aEndAngle)) {
return;
}
// Disabled for now due to a CG bug when using CGPathAddArc with stroke
// dashing and rotation transforms that are multiples of 90 degrees. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=949661#c8
#if 0
// Core Graphic's initial coordinate system is y-axis up, whereas Moz2D's is
// y-axis down. Core Graphics therefore considers "clockwise" to mean "sweep
// in the direction of decreasing angle" whereas Moz2D considers it to mean
// "sweep in the direction of increasing angle". In other words if this
// Moz2D method is instructed to sweep anti-clockwise we need to tell
// CGPathAddArc to sweep clockwise, and vice versa. Hence why we pass the
// value of aAntiClockwise directly to CGPathAddArc's "clockwise" bool
// parameter.
CGPathAddArc(mCGPath, nullptr,
aOrigin.x, aOrigin.y,
aRadius,
aStartAngle,
aEndAngle,
aAntiClockwise);
#endif
ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
aAntiClockwise);
}
Point
PathBuilderCG::CurrentPoint() const
{
Point ret;
if (!CGPathIsEmpty(mCGPath)) {
CGPoint pt = CGPathGetCurrentPoint(mCGPath);
ret.MoveTo(pt.x, pt.y);
}
return ret;
}
void
PathBuilderCG::EnsureActive(const Point &aPoint)
{
}
already_AddRefed<Path>
PathBuilderCG::Finish()
{
return MakeAndAddRef<PathCG>(mCGPath, mFillRule);
}
already_AddRefed<PathBuilder>
PathCG::CopyToBuilder(FillRule aFillRule) const
{
CGMutablePathRef path = CGPathCreateMutableCopy(mPath);
return MakeAndAddRef<PathBuilderCG>(path, aFillRule);
}
already_AddRefed<PathBuilder>
PathCG::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
{
// 10.7 adds CGPathCreateMutableCopyByTransformingPath it might be faster than doing
// this by hand
struct TransformApplier {
CGMutablePathRef path;
CGAffineTransform transform;
static void
TranformCGPathApplierFunc(void *vinfo, const CGPathElement *element)
{
TransformApplier *info = reinterpret_cast<TransformApplier*>(vinfo);
switch (element->type) {
case kCGPathElementMoveToPoint:
{
CGPoint pt = element->points[0];
CGPathMoveToPoint(info->path, &info->transform, pt.x, pt.y);
break;
}
case kCGPathElementAddLineToPoint:
{
CGPoint pt = element->points[0];
CGPathAddLineToPoint(info->path, &info->transform, pt.x, pt.y);
break;
}
case kCGPathElementAddQuadCurveToPoint:
{
CGPoint cpt = element->points[0];
CGPoint pt = element->points[1];
CGPathAddQuadCurveToPoint(info->path, &info->transform, cpt.x, cpt.y, pt.x, pt.y);
break;
}
case kCGPathElementAddCurveToPoint:
{
CGPoint cpt1 = element->points[0];
CGPoint cpt2 = element->points[1];
CGPoint pt = element->points[2];
CGPathAddCurveToPoint(info->path, &info->transform, cpt1.x, cpt1.y, cpt2.x, cpt2.y, pt.x, pt.y);
break;
}
case kCGPathElementCloseSubpath:
{
CGPathCloseSubpath(info->path);
break;
}
}
}
};
TransformApplier ta;
ta.path = CGPathCreateMutable();
ta.transform = GfxMatrixToCGAffineTransform(aTransform);
CGPathApply(mPath, &ta, TransformApplier::TranformCGPathApplierFunc);
return MakeAndAddRef<PathBuilderCG>(ta.path, aFillRule);
}
static void
StreamPathToSinkApplierFunc(void *vinfo, const CGPathElement *element)
{
PathSink *sink = reinterpret_cast<PathSink*>(vinfo);
switch (element->type) {
case kCGPathElementMoveToPoint:
{
CGPoint pt = element->points[0];
sink->MoveTo(CGPointToPoint(pt));
break;
}
case kCGPathElementAddLineToPoint:
{
CGPoint pt = element->points[0];
sink->LineTo(CGPointToPoint(pt));
break;
}
case kCGPathElementAddQuadCurveToPoint:
{
CGPoint cpt = element->points[0];
CGPoint pt = element->points[1];
sink->QuadraticBezierTo(CGPointToPoint(cpt),
CGPointToPoint(pt));
break;
}
case kCGPathElementAddCurveToPoint:
{
CGPoint cpt1 = element->points[0];
CGPoint cpt2 = element->points[1];
CGPoint pt = element->points[2];
sink->BezierTo(CGPointToPoint(cpt1),
CGPointToPoint(cpt2),
CGPointToPoint(pt));
break;
}
case kCGPathElementCloseSubpath:
{
sink->Close();
break;
}
}
}
void
PathCG::StreamToSink(PathSink *aSink) const
{
CGPathApply(mPath, aSink, StreamPathToSinkApplierFunc);
}
bool
PathCG::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
{
Matrix inverse = aTransform;
inverse.Invert();
Point transformedPoint = inverse.TransformPoint(aPoint);
// We could probably drop the input transform and just transform the point at the caller?
CGPoint point = {transformedPoint.x, transformedPoint.y};
// The transform parameter of CGPathContainsPoint doesn't seem to work properly on OS X 10.5
// so we transform aPoint ourselves.
return CGPathContainsPoint(mPath, nullptr, point, mFillRule == FillRule::FILL_EVEN_ODD);
}
static size_t
PutBytesNull(void *info, const void *buffer, size_t count)
{
return count;
}
/* The idea of a scratch context comes from WebKit */
static CGContextRef
CreateScratchContext()
{
CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr};
CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks);
CGContextRef cg = CGPDFContextCreate(consumer, nullptr, nullptr);
CGDataConsumerRelease(consumer);
return cg;
}
static CGContextRef
ScratchContext()
{
static CGContextRef cg = CreateScratchContext();
return cg;
}
bool
PathCG::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
const Point &aPoint,
const Matrix &aTransform) const
{
Matrix inverse = aTransform;
inverse.Invert();
Point transformedPoint = inverse.TransformPoint(aPoint);
// We could probably drop the input transform and just transform the point at the caller?
CGPoint point = {transformedPoint.x, transformedPoint.y};
CGContextRef cg = ScratchContext();
CGContextSaveGState(cg);
CGContextBeginPath(cg);
CGContextAddPath(cg, mPath);
SetStrokeOptions(cg, aStrokeOptions);
CGContextReplacePathWithStrokedPath(cg);
CGContextRestoreGState(cg);
CGPathRef sPath = CGContextCopyPath(cg);
bool inStroke = CGPathContainsPoint(sPath, nullptr, point, false);
CGPathRelease(sPath);
return inStroke;
}
//XXX: what should these functions return for an empty path?
// currently they return CGRectNull {inf,inf, 0, 0}
Rect
PathCG::GetBounds(const Matrix &aTransform) const
{
//XXX: are these bounds tight enough
Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath));
//XXX: currently this returns the bounds of the transformed bounds
// this is strictly looser than the bounds of the transformed path
return aTransform.TransformBounds(bounds);
}
Rect
PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform) const
{
// 10.7 has CGPathCreateCopyByStrokingPath which we could use
// instead of this scratch context business
CGContextRef cg = ScratchContext();
CGContextSaveGState(cg);
CGContextBeginPath(cg);
CGContextAddPath(cg, mPath);
SetStrokeOptions(cg, aStrokeOptions);
CGContextReplacePathWithStrokedPath(cg);
Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg));
CGContextRestoreGState(cg);
if (!bounds.IsFinite()) {
return Rect();
}
return aTransform.TransformBounds(bounds);
}
} // namespace gfx
} // namespace mozilla

114
gfx/2d/PathCG.h Normal file
View file

@ -0,0 +1,114 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_PATHCG_H_
#define MOZILLA_GFX_PATHCG_H_
#ifdef MOZ_WIDGET_COCOA
#include <ApplicationServices/ApplicationServices.h>
#else
#include <CoreGraphics/CoreGraphics.h>
#endif
#include "2D.h"
namespace mozilla {
namespace gfx {
class PathCG;
class PathBuilderCG : public PathBuilder
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCG)
// absorbs a reference of aPath
PathBuilderCG(CGMutablePathRef aPath, FillRule aFillRule)
: mFillRule(aFillRule)
{
mCGPath = aPath;
}
explicit PathBuilderCG(FillRule aFillRule)
: mFillRule(aFillRule)
{
mCGPath = CGPathCreateMutable();
}
virtual ~PathBuilderCG();
virtual void MoveTo(const Point &aPoint);
virtual void LineTo(const Point &aPoint);
virtual void BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3);
virtual void QuadraticBezierTo(const Point &aCP1,
const Point &aCP2);
virtual void Close();
virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
Float aEndAngle, bool aAntiClockwise = false);
virtual Point CurrentPoint() const;
virtual already_AddRefed<Path> Finish();
virtual BackendType GetBackendType() const { return BackendType::SKIA; }
private:
friend class PathCG;
friend class ScaledFontMac;
void EnsureActive(const Point &aPoint);
CGMutablePathRef mCGPath;
Point mCurrentPoint;
Point mBeginPoint;
FillRule mFillRule;
};
class PathCG : public Path
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCG)
PathCG(CGMutablePathRef aPath, FillRule aFillRule)
: mPath(aPath)
, mFillRule(aFillRule)
{
CGPathRetain(mPath);
}
virtual ~PathCG() { CGPathRelease(mPath); }
// Paths will always return BackendType::COREGRAPHICS, but note that they
// are compatible with BackendType::COREGRAPHICS_ACCELERATED backend.
virtual BackendType GetBackendType() const { return BackendType::SKIA; }
virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const;
virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform,
FillRule aFillRule) const;
virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const;
virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
const Point &aPoint,
const Matrix &aTransform) const;
virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const;
virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform = Matrix()) const;
virtual void StreamToSink(PathSink *aSink) const;
virtual FillRule GetFillRule() const { return mFillRule; }
CGMutablePathRef GetPath() const { return mPath; }
private:
friend class DrawTargetCG;
CGMutablePathRef mPath;
Point mEndPoint;
FillRule mFillRule;
};
} // namespace gfx
} // namespace mozilla
#endif

331
gfx/2d/PathCairo.cpp Normal file
View file

@ -0,0 +1,331 @@
/* -*- 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 "PathCairo.h"
#include <math.h>
#include "DrawTargetCairo.h"
#include "Logging.h"
#include "PathHelpers.h"
#include "HelpersCairo.h"
namespace mozilla {
namespace gfx {
PathBuilderCairo::PathBuilderCairo(FillRule aFillRule)
: mFillRule(aFillRule)
{
}
void
PathBuilderCairo::MoveTo(const Point &aPoint)
{
cairo_path_data_t data;
data.header.type = CAIRO_PATH_MOVE_TO;
data.header.length = 2;
mPathData.push_back(data);
data.point.x = aPoint.x;
data.point.y = aPoint.y;
mPathData.push_back(data);
mBeginPoint = mCurrentPoint = aPoint;
}
void
PathBuilderCairo::LineTo(const Point &aPoint)
{
cairo_path_data_t data;
data.header.type = CAIRO_PATH_LINE_TO;
data.header.length = 2;
mPathData.push_back(data);
data.point.x = aPoint.x;
data.point.y = aPoint.y;
mPathData.push_back(data);
mCurrentPoint = aPoint;
}
void
PathBuilderCairo::BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3)
{
cairo_path_data_t data;
data.header.type = CAIRO_PATH_CURVE_TO;
data.header.length = 4;
mPathData.push_back(data);
data.point.x = aCP1.x;
data.point.y = aCP1.y;
mPathData.push_back(data);
data.point.x = aCP2.x;
data.point.y = aCP2.y;
mPathData.push_back(data);
data.point.x = aCP3.x;
data.point.y = aCP3.y;
mPathData.push_back(data);
mCurrentPoint = aCP3;
}
void
PathBuilderCairo::QuadraticBezierTo(const Point &aCP1,
const Point &aCP2)
{
// We need to elevate the degree of this quadratic Bézier to cubic, so we're
// going to add an intermediate control point, and recompute control point 1.
// The first and last control points remain the same.
// This formula can be found on http://fontforge.sourceforge.net/bezier.html
Point CP0 = CurrentPoint();
Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
Point CP3 = aCP2;
cairo_path_data_t data;
data.header.type = CAIRO_PATH_CURVE_TO;
data.header.length = 4;
mPathData.push_back(data);
data.point.x = CP1.x;
data.point.y = CP1.y;
mPathData.push_back(data);
data.point.x = CP2.x;
data.point.y = CP2.y;
mPathData.push_back(data);
data.point.x = CP3.x;
data.point.y = CP3.y;
mPathData.push_back(data);
mCurrentPoint = aCP2;
}
void
PathBuilderCairo::Close()
{
cairo_path_data_t data;
data.header.type = CAIRO_PATH_CLOSE_PATH;
data.header.length = 1;
mPathData.push_back(data);
mCurrentPoint = mBeginPoint;
}
void
PathBuilderCairo::Arc(const Point &aOrigin, float aRadius, float aStartAngle,
float aEndAngle, bool aAntiClockwise)
{
ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise);
}
Point
PathBuilderCairo::CurrentPoint() const
{
return mCurrentPoint;
}
already_AddRefed<Path>
PathBuilderCairo::Finish()
{
return MakeAndAddRef<PathCairo>(mFillRule, mPathData, mCurrentPoint);
}
PathCairo::PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t> &aPathData, const Point &aCurrentPoint)
: mFillRule(aFillRule)
, mContainingContext(nullptr)
, mCurrentPoint(aCurrentPoint)
{
mPathData.swap(aPathData);
}
PathCairo::PathCairo(cairo_t *aContext)
: mFillRule(FillRule::FILL_WINDING)
, mContainingContext(nullptr)
{
cairo_path_t *path = cairo_copy_path(aContext);
// XXX - mCurrentPoint is not properly set here, the same is true for the
// D2D Path code, we never require current point when hitting this codepath
// but this should be fixed.
for (int i = 0; i < path->num_data; i++) {
mPathData.push_back(path->data[i]);
}
cairo_path_destroy(path);
}
PathCairo::~PathCairo()
{
if (mContainingContext) {
cairo_destroy(mContainingContext);
}
}
already_AddRefed<PathBuilder>
PathCairo::CopyToBuilder(FillRule aFillRule) const
{
RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule);
builder->mPathData = mPathData;
builder->mCurrentPoint = mCurrentPoint;
return builder.forget();
}
already_AddRefed<PathBuilder>
PathCairo::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
{
RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule);
AppendPathToBuilder(builder, &aTransform);
builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint);
return builder.forget();
}
bool
PathCairo::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
{
Matrix inverse = aTransform;
inverse.Invert();
Point transformed = inverse.TransformPoint(aPoint);
EnsureContainingContext(aTransform);
return cairo_in_fill(mContainingContext, transformed.x, transformed.y);
}
bool
PathCairo::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
const Point &aPoint,
const Matrix &aTransform) const
{
Matrix inverse = aTransform;
inverse.Invert();
Point transformed = inverse.TransformPoint(aPoint);
EnsureContainingContext(aTransform);
SetCairoStrokeOptions(mContainingContext, aStrokeOptions);
return cairo_in_stroke(mContainingContext, transformed.x, transformed.y);
}
Rect
PathCairo::GetBounds(const Matrix &aTransform) const
{
EnsureContainingContext(aTransform);
double x1, y1, x2, y2;
cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2);
Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1));
return aTransform.TransformBounds(bounds);
}
Rect
PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform) const
{
EnsureContainingContext(aTransform);
double x1, y1, x2, y2;
SetCairoStrokeOptions(mContainingContext, aStrokeOptions);
cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2);
Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1));
return aTransform.TransformBounds(bounds);
}
void
PathCairo::StreamToSink(PathSink *aSink) const
{
for (size_t i = 0; i < mPathData.size(); i++) {
switch (mPathData[i].header.type) {
case CAIRO_PATH_MOVE_TO:
i++;
aSink->MoveTo(Point(mPathData[i].point.x, mPathData[i].point.y));
break;
case CAIRO_PATH_LINE_TO:
i++;
aSink->LineTo(Point(mPathData[i].point.x, mPathData[i].point.y));
break;
case CAIRO_PATH_CURVE_TO:
aSink->BezierTo(Point(mPathData[i + 1].point.x, mPathData[i + 1].point.y),
Point(mPathData[i + 2].point.x, mPathData[i + 2].point.y),
Point(mPathData[i + 3].point.x, mPathData[i + 3].point.y));
i += 3;
break;
case CAIRO_PATH_CLOSE_PATH:
aSink->Close();
break;
default:
// Corrupt path data!
MOZ_ASSERT(false);
}
}
}
void
PathCairo::EnsureContainingContext(const Matrix &aTransform) const
{
if (mContainingContext) {
if (mContainingTransform.ExactlyEquals(aTransform)) {
return;
}
} else {
mContainingContext = cairo_create(DrawTargetCairo::GetDummySurface());
}
mContainingTransform = aTransform;
cairo_matrix_t mat;
GfxMatrixToCairoMatrix(mContainingTransform, mat);
cairo_set_matrix(mContainingContext, &mat);
SetPathOnContext(mContainingContext);
}
void
PathCairo::SetPathOnContext(cairo_t *aContext) const
{
// Needs the correct fill rule set.
cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule));
cairo_new_path(aContext);
if (mPathData.size()) {
cairo_path_t path;
path.data = const_cast<cairo_path_data_t*>(&mPathData.front());
path.num_data = mPathData.size();
path.status = CAIRO_STATUS_SUCCESS;
cairo_append_path(aContext, &path);
}
}
void
PathCairo::AppendPathToBuilder(PathBuilderCairo *aBuilder, const Matrix *aTransform) const
{
if (aTransform) {
size_t i = 0;
while (i < mPathData.size()) {
uint32_t pointCount = mPathData[i].header.length - 1;
aBuilder->mPathData.push_back(mPathData[i]);
i++;
for (uint32_t c = 0; c < pointCount; c++) {
cairo_path_data_t data;
Point newPoint = aTransform->TransformPoint(Point(mPathData[i].point.x, mPathData[i].point.y));
data.point.x = newPoint.x;
data.point.y = newPoint.y;
aBuilder->mPathData.push_back(data);
i++;
}
}
} else {
for (size_t i = 0; i < mPathData.size(); i++) {
aBuilder->mPathData.push_back(mPathData[i]);
}
}
}
} // namespace gfx
} // namespace mozilla

95
gfx/2d/PathCairo.h Normal file
View file

@ -0,0 +1,95 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_PATH_CAIRO_H_
#define MOZILLA_GFX_PATH_CAIRO_H_
#include "2D.h"
#include "cairo.h"
#include <vector>
namespace mozilla {
namespace gfx {
class PathCairo;
class PathBuilderCairo : public PathBuilder
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCairo)
explicit PathBuilderCairo(FillRule aFillRule);
virtual void MoveTo(const Point &aPoint);
virtual void LineTo(const Point &aPoint);
virtual void BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3);
virtual void QuadraticBezierTo(const Point &aCP1,
const Point &aCP2);
virtual void Close();
virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle,
float aEndAngle, bool aAntiClockwise = false);
virtual Point CurrentPoint() const;
virtual already_AddRefed<Path> Finish();
virtual BackendType GetBackendType() const { return BackendType::CAIRO; }
private: // data
friend class PathCairo;
FillRule mFillRule;
std::vector<cairo_path_data_t> mPathData;
// It's easiest to track this here, parsing the path data to find the current
// point is a little tricky.
Point mCurrentPoint;
Point mBeginPoint;
};
class PathCairo : public Path
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCairo)
PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t> &aPathData, const Point &aCurrentPoint);
explicit PathCairo(cairo_t *aContext);
~PathCairo();
virtual BackendType GetBackendType() const { return BackendType::CAIRO; }
virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const;
virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform,
FillRule aFillRule) const;
virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const;
virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
const Point &aPoint,
const Matrix &aTransform) const;
virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const;
virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform = Matrix()) const;
virtual void StreamToSink(PathSink *aSink) const;
virtual FillRule GetFillRule() const { return mFillRule; }
void SetPathOnContext(cairo_t *aContext) const;
void AppendPathToBuilder(PathBuilderCairo *aBuilder, const Matrix *aTransform = nullptr) const;
private:
void EnsureContainingContext(const Matrix &aTransform) const;
FillRule mFillRule;
std::vector<cairo_path_data_t> mPathData;
mutable cairo_t *mContainingContext;
mutable Matrix mContainingTransform;
Point mCurrentPoint;
};
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_PATH_CAIRO_H_ */

523
gfx/2d/PathD2D.cpp Normal file
View file

@ -0,0 +1,523 @@
/* -*- 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 "PathD2D.h"
#include "HelpersD2D.h"
#include <math.h>
#include "DrawTargetD2D1.h"
#include "Logging.h"
namespace mozilla {
namespace gfx {
// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows
// a geometry to be duplicated into a geometry sink, while removing the final
// figure end and thus allowing a figure that was implicitly closed to be
// continued.
class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink
{
public:
OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink)
: mSink(aSink)
, mNeedsFigureEnded(false)
{
}
HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
{
if (!aPtr) {
return E_POINTER;
}
if (aIID == IID_IUnknown) {
*aPtr = static_cast<IUnknown*>(this);
return S_OK;
} else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
*aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
return 1;
}
ULONG STDMETHODCALLTYPE Release()
{
return 1;
}
// We ignore SetFillMode, the copier will decide.
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
{ EnsureFigureEnded(); return; }
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
{ EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); }
STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
{ EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); }
STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
{ EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); }
STDMETHOD(Close)()
{ /* Should never be called! */ return S_OK; }
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
{ return mSink->SetSegmentFlags(aFlags); }
// This function is special - it's the reason this class exists.
// It needs to intercept the very last endfigure. So that a user can
// continue writing to this sink as if they never stopped.
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
{
if (aEnd == D2D1_FIGURE_END_CLOSED) {
return mSink->EndFigure(aEnd);
} else {
mNeedsFigureEnded = true;
}
}
private:
void EnsureFigureEnded()
{
if (mNeedsFigureEnded) {
mSink->EndFigure(D2D1_FIGURE_END_OPEN);
mNeedsFigureEnded = false;
}
}
ID2D1SimplifiedGeometrySink *mSink;
bool mNeedsFigureEnded;
};
class MOZ_STACK_CLASS AutoRestoreFP
{
public:
AutoRestoreFP()
{
// save the current floating point control word
_controlfp_s(&savedFPSetting, 0, 0);
UINT unused;
// set the floating point control word to its default value
_controlfp_s(&unused, _CW_DEFAULT, MCW_PC);
}
~AutoRestoreFP()
{
UINT unused;
// restore the saved floating point control word
_controlfp_s(&unused, savedFPSetting, MCW_PC);
}
private:
UINT savedFPSetting;
};
// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may
// get called from D2D with nonstandard floating point settings (see comments in
// bug 1134549) - use AutoRestoreFP to reset the floating point control word to
// what we expect
class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink
{
public:
StreamingGeometrySink(PathSink *aSink)
: mSink(aSink)
{
}
HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr)
{
if (!aPtr) {
return E_POINTER;
}
if (aIID == IID_IUnknown) {
*aPtr = static_cast<IUnknown*>(this);
return S_OK;
} else if (aIID == IID_ID2D1SimplifiedGeometrySink) {
*aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this);
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
return 1;
}
ULONG STDMETHODCALLTYPE Release()
{
return 1;
}
// We ignore SetFillMode, this depends on the destination sink.
STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode)
{ return; }
STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin)
{
AutoRestoreFP resetFloatingPoint;
mSink->MoveTo(ToPoint(aPoint));
}
STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount)
{
AutoRestoreFP resetFloatingPoint;
for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); }
}
STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount)
{
AutoRestoreFP resetFloatingPoint;
for (UINT i = 0; i < aCount; i++) {
mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3));
}
}
STDMETHOD(Close)()
{ /* Should never be called! */ return S_OK; }
STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags)
{ /* Should never be called! */ }
STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd)
{
AutoRestoreFP resetFloatingPoint;
if (aEnd == D2D1_FIGURE_END_CLOSED) {
return mSink->Close();
}
}
private:
PathSink *mSink;
};
PathBuilderD2D::~PathBuilderD2D()
{
}
void
PathBuilderD2D::MoveTo(const Point &aPoint)
{
if (mFigureActive) {
mSink->EndFigure(D2D1_FIGURE_END_OPEN);
mFigureActive = false;
}
EnsureActive(aPoint);
mCurrentPoint = aPoint;
}
void
PathBuilderD2D::LineTo(const Point &aPoint)
{
EnsureActive(aPoint);
mSink->AddLine(D2DPoint(aPoint));
mCurrentPoint = aPoint;
}
void
PathBuilderD2D::BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3)
{
EnsureActive(aCP1);
mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1),
D2DPoint(aCP2),
D2DPoint(aCP3)));
mCurrentPoint = aCP3;
}
void
PathBuilderD2D::QuadraticBezierTo(const Point &aCP1,
const Point &aCP2)
{
EnsureActive(aCP1);
mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1),
D2DPoint(aCP2)));
mCurrentPoint = aCP2;
}
void
PathBuilderD2D::Close()
{
if (mFigureActive) {
mSink->EndFigure(D2D1_FIGURE_END_CLOSED);
mFigureActive = false;
EnsureActive(mBeginPoint);
}
}
void
PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
Float aEndAngle, bool aAntiClockwise)
{
MOZ_ASSERT(aRadius >= 0);
if (aAntiClockwise && aStartAngle < aEndAngle) {
// D2D does things a little differently, and draws the arc by specifying an
// beginning and an end point. This means the circle will be the wrong way
// around if the start angle is smaller than the end angle. It might seem
// tempting to invert aAntiClockwise but that would change the sweeping
// direction of the arc so instead we exchange start/begin.
Float oldStart = aStartAngle;
aStartAngle = aEndAngle;
aEndAngle = oldStart;
}
// XXX - Workaround for now, D2D does not appear to do the desired thing when
// the angle sweeps a complete circle.
bool fullCircle = false;
if (aEndAngle - aStartAngle >= 2 * M_PI) {
fullCircle = true;
aEndAngle = Float(aStartAngle + M_PI * 1.9999);
} else if (aStartAngle - aEndAngle >= 2 * M_PI) {
fullCircle = true;
aStartAngle = Float(aEndAngle + M_PI * 1.9999);
}
Point startPoint;
startPoint.x = aOrigin.x + aRadius * cos(aStartAngle);
startPoint.y = aOrigin.y + aRadius * sin(aStartAngle);
if (!mFigureActive) {
EnsureActive(startPoint);
} else {
mSink->AddLine(D2DPoint(startPoint));
}
Point endPoint;
endPoint.x = aOrigin.x + aRadius * cosf(aEndAngle);
endPoint.y = aOrigin.y + aRadius * sinf(aEndAngle);
D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL;
D2D1_SWEEP_DIRECTION direction =
aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE :
D2D1_SWEEP_DIRECTION_CLOCKWISE;
// if startPoint and endPoint of our circle are too close there are D2D issues
// with drawing the circle as a single arc
const Float kEpsilon = 1e-5f;
if (!fullCircle ||
(std::abs(startPoint.x - endPoint.x) +
std::abs(startPoint.y - endPoint.y) > kEpsilon)) {
if (aAntiClockwise) {
if (aStartAngle - aEndAngle > M_PI) {
arcSize = D2D1_ARC_SIZE_LARGE;
}
} else {
if (aEndAngle - aStartAngle > M_PI) {
arcSize = D2D1_ARC_SIZE_LARGE;
}
}
mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint),
D2D1::SizeF(aRadius, aRadius),
0.0f,
direction,
arcSize));
}
else {
// our first workaround attempt didn't work, so instead draw the circle as
// two half-circles
Float midAngle = aEndAngle > aStartAngle ?
Float(aStartAngle + M_PI) : Float(aEndAngle + M_PI);
Point midPoint;
midPoint.x = aOrigin.x + aRadius * cosf(midAngle);
midPoint.y = aOrigin.y + aRadius * sinf(midAngle);
mSink->AddArc(D2D1::ArcSegment(D2DPoint(midPoint),
D2D1::SizeF(aRadius, aRadius),
0.0f,
direction,
arcSize));
// if the adjusted endPoint computed above is used here and endPoint !=
// startPoint then this half of the circle won't render...
mSink->AddArc(D2D1::ArcSegment(D2DPoint(startPoint),
D2D1::SizeF(aRadius, aRadius),
0.0f,
direction,
arcSize));
}
mCurrentPoint = endPoint;
}
Point
PathBuilderD2D::CurrentPoint() const
{
return mCurrentPoint;
}
void
PathBuilderD2D::EnsureActive(const Point &aPoint)
{
if (!mFigureActive) {
mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED);
mBeginPoint = aPoint;
mFigureActive = true;
}
}
already_AddRefed<Path>
PathBuilderD2D::Finish()
{
if (mFigureActive) {
mSink->EndFigure(D2D1_FIGURE_END_OPEN);
}
HRESULT hr = mSink->Close();
if (FAILED(hr)) {
gfxCriticalNote << "Failed to close PathSink. Code: " << hexa(hr);
return nullptr;
}
return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mCurrentPoint, mFillRule, mBackendType);
}
already_AddRefed<PathBuilder>
PathD2D::CopyToBuilder(FillRule aFillRule) const
{
return TransformedCopyToBuilder(Matrix(), aFillRule);
}
already_AddRefed<PathBuilder>
PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
{
RefPtr<ID2D1PathGeometry> path;
HRESULT hr = DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
if (FAILED(hr)) {
gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr);
return nullptr;
}
RefPtr<ID2D1GeometrySink> sink;
hr = path->Open(getter_AddRefs(sink));
if (FAILED(hr)) {
gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr);
return nullptr;
}
if (aFillRule == FillRule::FILL_WINDING) {
sink->SetFillMode(D2D1_FILL_MODE_WINDING);
}
if (mEndedActive) {
OpeningGeometrySink wrapSink(sink);
hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
D2DMatrix(aTransform),
&wrapSink);
} else {
hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
D2DMatrix(aTransform),
sink);
}
if (FAILED(hr)) {
gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: " << hexa(hr) << " Active: " << mEndedActive;
return nullptr;
}
RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule, mBackendType);
pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint);
if (mEndedActive) {
pathBuilder->mFigureActive = true;
}
return pathBuilder.forget();
}
void
PathD2D::StreamToSink(PathSink *aSink) const
{
HRESULT hr;
StreamingGeometrySink sink(aSink);
hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
D2D1::IdentityMatrix(), &sink);
if (FAILED(hr)) {
gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr);
return;
}
}
bool
PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
{
BOOL result;
HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result);
if (FAILED(hr)) {
// Log
return false;
}
return !!result;
}
bool
PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
const Point &aPoint,
const Matrix &aTransform) const
{
BOOL result;
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint),
aStrokeOptions.mLineWidth,
strokeStyle,
D2DMatrix(aTransform),
&result);
if (FAILED(hr)) {
// Log
return false;
}
return !!result;
}
Rect
PathD2D::GetBounds(const Matrix &aTransform) const
{
D2D1_RECT_F d2dBounds;
HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds);
Rect bounds = ToRect(d2dBounds);
if (FAILED(hr) || !bounds.IsFinite()) {
gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr);
return Rect();
}
return bounds;
}
Rect
PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform) const
{
D2D1_RECT_F d2dBounds;
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
HRESULT hr =
mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle,
D2DMatrix(aTransform), &d2dBounds);
Rect bounds = ToRect(d2dBounds);
if (FAILED(hr) || !bounds.IsFinite()) {
gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr);
return Rect();
}
return bounds;
}
}
}

117
gfx/2d/PathD2D.h Normal file
View file

@ -0,0 +1,117 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_PATHD2D_H_
#define MOZILLA_GFX_PATHD2D_H_
#include <d2d1.h>
#include "2D.h"
namespace mozilla {
namespace gfx {
class PathD2D;
class PathBuilderD2D : public PathBuilder
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderD2D)
PathBuilderD2D(ID2D1GeometrySink *aSink, ID2D1PathGeometry *aGeom, FillRule aFillRule, BackendType aBackendType)
: mSink(aSink)
, mGeometry(aGeom)
, mFigureActive(false)
, mFillRule(aFillRule)
, mBackendType(aBackendType)
{
}
virtual ~PathBuilderD2D();
virtual void MoveTo(const Point &aPoint);
virtual void LineTo(const Point &aPoint);
virtual void BezierTo(const Point &aCP1,
const Point &aCP2,
const Point &aCP3);
virtual void QuadraticBezierTo(const Point &aCP1,
const Point &aCP2);
virtual void Close();
virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
Float aEndAngle, bool aAntiClockwise = false);
virtual Point CurrentPoint() const;
virtual already_AddRefed<Path> Finish();
virtual BackendType GetBackendType() const { return mBackendType; }
ID2D1GeometrySink *GetSink() { return mSink; }
bool IsFigureActive() const { return mFigureActive; }
private:
friend class PathD2D;
void EnsureActive(const Point &aPoint);
RefPtr<ID2D1GeometrySink> mSink;
RefPtr<ID2D1PathGeometry> mGeometry;
bool mFigureActive;
Point mCurrentPoint;
Point mBeginPoint;
FillRule mFillRule;
BackendType mBackendType;
};
class PathD2D : public Path
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathD2D)
PathD2D(ID2D1PathGeometry *aGeometry, bool aEndedActive,
const Point &aEndPoint, FillRule aFillRule, BackendType aBackendType)
: mGeometry(aGeometry)
, mEndedActive(aEndedActive)
, mEndPoint(aEndPoint)
, mFillRule(aFillRule)
, mBackendType(aBackendType)
{}
virtual BackendType GetBackendType() const { return mBackendType; }
virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const;
virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform,
FillRule aFillRule) const;
virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const;
virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
const Point &aPoint,
const Matrix &aTransform) const;
virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const;
virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions,
const Matrix &aTransform = Matrix()) const;
virtual void StreamToSink(PathSink *aSink) const;
virtual FillRule GetFillRule() const { return mFillRule; }
ID2D1Geometry *GetGeometry() { return mGeometry; }
private:
friend class DrawTargetD2D;
friend class DrawTargetD2D1;
mutable RefPtr<ID2D1PathGeometry> mGeometry;
bool mEndedActive;
Point mEndPoint;
FillRule mFillRule;
BackendType mBackendType;
};
}
}
#endif /* MOZILLA_GFX_PATHD2D_H_ */

277
gfx/2d/PathHelpers.cpp Normal file
View file

@ -0,0 +1,277 @@
/* -*- 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 "PathHelpers.h"
namespace mozilla {
namespace gfx {
UserDataKey sDisablePixelSnapping;
void
AppendRectToPath(PathBuilder* aPathBuilder,
const Rect& aRect,
bool aDrawClockwise)
{
if (aDrawClockwise) {
aPathBuilder->MoveTo(aRect.TopLeft());
aPathBuilder->LineTo(aRect.TopRight());
aPathBuilder->LineTo(aRect.BottomRight());
aPathBuilder->LineTo(aRect.BottomLeft());
} else {
aPathBuilder->MoveTo(aRect.TopRight());
aPathBuilder->LineTo(aRect.TopLeft());
aPathBuilder->LineTo(aRect.BottomLeft());
aPathBuilder->LineTo(aRect.BottomRight());
}
aPathBuilder->Close();
}
void
AppendRoundedRectToPath(PathBuilder* aPathBuilder,
const Rect& aRect,
const RectCornerRadii& aRadii,
bool aDrawClockwise)
{
// For CW drawing, this looks like:
//
// ...******0** 1 C
// ****
// *** 2
// **
// *
// *
// 3
// *
// *
//
// Where 0, 1, 2, 3 are the control points of the Bezier curve for
// the corner, and C is the actual corner point.
//
// At the start of the loop, the current point is assumed to be
// the point adjacent to the top left corner on the top
// horizontal. Note that corner indices start at the top left and
// continue clockwise, whereas in our loop i = 0 refers to the top
// right corner.
//
// When going CCW, the control points are swapped, and the first
// corner that's drawn is the top left (along with the top segment).
//
// There is considerable latitude in how one chooses the four
// control points for a Bezier curve approximation to an ellipse.
// For the overall path to be continuous and show no corner at the
// endpoints of the arc, points 0 and 3 must be at the ends of the
// straight segments of the rectangle; points 0, 1, and C must be
// collinear; and points 3, 2, and C must also be collinear. This
// leaves only two free parameters: the ratio of the line segments
// 01 and 0C, and the ratio of the line segments 32 and 3C. See
// the following papers for extensive discussion of how to choose
// these ratios:
//
// Dokken, Tor, et al. "Good approximation of circles by
// curvature-continuous Bezier curves." Computer-Aided
// Geometric Design 7(1990) 33--41.
// Goldapp, Michael. "Approximation of circular arcs by cubic
// polynomials." Computer-Aided Geometric Design 8(1991) 227--238.
// Maisonobe, Luc. "Drawing an elliptical arc using polylines,
// quadratic, or cubic Bezier curves."
// http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
//
// We follow the approach in section 2 of Goldapp (least-error,
// Hermite-type approximation) and make both ratios equal to
//
// 2 2 + n - sqrt(2n + 28)
// alpha = - * ---------------------
// 3 n - 4
//
// where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ).
//
// This is the result of Goldapp's equation (10b) when the angle
// swept out by the arc is pi/2, and the parameter "a-bar" is the
// expression given immediately below equation (21).
//
// Using this value, the maximum radial error for a circle, as a
// fraction of the radius, is on the order of 0.2 x 10^-3.
// Neither Dokken nor Goldapp discusses error for a general
// ellipse; Maisonobe does, but his choice of control points
// follows different constraints, and Goldapp's expression for
// 'alpha' gives much smaller radial error, even for very flat
// ellipses, than Maisonobe's equivalent.
//
// For the various corners and for each axis, the sign of this
// constant changes, or it might be 0 -- it's multiplied by the
// appropriate multiplier from the list before using.
const Float alpha = Float(0.55191497064665766025);
typedef struct { Float a, b; } twoFloats;
twoFloats cwCornerMults[4] = { { -1, 0 }, // cc == clockwise
{ 0, -1 },
{ +1, 0 },
{ 0, +1 } };
twoFloats ccwCornerMults[4] = { { +1, 0 }, // ccw == counter-clockwise
{ 0, -1 },
{ -1, 0 },
{ 0, +1 } };
twoFloats *cornerMults = aDrawClockwise ? cwCornerMults : ccwCornerMults;
Point cornerCoords[] = { aRect.TopLeft(), aRect.TopRight(),
aRect.BottomRight(), aRect.BottomLeft() };
Point pc, p0, p1, p2, p3;
if (aDrawClockwise) {
aPathBuilder->MoveTo(Point(aRect.X() + aRadii[RectCorner::TopLeft].width,
aRect.Y()));
} else {
aPathBuilder->MoveTo(Point(aRect.X() + aRect.Width() - aRadii[RectCorner::TopRight].width,
aRect.Y()));
}
for (int i = 0; i < 4; ++i) {
// the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
int c = aDrawClockwise ? ((i+1) % 4) : ((4-i) % 4);
// i+2 and i+3 respectively. These are used to index into the corner
// multiplier table, and were deduced by calculating out the long form
// of each corner and finding a pattern in the signs and values.
int i2 = (i+2) % 4;
int i3 = (i+3) % 4;
pc = cornerCoords[c];
if (aRadii[c].width > 0.0 && aRadii[c].height > 0.0) {
p0.x = pc.x + cornerMults[i].a * aRadii[c].width;
p0.y = pc.y + cornerMults[i].b * aRadii[c].height;
p3.x = pc.x + cornerMults[i3].a * aRadii[c].width;
p3.y = pc.y + cornerMults[i3].b * aRadii[c].height;
p1.x = p0.x + alpha * cornerMults[i2].a * aRadii[c].width;
p1.y = p0.y + alpha * cornerMults[i2].b * aRadii[c].height;
p2.x = p3.x - alpha * cornerMults[i3].a * aRadii[c].width;
p2.y = p3.y - alpha * cornerMults[i3].b * aRadii[c].height;
aPathBuilder->LineTo(p0);
aPathBuilder->BezierTo(p1, p2, p3);
} else {
aPathBuilder->LineTo(pc);
}
}
aPathBuilder->Close();
}
void
AppendEllipseToPath(PathBuilder* aPathBuilder,
const Point& aCenter,
const Size& aDimensions)
{
Size halfDim = aDimensions / 2.f;
Rect rect(aCenter - Point(halfDim.width, halfDim.height), aDimensions);
RectCornerRadii radii(halfDim.width, halfDim.height);
AppendRoundedRectToPath(aPathBuilder, rect, radii);
}
bool
SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2,
const DrawTarget& aDrawTarget,
Float aLineWidth)
{
Matrix mat = aDrawTarget.GetTransform();
if (mat.HasNonTranslation()) {
return false;
}
if (aP1.x != aP2.x && aP1.y != aP2.y) {
return false; // not a horizontal or vertical line
}
Point p1 = aP1 + mat.GetTranslation(); // into device space
Point p2 = aP2 + mat.GetTranslation();
p1.Round();
p2.Round();
p1 -= mat.GetTranslation(); // back into user space
p2 -= mat.GetTranslation();
aP1 = p1;
aP2 = p2;
bool lineWidthIsOdd = (int(aLineWidth) % 2) == 1;
if (lineWidthIsOdd) {
if (aP1.x == aP2.x) {
// snap vertical line, adding 0.5 to align it to be mid-pixel:
aP1 += Point(0.5, 0);
aP2 += Point(0.5, 0);
} else {
// snap horizontal line, adding 0.5 to align it to be mid-pixel:
aP1 += Point(0, 0.5);
aP2 += Point(0, 0.5);
}
}
return true;
}
void
StrokeSnappedEdgesOfRect(const Rect& aRect, DrawTarget& aDrawTarget,
const ColorPattern& aColor,
const StrokeOptions& aStrokeOptions)
{
if (aRect.IsEmpty()) {
return;
}
Point p1 = aRect.TopLeft();
Point p2 = aRect.BottomLeft();
SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
aStrokeOptions.mLineWidth);
aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions);
p1 = aRect.BottomLeft();
p2 = aRect.BottomRight();
SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
aStrokeOptions.mLineWidth);
aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions);
p1 = aRect.TopLeft();
p2 = aRect.TopRight();
SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
aStrokeOptions.mLineWidth);
aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions);
p1 = aRect.TopRight();
p2 = aRect.BottomRight();
SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
aStrokeOptions.mLineWidth);
aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions);
}
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
Margin
MaxStrokeExtents(const StrokeOptions& aStrokeOptions,
const Matrix& aTransform)
{
double styleExpansionFactor = 0.5f;
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
styleExpansionFactor = M_SQRT1_2;
}
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
}
styleExpansionFactor *= aStrokeOptions.mLineWidth;
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
return Margin(dy, dx, dy, dx);
}
} // namespace gfx
} // namespace mozilla

424
gfx/2d/PathHelpers.h Normal file
View file

@ -0,0 +1,424 @@
/* -*- 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/. */
#ifndef MOZILLA_GFX_PATHHELPERS_H_
#define MOZILLA_GFX_PATHHELPERS_H_
#include "2D.h"
#include "UserData.h"
#include <cmath>
namespace mozilla {
namespace gfx {
// Kappa constant for 90-degree angle
const Float kKappaFactor = 0.55191497064665766025f;
// Calculate kappa constant for partial curve. The sign of angle in the
// tangent will actually ensure this is negative for a counter clockwise
// sweep, so changing signs later isn't needed.
inline Float ComputeKappaFactor(Float aAngle)
{
return (4.0f / 3.0f) * tanf(aAngle / 4.0f);
}
/**
* Draws a partial arc <= 90 degrees given exact start and end points.
* Assumes that it is continuing from an already specified start point.
*/
template <typename T>
inline void PartialArcToBezier(T* aSink,
const Point& aStartOffset, const Point& aEndOffset,
const Matrix& aTransform,
Float aKappaFactor = kKappaFactor)
{
Point cp1 =
aStartOffset + Point(-aStartOffset.y, aStartOffset.x) * aKappaFactor;
Point cp2 =
aEndOffset + Point(aEndOffset.y, -aEndOffset.x) * aKappaFactor;
aSink->BezierTo(aTransform.TransformPoint(cp1),
aTransform.TransformPoint(cp2),
aTransform.TransformPoint(aEndOffset));
}
/**
* Draws an acute arc (<= 90 degrees) given exact start and end points.
* Specialized version avoiding kappa calculation.
*/
template <typename T>
inline void AcuteArcToBezier(T* aSink,
const Point& aOrigin, const Size& aRadius,
const Point& aStartPoint, const Point& aEndPoint,
Float aKappaFactor = kKappaFactor)
{
aSink->LineTo(aStartPoint);
if (!aRadius.IsEmpty()) {
Float kappaX = aKappaFactor * aRadius.width / aRadius.height;
Float kappaY = aKappaFactor * aRadius.height / aRadius.width;
Point startOffset = aStartPoint - aOrigin;
Point endOffset = aEndPoint - aOrigin;
aSink->BezierTo(aStartPoint + Point(-startOffset.y * kappaX, startOffset.x * kappaY),
aEndPoint + Point(endOffset.y * kappaX, -endOffset.x * kappaY),
aEndPoint);
} else if (aEndPoint != aStartPoint) {
aSink->LineTo(aEndPoint);
}
}
/**
* Draws an acute arc (<= 90 degrees) given exact start and end points.
*/
template <typename T>
inline void AcuteArcToBezier(T* aSink,
const Point& aOrigin, const Size& aRadius,
const Point& aStartPoint, const Point& aEndPoint,
Float aStartAngle, Float aEndAngle)
{
AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint,
ComputeKappaFactor(aEndAngle - aStartAngle));
}
template <typename T>
void ArcToBezier(T* aSink, const Point &aOrigin, const Size &aRadius,
float aStartAngle, float aEndAngle, bool aAntiClockwise,
float aRotation = 0.0f)
{
Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f;
// Calculate the total arc we're going to sweep.
Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection;
// Clockwise we always sweep from the smaller to the larger angle, ccw
// it's vice versa.
if (arcSweepLeft < 0) {
// Rerverse sweep is modulo'd into range rather than clamped.
arcSweepLeft = Float(2.0f * M_PI) + fmodf(arcSweepLeft, Float(2.0f * M_PI));
// Recalculate the start angle to land closer to end angle.
aStartAngle = aEndAngle - arcSweepLeft * sweepDirection;
} else if (arcSweepLeft > Float(2.0f * M_PI)) {
// Sweeping more than 2 * pi is a full circle.
arcSweepLeft = Float(2.0f * M_PI);
}
Float currentStartAngle = aStartAngle;
Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle));
Matrix transform = Matrix::Scaling(aRadius.width, aRadius.height);
if (aRotation != 0.0f) {
transform *= Matrix::Rotation(aRotation);
}
transform.PostTranslate(aOrigin);
aSink->LineTo(transform.TransformPoint(currentStartOffset));
while (arcSweepLeft > 0) {
Float currentEndAngle =
currentStartAngle + std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection;
Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle));
PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform,
ComputeKappaFactor(currentEndAngle - currentStartAngle));
// We guarantee here the current point is the start point of the next
// curve segment.
arcSweepLeft -= Float(M_PI / 2.0f);
currentStartAngle = currentEndAngle;
currentStartOffset = currentEndOffset;
}
}
/* This is basically the ArcToBezier with the parameters for drawing a circle
* inlined which vastly simplifies it and avoids a bunch of transcedental function
* calls which should make it faster. */
template <typename T>
void EllipseToBezier(T* aSink, const Point &aOrigin, const Size &aRadius)
{
Matrix transform(aRadius.width, 0, 0, aRadius.height, aOrigin.x, aOrigin.y);
Point currentStartOffset(1, 0);
aSink->LineTo(transform.TransformPoint(currentStartOffset));
for (int i = 0; i < 4; i++) {
// cos(x+pi/2) == -sin(x)
// sin(x+pi/2) == cos(x)
Point currentEndOffset(-currentStartOffset.y, currentStartOffset.x);
PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform);
// We guarantee here the current point is the start point of the next
// curve segment.
currentStartOffset = currentEndOffset;
}
}
/**
* Appends a path represending a rectangle to the path being built by
* aPathBuilder.
*
* aRect The rectangle to append.
* aDrawClockwise If set to true, the path will start at the left of the top
* left edge and draw clockwise. If set to false the path will
* start at the right of the top left edge and draw counter-
* clockwise.
*/
GFX2D_API void AppendRectToPath(PathBuilder* aPathBuilder,
const Rect& aRect,
bool aDrawClockwise = true);
inline already_AddRefed<Path> MakePathForRect(const DrawTarget& aDrawTarget,
const Rect& aRect,
bool aDrawClockwise = true)
{
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
AppendRectToPath(builder, aRect, aDrawClockwise);
return builder->Finish();
}
struct RectCornerRadii {
Size radii[RectCorner::Count];
RectCornerRadii() {}
explicit RectCornerRadii(Float radius) {
for (int i = 0; i < RectCorner::Count; i++) {
radii[i].SizeTo(radius, radius);
}
}
explicit RectCornerRadii(Float radiusX, Float radiusY) {
for (int i = 0; i < RectCorner::Count; i++) {
radii[i].SizeTo(radiusX, radiusY);
}
}
RectCornerRadii(Float tl, Float tr, Float br, Float bl) {
radii[RectCorner::TopLeft].SizeTo(tl, tl);
radii[RectCorner::TopRight].SizeTo(tr, tr);
radii[RectCorner::BottomRight].SizeTo(br, br);
radii[RectCorner::BottomLeft].SizeTo(bl, bl);
}
RectCornerRadii(const Size& tl, const Size& tr,
const Size& br, const Size& bl) {
radii[RectCorner::TopLeft] = tl;
radii[RectCorner::TopRight] = tr;
radii[RectCorner::BottomRight] = br;
radii[RectCorner::BottomLeft] = bl;
}
const Size& operator[](size_t aCorner) const {
return radii[aCorner];
}
Size& operator[](size_t aCorner) {
return radii[aCorner];
}
bool operator==(const RectCornerRadii& aOther) const {
for (size_t i = 0; i < RectCorner::Count; i++) {
if (radii[i] != aOther.radii[i]) return false;
}
return true;
}
void Scale(Float aXScale, Float aYScale) {
for (int i = 0; i < RectCorner::Count; i++) {
radii[i].Scale(aXScale, aYScale);
}
}
const Size TopLeft() const { return radii[RectCorner::TopLeft]; }
Size& TopLeft() { return radii[RectCorner::TopLeft]; }
const Size TopRight() const { return radii[RectCorner::TopRight]; }
Size& TopRight() { return radii[RectCorner::TopRight]; }
const Size BottomRight() const { return radii[RectCorner::BottomRight]; }
Size& BottomRight() { return radii[RectCorner::BottomRight]; }
const Size BottomLeft() const { return radii[RectCorner::BottomLeft]; }
Size& BottomLeft() { return radii[RectCorner::BottomLeft]; }
};
/**
* Appends a path represending a rounded rectangle to the path being built by
* aPathBuilder.
*
* aRect The rectangle to append.
* aCornerRadii Contains the radii of the top-left, top-right, bottom-right
* and bottom-left corners, in that order.
* aDrawClockwise If set to true, the path will start at the left of the top
* left edge and draw clockwise. If set to false the path will
* start at the right of the top left edge and draw counter-
* clockwise.
*/
GFX2D_API void AppendRoundedRectToPath(PathBuilder* aPathBuilder,
const Rect& aRect,
const RectCornerRadii& aRadii,
bool aDrawClockwise = true);
inline already_AddRefed<Path> MakePathForRoundedRect(const DrawTarget& aDrawTarget,
const Rect& aRect,
const RectCornerRadii& aRadii,
bool aDrawClockwise = true)
{
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
AppendRoundedRectToPath(builder, aRect, aRadii, aDrawClockwise);
return builder->Finish();
}
/**
* Appends a path represending an ellipse to the path being built by
* aPathBuilder.
*
* The ellipse extends aDimensions.width / 2.0 in the horizontal direction
* from aCenter, and aDimensions.height / 2.0 in the vertical direction.
*/
GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder,
const Point& aCenter,
const Size& aDimensions);
inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget,
const Point& aCenter,
const Size& aDimensions)
{
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
AppendEllipseToPath(builder, aCenter, aDimensions);
return builder->Finish();
}
/**
* If aDrawTarget's transform only contains a translation, and if this line is
* a horizontal or vertical line, this function will snap the line's vertices
* to align with the device pixel grid so that stroking the line with a one
* pixel wide stroke will result in a crisp line that is not antialiased over
* two pixels across its width.
*
* @return Returns true if this function snaps aRect's vertices, else returns
* false.
*/
GFX2D_API bool SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2,
const DrawTarget& aDrawTarget,
Float aLineWidth);
/**
* This function paints each edge of aRect separately, snapping the edges using
* SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths
* helps ensure not only that the stroke spans a single row of device pixels if
* possible, but also that the ends of stroke dashes start and end on device
* pixels too.
*/
GFX2D_API void StrokeSnappedEdgesOfRect(const Rect& aRect,
DrawTarget& aDrawTarget,
const ColorPattern& aColor,
const StrokeOptions& aStrokeOptions);
/**
* Return the margin, in device space, by which a stroke can extend beyond the
* rendered shape.
* @param aStrokeOptions The stroke options that the stroke is drawn with.
* @param aTransform The user space to device space transform.
* @return The stroke margin.
*/
GFX2D_API Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions,
const Matrix& aTransform);
extern UserDataKey sDisablePixelSnapping;
/**
* If aDrawTarget's transform only contains a translation or, if
* aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this
* function will convert aRect to device space and snap it to device pixels.
* This function returns true if aRect is modified, otherwise it returns false.
*
* Note that the snapping is such that filling the rect using a DrawTarget
* which has the identity matrix as its transform will result in crisp edges.
* (That is, aRect will have integer values, aligning its edges between pixel
* boundaries.) If on the other hand you stroking the rect with an odd valued
* stroke width then the edges of the stroke will be antialiased (assuming an
* AntialiasMode that does antialiasing).
*
* Empty snaps are those which result in a rectangle of 0 area. If they are
* disallowed, an axis is left unsnapped if the rounding process results in a
* length of 0.
*/
inline bool UserToDevicePixelSnapped(Rect& aRect, const DrawTarget& aDrawTarget,
bool aAllowScaleOr90DegreeRotate = false,
bool aAllowEmptySnaps = true)
{
if (aDrawTarget.GetUserData(&sDisablePixelSnapping)) {
return false;
}
Matrix mat = aDrawTarget.GetTransform();
const Float epsilon = 0.0000001f;
#define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
if (!aAllowScaleOr90DegreeRotate &&
(!WITHIN_E(mat._11, 1.f) || !WITHIN_E(mat._22, 1.f) ||
!WITHIN_E(mat._12, 0.f) || !WITHIN_E(mat._21, 0.f))) {
// We have non-translation, but only translation is allowed.
return false;
}
#undef WITHIN_E
Point p1 = mat.TransformPoint(aRect.TopLeft());
Point p2 = mat.TransformPoint(aRect.TopRight());
Point p3 = mat.TransformPoint(aRect.BottomRight());
// Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
// two opposite corners define the entire rectangle. So check if
// the axis-aligned rectangle with opposite corners p1 and p3
// define an axis-aligned rectangle whose other corners are p2 and p4.
// We actually only need to check one of p2 and p4, since an affine
// transform maps parallelograms to parallelograms.
if (p2 == Point(p1.x, p3.y) || p2 == Point(p3.x, p1.y)) {
Point p1r = p1;
Point p3r = p3;
p1r.Round();
p3r.Round();
if (aAllowEmptySnaps || p1r.x != p3r.x) {
p1.x = p1r.x;
p3.x = p3r.x;
}
if (aAllowEmptySnaps || p1r.y != p3r.y) {
p1.y = p1r.y;
p3.y = p3r.y;
}
aRect.MoveTo(Point(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
aRect.SizeTo(Size(std::max(p1.x, p3.x) - aRect.X(),
std::max(p1.y, p3.y) - aRect.Y()));
return true;
}
return false;
}
/**
* This function has the same behavior as UserToDevicePixelSnapped except that
* aRect is not transformed to device space.
*/
inline bool MaybeSnapToDevicePixels(Rect& aRect, const DrawTarget& aDrawTarget,
bool aAllowScaleOr90DegreeRotate = false,
bool aAllowEmptySnaps = true)
{
if (UserToDevicePixelSnapped(aRect, aDrawTarget,
aAllowScaleOr90DegreeRotate, aAllowEmptySnaps)) {
// Since UserToDevicePixelSnapped returned true we know there is no
// rotation/skew in 'mat', so we can just use TransformBounds() here.
Matrix mat = aDrawTarget.GetTransform();
mat.Invert();
aRect = mat.TransformBounds(aRect);
return true;
}
return false;
}
} // namespace gfx
} // namespace mozilla
#endif /* MOZILLA_GFX_PATHHELPERS_H_ */

Some files were not shown because too many files have changed in this diff Show more