summaryrefslogtreecommitdiffstats
path: root/gfx/2d
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /gfx/2d
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/2d')
-rw-r--r--gfx/2d/2D.h2283
-rw-r--r--gfx/2d/AutoHelpersWin.h71
-rw-r--r--gfx/2d/BaseCoord.h101
-rw-r--r--gfx/2d/BaseMargin.h164
-rw-r--r--gfx/2d/BasePoint.h120
-rw-r--r--gfx/2d/BasePoint3D.h142
-rw-r--r--gfx/2d/BasePoint4D.h132
-rw-r--r--gfx/2d/BaseRect.h743
-rw-r--r--gfx/2d/BaseSize.h113
-rw-r--r--gfx/2d/BezierUtils.cpp326
-rw-r--r--gfx/2d/BezierUtils.h186
-rw-r--r--gfx/2d/BigEndianInts.h66
-rw-r--r--gfx/2d/Blur.cpp904
-rw-r--r--gfx/2d/Blur.h198
-rw-r--r--gfx/2d/BlurLS3.cpp569
-rw-r--r--gfx/2d/BlurNEON.cpp303
-rw-r--r--gfx/2d/BlurSSE2.cpp345
-rw-r--r--gfx/2d/BorrowedContext.h131
-rw-r--r--gfx/2d/BufferEdgePad.cpp104
-rw-r--r--gfx/2d/BufferEdgePad.h23
-rw-r--r--gfx/2d/BufferUnrotate.cpp74
-rw-r--r--gfx/2d/BufferUnrotate.h21
-rw-r--r--gfx/2d/ConicGradientEffectD2D1.cpp375
-rw-r--r--gfx/2d/ConicGradientEffectD2D1.h103
-rw-r--r--gfx/2d/ConvolutionFilter.cpp114
-rw-r--r--gfx/2d/ConvolutionFilter.h54
-rw-r--r--gfx/2d/ConvolutionFilterAVX2.cpp104
-rw-r--r--gfx/2d/ConvolutionFilterNEON.cpp287
-rw-r--r--gfx/2d/ConvolutionFilterSSE2.cpp304
-rw-r--r--gfx/2d/Coord.h176
-rw-r--r--gfx/2d/CriticalSection.h84
-rw-r--r--gfx/2d/DWriteSettings.cpp173
-rw-r--r--gfx/2d/DWriteSettings.h41
-rw-r--r--gfx/2d/DataSourceSurface.cpp20
-rw-r--r--gfx/2d/DataSourceSurfaceWrapper.h49
-rw-r--r--gfx/2d/DataSurfaceHelpers.cpp343
-rw-r--r--gfx/2d/DataSurfaceHelpers.h131
-rw-r--r--gfx/2d/DrawEventRecorder.cpp159
-rw-r--r--gfx/2d/DrawEventRecorder.h277
-rw-r--r--gfx/2d/DrawTarget.cpp339
-rw-r--r--gfx/2d/DrawTargetCairo.cpp2010
-rw-r--r--gfx/2d/DrawTargetCairo.h252
-rw-r--r--gfx/2d/DrawTargetD2D1.cpp2383
-rw-r--r--gfx/2d/DrawTargetD2D1.h332
-rw-r--r--gfx/2d/DrawTargetOffset.cpp226
-rw-r--r--gfx/2d/DrawTargetOffset.h191
-rw-r--r--gfx/2d/DrawTargetRecording.cpp770
-rw-r--r--gfx/2d/DrawTargetRecording.h367
-rw-r--r--gfx/2d/DrawTargetSkia.cpp2070
-rw-r--r--gfx/2d/DrawTargetSkia.h212
-rw-r--r--gfx/2d/ExtendInputEffectD2D1.cpp190
-rw-r--r--gfx/2d/ExtendInputEffectD2D1.h87
-rw-r--r--gfx/2d/Factory.cpp1355
-rw-r--r--gfx/2d/FilterNodeD2D1.cpp1140
-rw-r--r--gfx/2d/FilterNodeD2D1.h158
-rw-r--r--gfx/2d/FilterNodeSoftware.cpp3748
-rw-r--r--gfx/2d/FilterNodeSoftware.h780
-rw-r--r--gfx/2d/FilterProcessing.cpp282
-rw-r--r--gfx/2d/FilterProcessing.h209
-rw-r--r--gfx/2d/FilterProcessingSIMD-inl.h1299
-rw-r--r--gfx/2d/FilterProcessingSSE2.cpp126
-rw-r--r--gfx/2d/FilterProcessingScalar.cpp299
-rw-r--r--gfx/2d/Filters.h446
-rw-r--r--gfx/2d/FontVariation.h28
-rw-r--r--gfx/2d/GenericRefCounted.h119
-rw-r--r--gfx/2d/GradientStopsD2D.h42
-rw-r--r--gfx/2d/Helpers.h80
-rw-r--r--gfx/2d/HelpersCairo.h333
-rw-r--r--gfx/2d/HelpersD2D.h983
-rw-r--r--gfx/2d/HelpersSkia.h360
-rw-r--r--gfx/2d/HelpersWinFonts.h33
-rw-r--r--gfx/2d/ImageScaling.cpp245
-rw-r--r--gfx/2d/ImageScaling.h84
-rw-r--r--gfx/2d/ImageScalingSSE2.cpp333
-rw-r--r--gfx/2d/InlineTranslator.cpp117
-rw-r--r--gfx/2d/InlineTranslator.h191
-rw-r--r--gfx/2d/IterableArena.h175
-rw-r--r--gfx/2d/Logging.h965
-rw-r--r--gfx/2d/LoggingConstants.h31
-rw-r--r--gfx/2d/LuminanceNEON.cpp88
-rw-r--r--gfx/2d/LuminanceNEON.h18
-rw-r--r--gfx/2d/MMIHelpers.h232
-rw-r--r--gfx/2d/MacIOSurface.cpp621
-rw-r--r--gfx/2d/MacIOSurface.h162
-rw-r--r--gfx/2d/Matrix.cpp179
-rw-r--r--gfx/2d/Matrix.h2330
-rw-r--r--gfx/2d/MatrixFwd.h39
-rw-r--r--gfx/2d/NativeFontResource.cpp52
-rw-r--r--gfx/2d/NativeFontResourceDWrite.cpp269
-rw-r--r--gfx/2d/NativeFontResourceDWrite.h60
-rw-r--r--gfx/2d/NativeFontResourceFreeType.cpp92
-rw-r--r--gfx/2d/NativeFontResourceFreeType.h79
-rw-r--r--gfx/2d/NativeFontResourceGDI.cpp52
-rw-r--r--gfx/2d/NativeFontResourceGDI.h51
-rw-r--r--gfx/2d/NativeFontResourceMac.cpp174
-rw-r--r--gfx/2d/NativeFontResourceMac.h49
-rw-r--r--gfx/2d/NumericTools.h46
-rw-r--r--gfx/2d/Path.cpp552
-rw-r--r--gfx/2d/PathAnalysis.h67
-rw-r--r--gfx/2d/PathCairo.cpp301
-rw-r--r--gfx/2d/PathCairo.h97
-rw-r--r--gfx/2d/PathD2D.cpp422
-rw-r--r--gfx/2d/PathD2D.h112
-rw-r--r--gfx/2d/PathHelpers.cpp290
-rw-r--r--gfx/2d/PathHelpers.h390
-rw-r--r--gfx/2d/PathRecording.cpp307
-rw-r--r--gfx/2d/PathRecording.h238
-rw-r--r--gfx/2d/PathSkia.cpp273
-rw-r--r--gfx/2d/PathSkia.h110
-rw-r--r--gfx/2d/PatternHelpers.h141
-rw-r--r--gfx/2d/Point.h403
-rw-r--r--gfx/2d/Polygon.h396
-rw-r--r--gfx/2d/QuartzSupport.h106
-rw-r--r--gfx/2d/QuartzSupport.mm582
-rw-r--r--gfx/2d/Quaternion.cpp23
-rw-r--r--gfx/2d/Quaternion.h150
-rw-r--r--gfx/2d/RadialGradientEffectD2D1.cpp405
-rw-r--r--gfx/2d/RadialGradientEffectD2D1.h103
-rw-r--r--gfx/2d/RecordedEvent.cpp209
-rw-r--r--gfx/2d/RecordedEvent.h517
-rw-r--r--gfx/2d/RecordedEventImpl.h4088
-rw-r--r--gfx/2d/RecordingTypes.h71
-rw-r--r--gfx/2d/Rect.h495
-rw-r--r--gfx/2d/RectAbsolute.h305
-rw-r--r--gfx/2d/SFNTData.cpp206
-rw-r--r--gfx/2d/SFNTData.h59
-rw-r--r--gfx/2d/SIMD.h1039
-rw-r--r--gfx/2d/SSEHelpers.h18
-rw-r--r--gfx/2d/SVGTurbulenceRenderer-inl.h362
-rw-r--r--gfx/2d/Scale.h39
-rw-r--r--gfx/2d/ScaleFactor.h98
-rw-r--r--gfx/2d/ScaleFactors2D.h198
-rw-r--r--gfx/2d/ScaledFontBase.cpp223
-rw-r--r--gfx/2d/ScaledFontBase.h59
-rw-r--r--gfx/2d/ScaledFontDWrite.cpp769
-rw-r--r--gfx/2d/ScaledFontDWrite.h105
-rw-r--r--gfx/2d/ScaledFontFontconfig.cpp550
-rw-r--r--gfx/2d/ScaledFontFontconfig.h91
-rw-r--r--gfx/2d/ScaledFontFreeType.cpp139
-rw-r--r--gfx/2d/ScaledFontFreeType.h74
-rw-r--r--gfx/2d/ScaledFontMac.cpp796
-rw-r--r--gfx/2d/ScaledFontMac.h102
-rw-r--r--gfx/2d/ScaledFontWin.cpp121
-rw-r--r--gfx/2d/ScaledFontWin.h46
-rw-r--r--gfx/2d/ShadersD2D.fx824
-rw-r--r--gfx/2d/ShadersD2D.h10855
-rw-r--r--gfx/2d/ShadersD2D1.h1186
-rw-r--r--gfx/2d/ShadersD2D1.hlsl162
-rw-r--r--gfx/2d/SkConvolver.cpp559
-rw-r--r--gfx/2d/SkConvolver.h169
-rw-r--r--gfx/2d/SourceSurfaceCairo.cpp129
-rw-r--r--gfx/2d/SourceSurfaceCairo.h71
-rw-r--r--gfx/2d/SourceSurfaceD2D1.cpp245
-rw-r--r--gfx/2d/SourceSurfaceD2D1.h102
-rw-r--r--gfx/2d/SourceSurfaceRawData.cpp72
-rw-r--r--gfx/2d/SourceSurfaceRawData.h129
-rw-r--r--gfx/2d/SourceSurfaceSkia.cpp228
-rw-r--r--gfx/2d/SourceSurfaceSkia.h78
-rw-r--r--gfx/2d/StackArray.h33
-rw-r--r--gfx/2d/Swizzle.cpp1574
-rw-r--r--gfx/2d/Swizzle.h112
-rw-r--r--gfx/2d/SwizzleAVX2.cpp83
-rw-r--r--gfx/2d/SwizzleNEON.cpp451
-rw-r--r--gfx/2d/SwizzleSSE2.cpp391
-rw-r--r--gfx/2d/SwizzleSSSE3.cpp65
-rw-r--r--gfx/2d/Tools.h198
-rw-r--r--gfx/2d/Triangle.h61
-rw-r--r--gfx/2d/Types.cpp102
-rw-r--r--gfx/2d/Types.h1135
-rw-r--r--gfx/2d/UnscaledFontDWrite.h59
-rw-r--r--gfx/2d/UnscaledFontFreeType.cpp242
-rw-r--r--gfx/2d/UnscaledFontFreeType.h110
-rw-r--r--gfx/2d/UnscaledFontGDI.h46
-rw-r--r--gfx/2d/UnscaledFontMac.h94
-rw-r--r--gfx/2d/UserData.h214
-rw-r--r--gfx/2d/dw-extra.h136
-rw-r--r--gfx/2d/genshaders.sh12
-rw-r--r--gfx/2d/gfx2d.sln29
-rw-r--r--gfx/2d/gfx2d.vcxproj139
-rw-r--r--gfx/2d/moz.build236
-rw-r--r--gfx/2d/ssse3-scaler.c527
-rw-r--r--gfx/2d/ssse3-scaler.h24
-rw-r--r--gfx/2d/unittest/Main.cpp51
-rw-r--r--gfx/2d/unittest/SanityChecks.cpp15
-rw-r--r--gfx/2d/unittest/SanityChecks.h16
-rw-r--r--gfx/2d/unittest/TestBase.cpp44
-rw-r--r--gfx/2d/unittest/TestBase.h53
-rw-r--r--gfx/2d/unittest/TestBugs.cpp80
-rw-r--r--gfx/2d/unittest/TestBugs.h17
-rw-r--r--gfx/2d/unittest/TestCairo.cpp99
-rw-r--r--gfx/2d/unittest/TestDrawTargetBase.cpp103
-rw-r--r--gfx/2d/unittest/TestDrawTargetBase.h38
-rw-r--r--gfx/2d/unittest/TestDrawTargetD2D.cpp22
-rw-r--r--gfx/2d/unittest/TestDrawTargetD2D.h19
-rw-r--r--gfx/2d/unittest/TestPoint.cpp53
-rw-r--r--gfx/2d/unittest/TestPoint.h18
-rw-r--r--gfx/2d/unittest/TestScaling.cpp235
-rw-r--r--gfx/2d/unittest/TestScaling.h22
-rw-r--r--gfx/2d/unittest/unittest.vcxproj94
199 files changed, 76004 insertions, 0 deletions
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h
new file mode 100644
index 0000000000..3f16e39445
--- /dev/null
+++ b/gfx/2d/2D.h
@@ -0,0 +1,2283 @@
+/* -*- 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_2D_H
+#define _MOZILLA_GFX_2D_H
+
+#include "Types.h"
+#include "Point.h"
+#include "Rect.h"
+#include "Matrix.h"
+#include "Quaternion.h"
+#include "UserData.h"
+#include "FontVariation.h"
+#include <vector>
+
+// GenericRefCountedBase allows us to hold on to refcounted objects of any type
+// (contrary to RefCounted<T> which requires knowing the type T) and, in
+// particular, without having a dependency on that type. This is used for
+// DrawTargetSkia to be able to hold on to a GLContext.
+#include "mozilla/GenericRefCounted.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Path.h"
+
+// This RefPtr class isn't ideal for usage in Azure, as it doesn't allow T**
+// outparams using the &-operator. But it will have to do as there's no easy
+// solution.
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "mozilla/Atomics.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsRegionFwd.h"
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
+# ifndef MOZ_ENABLE_FREETYPE
+# define MOZ_ENABLE_FREETYPE
+# endif
+#endif
+
+struct _cairo_surface;
+typedef _cairo_surface cairo_surface_t;
+
+struct _cairo_scaled_font;
+typedef _cairo_scaled_font cairo_scaled_font_t;
+
+struct FT_LibraryRec_;
+typedef FT_LibraryRec_* FT_Library;
+
+struct FT_FaceRec_;
+typedef FT_FaceRec_* FT_Face;
+
+typedef int FT_Error;
+
+struct _FcPattern;
+typedef _FcPattern FcPattern;
+
+struct ID3D11Texture2D;
+struct ID3D11Device;
+struct ID2D1Device;
+struct ID2D1DeviceContext;
+struct ID2D1Multithread;
+struct IDWriteFactory;
+struct IDWriteRenderingParams;
+struct IDWriteFontFace;
+struct IDWriteFontCollection;
+
+class SkCanvas;
+struct gfxFontStyle;
+
+struct CGContext;
+typedef struct CGContext* CGContextRef;
+
+struct CGFont;
+typedef CGFont* CGFontRef;
+
+namespace mozilla {
+
+class Mutex;
+
+namespace layers {
+class TextureData;
+}
+
+namespace wr {
+struct FontInstanceOptions;
+struct FontInstancePlatformOptions;
+} // namespace wr
+
+namespace gfx {
+class UnscaledFont;
+class ScaledFont;
+} // namespace gfx
+
+namespace gfx {
+
+class AlphaBoxBlur;
+class ScaledFont;
+class SourceSurface;
+class DataSourceSurface;
+class DrawTarget;
+class DrawEventRecorder;
+class FilterNode;
+class LogForwarder;
+
+struct NativeSurface {
+ NativeSurfaceType mType;
+ SurfaceFormat mFormat;
+ gfx::IntSize mSize;
+ void* mSurface;
+};
+
+/**
+ * This structure is used to send draw options that are universal to all drawing
+ * operations.
+ */
+struct DrawOptions {
+ /// For constructor parameter description, see member data documentation.
+ explicit DrawOptions(Float aAlpha = 1.0f,
+ CompositionOp aCompositionOp = CompositionOp::OP_OVER,
+ AntialiasMode aAntialiasMode = AntialiasMode::DEFAULT)
+ : mAlpha(aAlpha),
+ mCompositionOp(aCompositionOp),
+ mAntialiasMode(aAntialiasMode) {}
+
+ Float mAlpha; /**< Alpha value by which the mask generated by this
+ operation is multiplied. */
+ CompositionOp mCompositionOp; /**< The operator that indicates how the source
+ and destination patterns are blended. */
+ AntialiasMode mAntialiasMode; /**< The AntiAlias mode used for this drawing
+ operation. */
+};
+
+struct StoredStrokeOptions;
+
+/**
+ * This structure is used to send stroke options that are used in stroking
+ * operations.
+ */
+struct StrokeOptions {
+ /// For constructor parameter description, see member data documentation.
+ explicit StrokeOptions(Float aLineWidth = 1.0f,
+ JoinStyle aLineJoin = JoinStyle::MITER_OR_BEVEL,
+ CapStyle aLineCap = CapStyle::BUTT,
+ Float aMiterLimit = 10.0f, size_t aDashLength = 0,
+ const Float* aDashPattern = 0, Float aDashOffset = 0.f)
+ : mLineWidth(aLineWidth),
+ mMiterLimit(aMiterLimit),
+ mDashPattern(aDashLength > 0 ? aDashPattern : 0),
+ mDashLength(aDashLength),
+ mDashOffset(aDashOffset),
+ mLineJoin(aLineJoin),
+ mLineCap(aLineCap) {
+ MOZ_ASSERT(aDashLength == 0 || aDashPattern);
+ }
+
+ Float mLineWidth; //!< Width of the stroke in userspace.
+ Float mMiterLimit; //!< Miter limit in units of linewidth
+ const Float* mDashPattern; /**< Series of on/off userspace lengths defining
+ dash. Owned by the caller; must live at least as
+ long as this StrokeOptions.
+ mDashPattern != null <=> mDashLength > 0. */
+ size_t mDashLength; //!< Number of on/off lengths in mDashPattern.
+ Float mDashOffset; /**< Userspace offset within mDashPattern at which
+ stroking begins. */
+ JoinStyle mLineJoin; //!< Join style used for joining lines.
+ CapStyle mLineCap; //!< Cap style used for capping lines.
+
+ StoredStrokeOptions* Clone() const;
+
+ bool operator==(const StrokeOptions& aOther) const {
+ return mLineWidth == aOther.mLineWidth &&
+ mMiterLimit == aOther.mMiterLimit &&
+ mDashLength == aOther.mDashLength &&
+ (!mDashLength || (mDashPattern && aOther.mDashPattern &&
+ !memcmp(mDashPattern, aOther.mDashPattern,
+ mDashLength * sizeof(Float)))) &&
+ mDashOffset == aOther.mDashOffset && mLineJoin == aOther.mLineJoin &&
+ mLineCap == aOther.mLineCap;
+ }
+};
+
+/**
+ * Heap-allocated variation of StrokeOptions that ensures dash patterns are
+ * properly allocated and destroyed even if the source was stack-allocated.
+ */
+struct StoredStrokeOptions : public StrokeOptions {
+ explicit StoredStrokeOptions(const StrokeOptions& aOptions)
+ : StrokeOptions(aOptions) {
+ if (mDashLength) {
+ Float* pattern = new Float[mDashLength];
+ memcpy(pattern, mDashPattern, mDashLength * sizeof(Float));
+ mDashPattern = pattern;
+ }
+ }
+
+ ~StoredStrokeOptions() {
+ if (mDashPattern) {
+ delete[] mDashPattern;
+ }
+ }
+};
+
+inline StoredStrokeOptions* StrokeOptions::Clone() const {
+ return new StoredStrokeOptions(*this);
+}
+
+/**
+ * This structure supplies additional options for calls to DrawSurface.
+ */
+struct DrawSurfaceOptions {
+ /// For constructor parameter description, see member data documentation.
+ explicit DrawSurfaceOptions(
+ SamplingFilter aSamplingFilter = SamplingFilter::LINEAR,
+ SamplingBounds aSamplingBounds = SamplingBounds::UNBOUNDED)
+ : mSamplingFilter(aSamplingFilter), mSamplingBounds(aSamplingBounds) {}
+
+ SamplingFilter
+ mSamplingFilter; /**< SamplingFilter used when resampling source surface
+ region to the destination region. */
+ SamplingBounds mSamplingBounds; /**< This indicates whether the implementation
+ is allowed to sample pixels outside the
+ source rectangle as specified in
+ DrawSurface on the surface. */
+};
+
+/**
+ * ShadowOptions supplies options necessary for describing the appearance of a
+ * a shadow in draw calls that use shadowing.
+ */
+struct ShadowOptions {
+ explicit ShadowOptions(const DeviceColor& aColor = DeviceColor(0.0f, 0.0f,
+ 0.0f),
+ const Point& aOffset = Point(), Float aSigma = 0.0f)
+ : mColor(aColor), mOffset(aOffset), mSigma(aSigma) {}
+
+ DeviceColor mColor; /**< Color of the drawn shadow. */
+ Point mOffset; /**< Offset of the shadow. */
+ Float mSigma; /**< Sigma used for the Gaussian filter kernel. */
+
+ int32_t BlurRadius() const;
+};
+
+/**
+ * This class is used to store gradient stops, it can only be used with a
+ * matching DrawTarget. Not adhering to this condition will make a draw call
+ * fail.
+ */
+class GradientStops : public SupportsThreadSafeWeakPtr<GradientStops> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStops)
+ virtual ~GradientStops() = default;
+
+ virtual BackendType GetBackendType() const = 0;
+ virtual bool IsValid() const { return true; }
+
+ protected:
+ GradientStops() = default;
+};
+
+/**
+ * This is the base class for 'patterns'. Patterns describe the pixels used as
+ * the source for a masked composition operation that is done by the different
+ * drawing commands. These objects are not backend specific, however for
+ * example the gradient stops on a gradient pattern can be backend specific.
+ */
+class Pattern {
+ public:
+ virtual ~Pattern() = default;
+
+ virtual PatternType GetType() const = 0;
+
+ /** Instantiate a new clone with the same pattern type and values. Any
+ * internal strong references will be converted to weak references. */
+ virtual Pattern* CloneWeak() const { return nullptr; }
+
+ /** Whether the pattern holds an internal weak reference. */
+ virtual bool IsWeak() const { return false; }
+
+ /** Whether any internal weak references still point to a target. */
+ virtual bool IsValid() const { return true; }
+
+ /** Determine if the pattern type and values exactly match. */
+ virtual bool operator==(const Pattern& aOther) const = 0;
+
+ bool operator!=(const Pattern& aOther) const { return !(*this == aOther); }
+
+ protected:
+ Pattern() = default;
+
+ // Utility functions to check if a weak reference is still valid.
+ template <typename T>
+ static inline bool IsRefValid(const RefPtr<T>& aPtr) {
+ // RefPtrs are always valid.
+ return true;
+ }
+
+ template <typename T>
+ static inline bool IsRefValid(const ThreadSafeWeakPtr<T>& aPtr) {
+ // Weak refs are only valid if they aren't dead.
+ return !aPtr.IsDead();
+ }
+};
+
+class ColorPattern : public Pattern {
+ public:
+ // Explicit because consumers should generally use ToDeviceColor when
+ // creating a ColorPattern.
+ explicit ColorPattern(const DeviceColor& aColor) : mColor(aColor) {}
+
+ PatternType GetType() const override { return PatternType::COLOR; }
+
+ Pattern* CloneWeak() const override { return new ColorPattern(mColor); }
+
+ bool operator==(const Pattern& aOther) const override {
+ if (aOther.GetType() != PatternType::COLOR) {
+ return false;
+ }
+ const ColorPattern& other = static_cast<const ColorPattern&>(aOther);
+ return mColor == other.mColor;
+ }
+
+ DeviceColor mColor;
+};
+
+/**
+ * This class is used for Linear Gradient Patterns, the gradient stops are
+ * stored in a separate object and are backend dependent. This class itself
+ * may be used on the stack.
+ */
+template <template <typename> typename REF = RefPtr>
+class LinearGradientPatternT : public Pattern {
+ typedef LinearGradientPatternT<ThreadSafeWeakPtr> Weak;
+
+ public:
+ /// For constructor parameter description, see member data documentation.
+ LinearGradientPatternT(const Point& aBegin, const Point& aEnd,
+ RefPtr<GradientStops> aStops,
+ const Matrix& aMatrix = Matrix())
+ : mBegin(aBegin),
+ mEnd(aEnd),
+ mStops(std::move(aStops)),
+ mMatrix(aMatrix) {}
+
+ PatternType GetType() const override { return PatternType::LINEAR_GRADIENT; }
+
+ Pattern* CloneWeak() const override {
+ return new Weak(mBegin, mEnd, do_AddRef(mStops), mMatrix);
+ }
+
+ bool IsWeak() const override {
+ return std::is_same<decltype(*this), Weak>::value;
+ }
+
+ bool IsValid() const override { return IsRefValid(mStops); }
+
+ template <template <typename> typename T>
+ bool operator==(const LinearGradientPatternT<T>& aOther) const {
+ return mBegin == aOther.mBegin && mEnd == aOther.mEnd &&
+ mStops == aOther.mStops && mMatrix.ExactlyEquals(aOther.mMatrix);
+ }
+
+ bool operator==(const Pattern& aOther) const override {
+ if (aOther.GetType() != PatternType::LINEAR_GRADIENT) {
+ return false;
+ }
+ return aOther.IsWeak()
+ ? *this == static_cast<const Weak&>(aOther)
+ : *this == static_cast<const LinearGradientPatternT<>&>(aOther);
+ }
+
+ Point mBegin; //!< Start of the linear gradient
+ Point mEnd; /**< End of the linear gradient - NOTE: In the case
+ of a zero length gradient it will act as the
+ color of the last stop. */
+ REF<GradientStops> mStops; /**< GradientStops object for this gradient, this
+ should match the backend type of the draw
+ target this pattern will be used with. */
+ Matrix mMatrix; /**< A matrix that transforms the pattern into
+ user space */
+};
+
+typedef LinearGradientPatternT<> LinearGradientPattern;
+
+/**
+ * This class is used for Radial Gradient Patterns, the gradient stops are
+ * stored in a separate object and are backend dependent. This class itself
+ * may be used on the stack.
+ */
+template <template <typename> typename REF = RefPtr>
+class RadialGradientPatternT : public Pattern {
+ typedef RadialGradientPatternT<ThreadSafeWeakPtr> Weak;
+
+ public:
+ /// For constructor parameter description, see member data documentation.
+ RadialGradientPatternT(const Point& aCenter1, const Point& aCenter2,
+ Float aRadius1, Float aRadius2,
+ RefPtr<GradientStops> aStops,
+ const Matrix& aMatrix = Matrix())
+ : mCenter1(aCenter1),
+ mCenter2(aCenter2),
+ mRadius1(aRadius1),
+ mRadius2(aRadius2),
+ mStops(std::move(aStops)),
+ mMatrix(aMatrix) {}
+
+ PatternType GetType() const override { return PatternType::RADIAL_GRADIENT; }
+
+ Pattern* CloneWeak() const override {
+ return new Weak(mCenter1, mCenter2, mRadius1, mRadius2, do_AddRef(mStops),
+ mMatrix);
+ }
+
+ bool IsWeak() const override {
+ return std::is_same<decltype(*this), Weak>::value;
+ }
+
+ bool IsValid() const override { return IsRefValid(mStops); }
+
+ template <template <typename> typename T>
+ bool operator==(const RadialGradientPatternT<T>& aOther) const {
+ return mCenter1 == aOther.mCenter1 && mCenter2 == aOther.mCenter2 &&
+ mRadius1 == aOther.mRadius1 && mRadius2 == aOther.mRadius2 &&
+ mStops == aOther.mStops && mMatrix.ExactlyEquals(aOther.mMatrix);
+ }
+
+ bool operator==(const Pattern& aOther) const override {
+ if (aOther.GetType() != PatternType::RADIAL_GRADIENT) {
+ return false;
+ }
+ return aOther.IsWeak()
+ ? *this == static_cast<const Weak&>(aOther)
+ : *this == static_cast<const RadialGradientPatternT<>&>(aOther);
+ }
+
+ Point mCenter1; //!< Center of the inner (focal) circle.
+ Point mCenter2; //!< Center of the outer circle.
+ Float mRadius1; //!< Radius of the inner (focal) circle.
+ Float mRadius2; //!< Radius of the outer circle.
+ REF<GradientStops> mStops; /**< GradientStops object for this gradient, this
+ should match the backend type of the draw
+ target this pattern will be used with. */
+ Matrix mMatrix; //!< A matrix that transforms the pattern into user space
+};
+
+typedef RadialGradientPatternT<> RadialGradientPattern;
+
+/**
+ * This class is used for Conic Gradient Patterns, the gradient stops are
+ * stored in a separate object and are backend dependent. This class itself
+ * may be used on the stack.
+ */
+template <template <typename> typename REF = RefPtr>
+class ConicGradientPatternT : public Pattern {
+ typedef ConicGradientPatternT<ThreadSafeWeakPtr> Weak;
+
+ public:
+ /// For constructor parameter description, see member data documentation.
+ ConicGradientPatternT(const Point& aCenter, Float aAngle, Float aStartOffset,
+ Float aEndOffset, RefPtr<GradientStops> aStops,
+ const Matrix& aMatrix = Matrix())
+ : mCenter(aCenter),
+ mAngle(aAngle),
+ mStartOffset(aStartOffset),
+ mEndOffset(aEndOffset),
+ mStops(std::move(aStops)),
+ mMatrix(aMatrix) {}
+
+ PatternType GetType() const override { return PatternType::CONIC_GRADIENT; }
+
+ Pattern* CloneWeak() const override {
+ return new Weak(mCenter, mAngle, mStartOffset, mEndOffset,
+ do_AddRef(mStops), mMatrix);
+ }
+
+ bool IsWeak() const override {
+ return std::is_same<decltype(*this), Weak>::value;
+ }
+
+ bool IsValid() const override { return IsRefValid(mStops); }
+
+ template <template <typename> typename T>
+ bool operator==(const ConicGradientPatternT<T>& aOther) const {
+ return mCenter == aOther.mCenter && mAngle == aOther.mAngle &&
+ mStartOffset == aOther.mStartOffset &&
+ mEndOffset == aOther.mEndOffset && mStops == aOther.mStops &&
+ mMatrix.ExactlyEquals(aOther.mMatrix);
+ }
+
+ bool operator==(const Pattern& aOther) const override {
+ if (aOther.GetType() != PatternType::CONIC_GRADIENT) {
+ return false;
+ }
+ return aOther.IsWeak()
+ ? *this == static_cast<const Weak&>(aOther)
+ : *this == static_cast<const ConicGradientPatternT<>&>(aOther);
+ }
+
+ Point mCenter; //!< Center of the gradient
+ Float mAngle; //!< Start angle of gradient
+ Float mStartOffset; // Offset of first stop
+ Float mEndOffset; // Offset of last stop
+ REF<GradientStops> mStops; /**< GradientStops object for this gradient, this
+ should match the backend type of the draw
+ target this pattern will be used with. */
+ Matrix mMatrix; //!< A matrix that transforms the pattern into user space
+};
+
+typedef ConicGradientPatternT<> ConicGradientPattern;
+
+/**
+ * This class is used for Surface Patterns, they wrap a surface and a
+ * repetition mode for the surface. This may be used on the stack.
+ */
+template <template <typename> typename REF = RefPtr>
+class SurfacePatternT : public Pattern {
+ typedef SurfacePatternT<ThreadSafeWeakPtr> Weak;
+
+ public:
+ /// For constructor parameter description, see member data documentation.
+ SurfacePatternT(RefPtr<SourceSurface> aSourceSurface, ExtendMode aExtendMode,
+ const Matrix& aMatrix = Matrix(),
+ SamplingFilter aSamplingFilter = SamplingFilter::GOOD,
+ const IntRect& aSamplingRect = IntRect())
+ : mSurface(std::move(aSourceSurface)),
+ mExtendMode(aExtendMode),
+ mSamplingFilter(aSamplingFilter),
+ mMatrix(aMatrix),
+ mSamplingRect(aSamplingRect) {}
+
+ PatternType GetType() const override { return PatternType::SURFACE; }
+
+ Pattern* CloneWeak() const override {
+ return new Weak(do_AddRef(mSurface), mExtendMode, mMatrix, mSamplingFilter,
+ mSamplingRect);
+ }
+
+ bool IsWeak() const override {
+ return std::is_same<decltype(*this), Weak>::value;
+ }
+
+ bool IsValid() const override { return IsRefValid(mSurface); }
+
+ template <template <typename> typename T>
+ bool operator==(const SurfacePatternT<T>& aOther) const {
+ return mSurface == aOther.mSurface && mExtendMode == aOther.mExtendMode &&
+ mSamplingFilter == aOther.mSamplingFilter &&
+ mMatrix.ExactlyEquals(aOther.mMatrix) &&
+ mSamplingRect.IsEqualEdges(aOther.mSamplingRect);
+ }
+
+ bool operator==(const Pattern& aOther) const override {
+ if (aOther.GetType() != PatternType::SURFACE) {
+ return false;
+ }
+ return aOther.IsWeak()
+ ? *this == static_cast<const Weak&>(aOther)
+ : *this == static_cast<const SurfacePatternT<>&>(aOther);
+ }
+
+ REF<SourceSurface> mSurface; //!< Surface to use for drawing
+ ExtendMode mExtendMode; /**< This determines how the image is extended
+ outside the bounds of the image */
+ SamplingFilter
+ mSamplingFilter; //!< Resampling filter for resampling the image.
+ Matrix mMatrix; //!< Transforms the pattern into user space
+
+ IntRect mSamplingRect; /**< Rect that must not be sampled outside of,
+ or an empty rect if none has been
+ specified. */
+};
+
+typedef SurfacePatternT<> SurfacePattern;
+
+class StoredPattern;
+
+static const int32_t kReasonableSurfaceSize = 8192;
+
+/**
+ * This is the base class for source surfaces. These objects are surfaces
+ * which may be used as a source in a SurfacePattern or a DrawSurface call.
+ * They cannot be drawn to directly.
+ *
+ * Although SourceSurface has thread-safe refcount, some SourceSurface cannot
+ * be used on random threads at the same time. Only DataSourceSurface can be
+ * used on random threads now. This will be fixed in the future. Eventually
+ * all SourceSurface should be thread-safe.
+ */
+class SourceSurface : public SupportsThreadSafeWeakPtr<SourceSurface> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurface)
+ virtual ~SourceSurface() = default;
+
+ virtual SurfaceType GetType() const = 0;
+ virtual IntSize GetSize() const = 0;
+ /* GetRect is useful for when the underlying surface doesn't actually
+ * have a backing store starting at 0, 0. e.g. SourceSurfaceOffset */
+ virtual IntRect GetRect() const { return IntRect(IntPoint(0, 0), GetSize()); }
+ virtual SurfaceFormat GetFormat() const = 0;
+
+ /**
+ * Structure containing memory size information for the surface.
+ */
+ struct SizeOfInfo {
+ SizeOfInfo()
+ : mHeapBytes(0),
+ mNonHeapBytes(0),
+ mUnknownBytes(0),
+ mExternalHandles(0),
+ mExternalId(0),
+ mTypes(0) {}
+
+ void Accumulate(const SizeOfInfo& aOther) {
+ mHeapBytes += aOther.mHeapBytes;
+ mNonHeapBytes += aOther.mNonHeapBytes;
+ mUnknownBytes += aOther.mUnknownBytes;
+ mExternalHandles += aOther.mExternalHandles;
+ if (aOther.mExternalId) {
+ mExternalId = aOther.mExternalId;
+ }
+ mTypes |= aOther.mTypes;
+ }
+
+ void AddType(SurfaceType aType) { mTypes |= 1 << uint32_t(aType); }
+
+ size_t mHeapBytes; // Bytes allocated on the heap.
+ size_t mNonHeapBytes; // Bytes allocated off the heap.
+ size_t mUnknownBytes; // Bytes allocated to either, but unknown.
+ size_t mExternalHandles; // Open handles for the surface.
+ uint64_t mExternalId; // External ID for WebRender, if available.
+ uint32_t mTypes; // Bit shifted values representing SurfaceType.
+ };
+
+ /**
+ * Get the size information of the underlying data buffer.
+ */
+ virtual void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ SizeOfInfo& aInfo) const {
+ // Default is to estimate the footprint based on its size/format.
+ auto size = GetSize();
+ auto format = GetFormat();
+ aInfo.AddType(GetType());
+ aInfo.mUnknownBytes = size.width * size.height * BytesPerPixel(format);
+ }
+
+ /** This returns false if some event has made this source surface invalid for
+ * usage with current DrawTargets. For example in the case of Direct2D this
+ * could return false if we have switched devices since this surface was
+ * created.
+ */
+ virtual bool IsValid() const { return true; }
+
+ /**
+ * This returns true if it is the same underlying surface data, even if
+ * the objects are different (e.g. indirection due to
+ * DataSourceSurfaceWrapper).
+ */
+ virtual bool Equals(SourceSurface* aOther, bool aSymmetric = true) {
+ return this == aOther ||
+ (aSymmetric && aOther && aOther->Equals(this, false));
+ }
+
+ /**
+ * This function will return true if the surface type matches that of a
+ * DataSourceSurface and if GetDataSurface will return the same object.
+ */
+ bool IsDataSourceSurface() const {
+ switch (GetType()) {
+ case SurfaceType::DATA:
+ case SurfaceType::DATA_SHARED:
+ case SurfaceType::DATA_RECYCLING_SHARED:
+ case SurfaceType::DATA_ALIGNED:
+ case SurfaceType::DATA_SHARED_WRAPPER:
+ case SurfaceType::DATA_MAPPED:
+ case SurfaceType::SKIA:
+ case SurfaceType::WEBGL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * This function will get a DataSourceSurface for this surface, a
+ * DataSourceSurface's data can be accessed directly.
+ */
+ virtual already_AddRefed<DataSourceSurface> GetDataSurface() = 0;
+
+ /** This function will return a SourceSurface without any offset. */
+ virtual already_AddRefed<SourceSurface> GetUnderlyingSurface() {
+ RefPtr<SourceSurface> surface = this;
+ return surface.forget();
+ }
+
+ /** Tries to get this SourceSurface's native surface. This will fail if aType
+ * is not the type of this SourceSurface's native surface.
+ */
+ virtual void* GetNativeSurface(NativeSurfaceType aType) { return nullptr; }
+
+ void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) {
+ mUserData.Add(key, userData, destroy);
+ }
+ void* GetUserData(UserDataKey* key) const { return mUserData.Get(key); }
+ void RemoveUserData(UserDataKey* key) { mUserData.RemoveAndDestroy(key); }
+
+ /** Tries to extract an optimal subrect for the surface. This may fail if the
+ * request can't be satisfied.
+ */
+ virtual already_AddRefed<SourceSurface> ExtractSubrect(const IntRect& aRect) {
+ return nullptr;
+ }
+
+ protected:
+ friend class StoredPattern;
+
+ ThreadSafeUserData mUserData;
+};
+
+class DataSourceSurface : public SourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurface, override)
+ DataSourceSurface() : mMapCount(0) {}
+
+#ifdef DEBUG
+ virtual ~DataSourceSurface() { MOZ_ASSERT(mMapCount == 0); }
+#endif
+
+ struct MappedSurface {
+ uint8_t* mData = nullptr;
+ int32_t mStride = 0;
+ };
+
+ enum MapType { READ, WRITE, READ_WRITE };
+
+ /**
+ * This is a scoped version of Map(). Map() is called in the constructor and
+ * Unmap() in the destructor. Use this for automatic unmapping of your data
+ * surfaces.
+ *
+ * Use IsMapped() to verify whether Map() succeeded or not.
+ */
+ class ScopedMap final {
+ public:
+ ScopedMap(DataSourceSurface* aSurface, MapType aType)
+ : mSurface(aSurface), mIsMapped(aSurface->Map(aType, &mMap)) {}
+
+ ScopedMap(ScopedMap&& aOther)
+ : mSurface(std::move(aOther.mSurface)),
+ mMap(aOther.mMap),
+ mIsMapped(aOther.mIsMapped) {
+ aOther.mMap.mData = nullptr;
+ aOther.mIsMapped = false;
+ }
+
+ ScopedMap& operator=(ScopedMap&& aOther) {
+ if (mIsMapped) {
+ mSurface->Unmap();
+ }
+ mSurface = std::move(aOther.mSurface);
+ mMap = aOther.mMap;
+ mIsMapped = aOther.mIsMapped;
+ aOther.mMap.mData = nullptr;
+ aOther.mIsMapped = false;
+ return *this;
+ }
+
+ ~ScopedMap() {
+ if (mIsMapped) {
+ mSurface->Unmap();
+ }
+ }
+
+ uint8_t* GetData() const {
+ MOZ_ASSERT(mIsMapped);
+ return mMap.mData;
+ }
+
+ int32_t GetStride() const {
+ MOZ_ASSERT(mIsMapped);
+ return mMap.mStride;
+ }
+
+ const MappedSurface* GetMappedSurface() const {
+ MOZ_ASSERT(mIsMapped);
+ return &mMap;
+ }
+
+ const DataSourceSurface* GetSurface() const {
+ MOZ_ASSERT(mIsMapped);
+ return mSurface;
+ }
+
+ bool IsMapped() const { return mIsMapped; }
+
+ private:
+ ScopedMap(const ScopedMap& aOther) = delete;
+ ScopedMap& operator=(const ScopedMap& aOther) = delete;
+
+ RefPtr<DataSourceSurface> mSurface;
+ MappedSurface mMap;
+ bool mIsMapped;
+ };
+
+ SurfaceType GetType() const override { return SurfaceType::DATA; }
+ /** @deprecated
+ * Get the raw bitmap data of the surface.
+ * Can return null if there was OOM allocating surface data.
+ *
+ * Deprecated means you shouldn't be using this!! Use Map instead.
+ * Please deny any reviews which add calls to this!
+ */
+ virtual uint8_t* GetData() = 0;
+
+ /** @deprecated
+ * Stride of the surface, distance in bytes between the start of the image
+ * data belonging to row y and row y+1. This may be negative.
+ * Can return 0 if there was OOM allocating surface data.
+ */
+ virtual int32_t Stride() = 0;
+
+ /**
+ * The caller is responsible for ensuring aMappedSurface is not null.
+ // Althought Map (and Moz2D in general) isn't normally threadsafe,
+ // we want to allow it for SourceSurfaceRawData since it should
+ // always be fine (for reading at least).
+ //
+ // This is the same as the base class implementation except using
+ // mMapCount instead of mIsMapped since that breaks for multithread.
+ //
+ // Once mfbt supports Monitors we should implement proper read/write
+ // locking to prevent write races.
+ */
+ virtual bool Map(MapType, MappedSurface* aMappedSurface) {
+ aMappedSurface->mData = GetData();
+ aMappedSurface->mStride = Stride();
+ bool success = !!aMappedSurface->mData;
+ if (success) {
+ mMapCount++;
+ }
+ return success;
+ }
+
+ virtual void Unmap() {
+ mMapCount--;
+ MOZ_ASSERT(mMapCount >= 0);
+ }
+
+ /**
+ * Returns a DataSourceSurface with the same data as this one, but
+ * guaranteed to have surface->GetType() == SurfaceType::DATA.
+ *
+ * The returning surface might be null, because of OOM or gfx device reset.
+ * The caller needs to do null-check before using it.
+ */
+ already_AddRefed<DataSourceSurface> GetDataSurface() override;
+
+ /**
+ * Returns whether or not the data was allocated on the heap. This should
+ * be used to determine if the memory needs to be cleared to 0.
+ */
+ virtual bool OnHeap() const { return true; }
+
+ /**
+ * Yields a dirty rect of what has changed since it was last called.
+ */
+ virtual Maybe<IntRect> TakeDirtyRect() { return Nothing(); }
+
+ /**
+ * Indicate a region which has changed in the surface.
+ */
+ virtual void Invalidate(const IntRect& aDirtyRect) {}
+
+ protected:
+ Atomic<int32_t> mMapCount;
+};
+
+/** This is an abstract object that accepts path segments. */
+class PathSink : public RefCounted<PathSink> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathSink)
+ virtual ~PathSink() = default;
+
+ /** Move the current point in the path, any figure currently being drawn will
+ * be considered closed during fill operations, however when stroking the
+ * closing line segment will not be drawn.
+ */
+ virtual void MoveTo(const Point& aPoint) = 0;
+ /** Add a linesegment to the current figure */
+ virtual void LineTo(const Point& aPoint) = 0;
+ /** Add a cubic bezier curve to the current figure */
+ virtual void BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) = 0;
+ /** Add a quadratic bezier curve to the current figure */
+ virtual void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) = 0;
+ /** Close the current figure, this will essentially generate a line segment
+ * from the current point to the starting point for the current figure
+ */
+ virtual void Close() = 0;
+ /** Add an arc to the current figure */
+ virtual void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise = false) = 0;
+
+ virtual Point CurrentPoint() const { return mCurrentPoint; }
+
+ virtual Point BeginPoint() const { return mBeginPoint; }
+
+ virtual void SetCurrentPoint(const Point& aPoint) { mCurrentPoint = aPoint; }
+
+ virtual void SetBeginPoint(const Point& aPoint) { mBeginPoint = aPoint; }
+
+ protected:
+ /** Point the current subpath is at - or where the next subpath will start
+ * if there is no active subpath.
+ */
+ Point mCurrentPoint;
+
+ /** Position of the previous MoveTo operation. */
+ Point mBeginPoint;
+};
+
+class PathBuilder;
+class FlattenedPath;
+
+/** The path class is used to create (sets of) figures of any shape that can be
+ * filled or stroked to a DrawTarget
+ */
+class Path : public external::AtomicRefCounted<Path> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(Path)
+ virtual ~Path();
+
+ virtual BackendType GetBackendType() const = 0;
+
+ /** This returns a PathBuilder object that contains a copy of the contents of
+ * this path and is still writable.
+ */
+ inline already_AddRefed<PathBuilder> CopyToBuilder() const {
+ return CopyToBuilder(GetFillRule());
+ }
+ inline already_AddRefed<PathBuilder> TransformedCopyToBuilder(
+ const Matrix& aTransform) const {
+ return TransformedCopyToBuilder(aTransform, GetFillRule());
+ }
+ /** This returns a PathBuilder object that contains a copy of the contents of
+ * this path, converted to use the specified FillRule, and still writable.
+ */
+ virtual already_AddRefed<PathBuilder> CopyToBuilder(
+ FillRule aFillRule) const = 0;
+ virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(
+ const Matrix& aTransform, FillRule aFillRule) const = 0;
+
+ /** This function checks if a point lies within a path. It allows passing a
+ * transform that will transform the path to the coordinate space in which
+ * aPoint is given.
+ */
+ virtual bool ContainsPoint(const Point& aPoint,
+ const Matrix& aTransform) const = 0;
+
+ /** This function checks if a point lies within the stroke of a path using the
+ * specified strokeoptions. It allows passing a transform that will transform
+ * the path to the coordinate space in which aPoint is given.
+ */
+ virtual bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
+ const Point& aPoint,
+ const Matrix& aTransform) const = 0;
+
+ /** This functions gets the bounds of this path. These bounds are not
+ * guaranteed to be tight. A transform may be specified that gives the bounds
+ * after application of the transform.
+ */
+ virtual Rect GetBounds(const Matrix& aTransform = Matrix()) const = 0;
+
+ /** This function gets the bounds of the stroke of this path using the
+ * specified strokeoptions. These bounds are not guaranteed to be tight.
+ * A transform may be specified that gives the bounds after application of
+ * the transform.
+ */
+ virtual Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform = Matrix()) const = 0;
+
+ /** Gets conservative bounds for the path, optionally stroked or transformed.
+ * This function will prioritize speed of computation over tightness of the
+ * computed bounds if the backend supports the distinction.
+ */
+ virtual Rect GetFastBounds(
+ const Matrix& aTransform = Matrix(),
+ const StrokeOptions* aStrokeOptions = nullptr) const;
+
+ /** Take the contents of this path and stream it to another sink, this works
+ * regardless of the backend that might be used for the destination sink.
+ */
+ virtual void StreamToSink(PathSink* aSink) const = 0;
+
+ /** This gets the fillrule this path's builder was created with. This is not
+ * mutable.
+ */
+ virtual FillRule GetFillRule() const = 0;
+
+ virtual Float ComputeLength();
+
+ virtual Maybe<Rect> AsRect() const { return Nothing(); }
+
+ virtual Point ComputePointAtLength(Float aLength, Point* aTangent = nullptr);
+
+ protected:
+ Path();
+ void EnsureFlattenedPath();
+
+ RefPtr<FlattenedPath> mFlattenedPath;
+};
+
+/** The PathBuilder class allows path creation. Once finish is called on the
+ * pathbuilder it may no longer be written to.
+ */
+class PathBuilder : public PathSink {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilder, override)
+ /** Finish writing to the path and return a Path object that can be used for
+ * drawing. Future use of the builder results in a crash!
+ */
+ virtual already_AddRefed<Path> Finish() = 0;
+
+ virtual BackendType GetBackendType() const = 0;
+};
+
+struct Glyph {
+ uint32_t mIndex;
+ Point mPosition;
+};
+
+static inline bool operator==(const Glyph& aOne, const Glyph& aOther) {
+ return aOne.mIndex == aOther.mIndex && aOne.mPosition == aOther.mPosition;
+}
+
+/** This class functions as a glyph buffer that can be drawn to a DrawTarget.
+ * @todo XXX - This should probably contain the guts of gfxTextRun in the future
+ * as roc suggested. But for now it's a simple container for a glyph vector.
+ */
+struct GlyphBuffer {
+ const Glyph*
+ mGlyphs; //!< A pointer to a buffer of glyphs. Managed by the caller.
+ uint32_t mNumGlyphs; //!< Number of glyphs mGlyphs points to.
+};
+
+#ifdef MOZ_ENABLE_FREETYPE
+class SharedFTFace;
+
+/** SharedFTFaceData abstracts data that may be used to back a SharedFTFace.
+ * Its main function is to manage the lifetime of the data and ensure that it
+ * lasts as long as the face.
+ */
+class SharedFTFaceData {
+ public:
+ /** Utility for creating a new face from this data. */
+ virtual already_AddRefed<SharedFTFace> CloneFace(int aFaceIndex = 0) {
+ return nullptr;
+ }
+ /** Binds the data's lifetime to the face. */
+ virtual void BindData() = 0;
+ /** Signals that the data is no longer needed by a face. */
+ virtual void ReleaseData() = 0;
+};
+
+/** Wrapper class for ref-counted SharedFTFaceData that handles calling the
+ * appropriate ref-counting methods
+ */
+template <class T>
+class SharedFTFaceRefCountedData : public SharedFTFaceData {
+ public:
+ void BindData() { static_cast<T*>(this)->AddRef(); }
+ void ReleaseData() { static_cast<T*>(this)->Release(); }
+};
+
+// Helper class used for clearing out user font data when FT font
+// face is destroyed. Since multiple faces may use the same data, be
+// careful to assure that the data is only cleared out when all uses
+// expire. The font entry object contains a refptr to FTUserFontData and
+// each FT face created from that font entry contains a refptr to that
+// same FTUserFontData object.
+// This is also attached to FT faces for installed fonts (recording the
+// filename, rather than storing the font data) if variations are present.
+class FTUserFontData final
+ : public mozilla::gfx::SharedFTFaceRefCountedData<FTUserFontData> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FTUserFontData)
+
+ FTUserFontData(const uint8_t* aData, uint32_t aLength)
+ : mFontData(aData), mLength(aLength) {}
+ explicit FTUserFontData(const char* aFilename) : mFilename(aFilename) {}
+
+ const uint8_t* FontData() const { return mFontData; }
+
+ already_AddRefed<mozilla::gfx::SharedFTFace> CloneFace(
+ int aFaceIndex = 0) override;
+
+ private:
+ ~FTUserFontData() {
+ if (mFontData) {
+ free((void*)mFontData);
+ }
+ }
+
+ std::string mFilename;
+ const uint8_t* mFontData = nullptr;
+ uint32_t mLength = 0;
+};
+
+/** SharedFTFace is a shared wrapper around an FT_Face. It is ref-counted,
+ * unlike FT_Face itself, so that it may be shared among many users with
+ * RefPtr. Users should take care to lock SharedFTFace before accessing any
+ * FT_Face fields that may change to ensure exclusive access to it. It also
+ * allows backing data's lifetime to be bound to it via SharedFTFaceData so
+ * that the data will not disappear before the face does.
+ */
+class SharedFTFace : public external::AtomicRefCounted<SharedFTFace> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SharedFTFace)
+
+ explicit SharedFTFace(FT_Face aFace, SharedFTFaceData* aData);
+ virtual ~SharedFTFace();
+
+ FT_Face GetFace() const { return mFace; }
+ SharedFTFaceData* GetData() const { return mData; }
+
+ /** Locks the face for exclusive access by a given owner. Returns false if
+ * the given owner is acquiring the lock for the first time, and true if
+ * the owner was the prior owner of the lock. Thus the return value can be
+ * used to do owner-specific initialization of the FT face such as setting
+ * a size or transform that may have been invalidated by a previous owner.
+ * If no owner is given, then the user should avoid modifying any state on
+ * the face so as not to invalidate the prior owner's modification.
+ */
+ bool Lock(const void* aOwner = nullptr) MOZ_CAPABILITY_ACQUIRE(mLock) {
+ mLock.Lock();
+ return !aOwner || mLastLockOwner.exchange(aOwner) == aOwner;
+ }
+ void Unlock() MOZ_CAPABILITY_RELEASE(mLock) { mLock.Unlock(); }
+
+ /** Should be called when a lock owner is destroyed so that we don't have
+ * a dangling pointer to a destroyed owner.
+ */
+ void ForgetLockOwner(const void* aOwner) {
+ if (aOwner) {
+ mLastLockOwner.compareExchange(aOwner, nullptr);
+ }
+ }
+
+ private:
+ FT_Face mFace;
+ SharedFTFaceData* mData;
+ Mutex mLock;
+ // Remember the last owner of the lock, even after unlocking, to allow users
+ // to avoid reinitializing state on the FT face if the last owner hasn't
+ // changed by the next time it is locked with the same owner.
+ Atomic<const void*> mLastLockOwner;
+};
+#endif
+
+class UnscaledFont : public SupportsThreadSafeWeakPtr<UnscaledFont> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFont)
+
+ virtual ~UnscaledFont();
+
+ virtual FontType GetType() const = 0;
+
+ static uint32_t DeletionCounter() { return sDeletionCounter; }
+
+ typedef void (*FontFileDataOutput)(const uint8_t* aData, uint32_t aLength,
+ uint32_t aIndex, void* aBaton);
+ typedef void (*FontInstanceDataOutput)(const uint8_t* aData, uint32_t aLength,
+ void* aBaton);
+ typedef void (*FontDescriptorOutput)(const uint8_t* aData, uint32_t aLength,
+ uint32_t aIndex, void* aBaton);
+
+ virtual bool GetFontFileData(FontFileDataOutput, void*) { return false; }
+
+ virtual bool GetFontInstanceData(FontInstanceDataOutput, void*) {
+ return false;
+ }
+
+ virtual bool GetFontDescriptor(FontDescriptorOutput, void*) { return false; }
+
+ virtual already_AddRefed<ScaledFont> CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) {
+ return nullptr;
+ }
+
+ virtual already_AddRefed<ScaledFont> CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ return CreateScaledFont(aGlyphSize, nullptr, 0, aVariations,
+ aNumVariations);
+ }
+
+ protected:
+ UnscaledFont() = default;
+
+ private:
+ static Atomic<uint32_t> sDeletionCounter;
+};
+
+/** This class is an abstraction of a backend/platform specific font object
+ * at a particular size. It is passed into text drawing calls to describe
+ * the font used for the drawing call.
+ */
+class ScaledFont : public SupportsThreadSafeWeakPtr<ScaledFont> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFont)
+
+ virtual ~ScaledFont();
+
+ virtual FontType GetType() const = 0;
+ virtual Float GetSize() const = 0;
+ virtual AntialiasMode GetDefaultAAMode() { return AntialiasMode::DEFAULT; }
+
+ static uint32_t DeletionCounter() { return sDeletionCounter; }
+
+ /** This allows getting a path that describes the outline of a set of glyphs.
+ * A target is passed in so that the guarantee is made the returned path
+ * can be used with any DrawTarget that has the same backend as the one
+ * passed in.
+ */
+ virtual already_AddRefed<Path> GetPathForGlyphs(
+ const GlyphBuffer& aBuffer, const DrawTarget* aTarget) = 0;
+
+ /** This copies the path describing the glyphs into a PathBuilder. We use this
+ * API rather than a generic API to append paths because it allows easier
+ * implementation in some backends, and more efficient implementation in
+ * others.
+ */
+ virtual void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
+ PathBuilder* aBuilder,
+ const Matrix* aTransformHint = nullptr) = 0;
+
+ typedef void (*FontInstanceDataOutput)(const uint8_t* aData, uint32_t aLength,
+ const FontVariation* aVariations,
+ uint32_t aNumVariations, void* aBaton);
+
+ virtual bool GetFontInstanceData(FontInstanceDataOutput, void*) {
+ return false;
+ }
+
+ virtual bool GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) {
+ return false;
+ }
+
+ virtual bool CanSerialize() { return false; }
+
+ virtual bool HasVariationSettings() { return false; }
+
+ virtual bool MayUseBitmaps() { return false; }
+
+ virtual bool UseSubpixelPosition() const { return false; }
+
+ void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) {
+ mUserData.Add(key, userData, destroy);
+ }
+ void* GetUserData(UserDataKey* key) { return mUserData.Get(key); }
+
+ void RemoveUserData(UserDataKey* key) { mUserData.RemoveAndDestroy(key); }
+
+ const RefPtr<UnscaledFont>& GetUnscaledFont() const { return mUnscaledFont; }
+
+ virtual cairo_scaled_font_t* GetCairoScaledFont() { return nullptr; }
+
+ Float GetSyntheticObliqueAngle() const { return mSyntheticObliqueAngle; }
+ void SetSyntheticObliqueAngle(Float aAngle) {
+ mSyntheticObliqueAngle = aAngle;
+ }
+
+ protected:
+ explicit ScaledFont(const RefPtr<UnscaledFont>& aUnscaledFont)
+ : mUnscaledFont(aUnscaledFont), mSyntheticObliqueAngle(0.0f) {}
+
+ ThreadSafeUserData mUserData;
+ RefPtr<UnscaledFont> mUnscaledFont;
+ Float mSyntheticObliqueAngle;
+
+ private:
+ static Atomic<uint32_t> sDeletionCounter;
+};
+
+/**
+ * Derived classes hold a native font resource from which to create
+ * ScaledFonts.
+ */
+class NativeFontResource
+ : public external::AtomicRefCounted<NativeFontResource> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResource)
+
+ /**
+ * Creates a UnscaledFont using the font corresponding to the index.
+ *
+ * @param aIndex index for the font within the resource.
+ * @param aInstanceData pointer to read-only buffer of any available instance
+ * data.
+ * @param aInstanceDataLength the size of the instance data.
+ * @return an already_addrefed UnscaledFont, containing nullptr if failed.
+ */
+ virtual already_AddRefed<UnscaledFont> CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) = 0;
+
+ NativeFontResource(size_t aDataLength);
+ virtual ~NativeFontResource();
+
+ static void RegisterMemoryReporter();
+
+ private:
+ size_t mDataLength;
+};
+
+/** This is the main class used for all the drawing. It is created through the
+ * factory and accepts drawing commands. The results of drawing to a target
+ * may be used either through a Snapshot or by flushing the target and directly
+ * accessing the backing store a DrawTarget was created with.
+ */
+class DrawTarget : public external::AtomicRefCounted<DrawTarget> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTarget)
+ DrawTarget()
+ : mTransformDirty(false),
+ mPermitSubpixelAA(false),
+ mFormat(SurfaceFormat::UNKNOWN) {}
+ virtual ~DrawTarget() = default;
+
+ virtual bool IsValid() const { return true; };
+ virtual DrawTargetType GetType() const = 0;
+
+ virtual BackendType GetBackendType() const = 0;
+
+ virtual bool IsRecording() const { return false; }
+
+ /**
+ * Method to generate hyperlink in PDF output (with appropriate backend).
+ */
+ virtual void Link(const char* aDestination, const Rect& aRect) {}
+ virtual void Destination(const char* aDestination, const Point& aPoint) {}
+
+ /**
+ * Returns a SourceSurface which is a snapshot of the current contents of the
+ * DrawTarget. Multiple calls to Snapshot() without any drawing operations in
+ * between will normally return the same SourceSurface object.
+ */
+ virtual already_AddRefed<SourceSurface> Snapshot() = 0;
+
+ /**
+ * Returns a SourceSurface which wraps the buffer backing the DrawTarget. The
+ * contents of the buffer may change if there are drawing operations after
+ * calling but only guarantees that it reflects the state at the time it was
+ * called.
+ */
+ virtual already_AddRefed<SourceSurface> GetBackingSurface() {
+ return Snapshot();
+ }
+
+ // Snapshots the contents and returns an alpha mask
+ // based on the RGB values.
+ virtual already_AddRefed<SourceSurface> IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity);
+ virtual IntSize GetSize() const = 0;
+ virtual IntRect GetRect() const { return IntRect(IntPoint(0, 0), GetSize()); }
+
+ /**
+ * If possible returns the bits to this DrawTarget for direct manipulation.
+ * While the bits is locked any modifications to this DrawTarget is forbidden.
+ * Release takes the original data pointer for safety.
+ */
+ virtual bool LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride,
+ SurfaceFormat* aFormat, IntPoint* aOrigin = nullptr) {
+ return false;
+ }
+ virtual void ReleaseBits(uint8_t* aData) {}
+
+ /** 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() = 0;
+
+ /**
+ * Draw a surface to the draw target. Possibly doing partial drawing or
+ * applying scaling. No sampling happens outside the source.
+ *
+ * @param aSurface Source surface to draw
+ * @param aDest Destination rectangle that this drawing operation should draw
+ * to
+ * @param aSource Source rectangle in aSurface coordinates, this area of
+ * aSurface
+ * will be stretched to the size of aDest.
+ * @param aOptions General draw options that are applied to the operation
+ * @param 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()) = 0;
+
+ /**
+ * Draw a surface to the draw target, when the surface will be available
+ * at a later time. This is only valid for recording DrawTargets.
+ *
+ * This is considered fallible, and replaying this without making the surface
+ * available to the replay will just skip the draw.
+ */
+ virtual void DrawDependentSurface(uint64_t aId, const Rect& aDest) {
+ MOZ_CRASH("GFX: DrawDependentSurface");
+ }
+
+ /**
+ * Draw the output of a FilterNode to the DrawTarget.
+ *
+ * @param aNode FilterNode to draw
+ * @param aSourceRect Source rectangle in FilterNode space to draw
+ * @param aDestPoint Destination point on the DrawTarget to draw the
+ * SourceRectangle of the filter output to
+ */
+ virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * 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!
+ *
+ * @param aSurface Source surface to draw.
+ * @param aDest Destination point that this drawing operation should draw to.
+ * @param aShadow Description of shadow to be drawn.
+ * @param aOperator Composition operator used
+ */
+ virtual void DrawSurfaceWithShadow(SourceSurface* aSurface,
+ const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOperator) = 0;
+
+ /**
+ * Draws a shadow for the specified path, which may be optionally stroked.
+ *
+ * @param aPath The path to use for the shadow geometry.
+ * @param aPattern The pattern to use for filling the path.
+ * @param aShadow Description of shadow to be drawn.
+ * @param aOptions General drawing options to apply to drawing the path.
+ * @param aStrokeOptions Stroking parameters that control stroking of path
+ * geometry, if supplied.
+ */
+ virtual void DrawShadow(const Path* aPath, const Pattern& aPattern,
+ const ShadowOptions& aShadow,
+ const DrawOptions& aOptions = DrawOptions(),
+ const StrokeOptions* aStrokeOptions = nullptr);
+
+ /**
+ * Clear a rectangle on the draw target to transparent black. This will
+ * respect the clipping region and transform.
+ *
+ * @param aRect Rectangle to clear
+ */
+ virtual void ClearRect(const Rect& aRect) = 0;
+
+ /**
+ * 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.
+ *
+ * @param aSurface Surface to copy from
+ * @param aSourceRect Source rectangle to be copied
+ * @param aDest Destination point to copy the surface to
+ */
+ virtual void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect,
+ const IntPoint& aDestination) = 0;
+
+ /** @see CopySurface
+ * Same as CopySurface, except uses itself as the source.
+ *
+ * Some backends may be able to optimize this better
+ * than just taking a snapshot and using CopySurface.
+ */
+ virtual void CopyRect(const IntRect& aSourceRect,
+ const IntPoint& aDestination) {
+ RefPtr<SourceSurface> source = Snapshot();
+ CopySurface(source, aSourceRect, aDestination);
+ }
+
+ /**
+ * Fill a rectangle on the DrawTarget with a certain source pattern.
+ *
+ * @param aRect Rectangle that forms the mask of this filling operation
+ * @param aPattern Pattern that forms the source of this filling operation
+ * @param aOptions Options that are applied to this operation
+ */
+ virtual void FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * Fill a rounded rectangle on the DrawTarget with a certain source pattern.
+ *
+ * @param aRect Rounded rectangle that forms the mask of this filling
+ * operation
+ * @param aPattern Pattern that forms the source of this filling operation
+ * @param aOptions Options that are applied to this operation
+ */
+ virtual void FillRoundedRect(const RoundedRect& aRect,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions());
+
+ /**
+ * Stroke a rectangle on the DrawTarget with a certain source pattern.
+ *
+ * @param aRect Rectangle that forms the mask of this stroking operation
+ * @param aPattern Pattern that forms the source of this stroking operation
+ * @param 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()) = 0;
+
+ /**
+ * Stroke a line on the DrawTarget with a certain source pattern.
+ *
+ * @param aStart Starting point of the line
+ * @param aEnd End point of the line
+ * @param aPattern Pattern that forms the source of this stroking operation
+ * @param 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()) = 0;
+
+ /**
+ * Stroke a path on the draw target with a certain source pattern.
+ *
+ * @param aPath Path that is to be stroked
+ * @param aPattern Pattern that should be used for the stroke
+ * @param aStrokeOptions Stroke options used for this operation
+ * @param aOptions Draw options used for this operation
+ */
+ virtual void Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions = StrokeOptions(),
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * Fill a path on the draw target with a certain source pattern.
+ *
+ * @param aPath Path that is to be filled
+ * @param aPattern Pattern that should be used for the fill
+ * @param aOptions Draw options used for this operation
+ */
+ virtual void Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * Fill a series of glyphs on the draw target with a certain source pattern.
+ */
+ virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * Stroke a series of glyphs on the draw target with a certain source pattern.
+ */
+ virtual void StrokeGlyphs(
+ ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions = StrokeOptions(),
+ const DrawOptions& aOptions = DrawOptions());
+
+ /**
+ * 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.
+ *
+ * @param aSource Source pattern
+ * @param aMask Mask pattern
+ * @param aOptions Drawing options
+ */
+ virtual void Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * This takes a source pattern and a mask, and composites the source pattern
+ * onto the destination surface using the alpha channel of the mask source.
+ * The operation is bound by the extents of the mask.
+ *
+ * @param aSource Source pattern
+ * @param aMask Mask surface
+ * @param aOffset a transformed offset that the surface is masked at
+ * @param aOptions Drawing options
+ */
+ virtual void MaskSurface(const Pattern& aSource, SourceSurface* aMask,
+ Point aOffset,
+ const DrawOptions& aOptions = DrawOptions()) = 0;
+
+ /**
+ * Draw aSurface using the 3D transform aMatrix. The DrawTarget's transform
+ * and clip are applied after the 3D transform.
+ *
+ * If the transform fails (i.e. because aMatrix is singular), false is
+ * returned and nothing is drawn.
+ */
+ virtual bool Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix);
+
+ /**
+ * Push a clip to the DrawTarget.
+ *
+ * @param aPath The path to clip to
+ */
+ virtual void PushClip(const Path* aPath) = 0;
+
+ /**
+ * Push an axis-aligned rectangular clip to the DrawTarget. This rectangle
+ * is specified in user space.
+ *
+ * @param aRect The rect to clip to
+ */
+ virtual void PushClipRect(const Rect& aRect) = 0;
+
+ /**
+ * Push a clip region specifed by the union of axis-aligned rectangular
+ * clips to the DrawTarget. These rectangles are specified in device space.
+ * This must be balanced by a corresponding call to PopClip within a layer.
+ *
+ * @param aRects The rects to clip to
+ * @param aCount The number of rectangles
+ */
+ virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount);
+
+ /** Pop a clip from the DrawTarget. A pop without a corresponding push will
+ * be ignored.
+ */
+ virtual void PopClip() = 0;
+
+ /**
+ * 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) {
+ MOZ_CRASH("GFX: PushLayer");
+ }
+
+ /**
+ * 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 PushLayerWithBlend(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds = IntRect(),
+ bool aCopyBackground = false,
+ CompositionOp = CompositionOp::OP_OVER) {
+ MOZ_CRASH("GFX: PushLayerWithBlend");
+ }
+
+ /**
+ * 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() { MOZ_CRASH("GFX: PopLayer"); }
+
+ /**
+ * Perform an in-place blur operation. This is only supported on data draw
+ * targets.
+ */
+ virtual void Blur(const AlphaBoxBlur& aBlur);
+
+ /**
+ * Performs an in-place edge padding operation.
+ * aRegion is specified in device space.
+ */
+ virtual void PadEdges(const IntRegion& aRegion);
+
+ /**
+ * Performs an in-place buffer unrotation operation.
+ */
+ virtual bool Unrotate(IntPoint aRotation);
+
+ /**
+ * 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 = 0;
+
+ /**
+ * Create a SourceSurface optimized for use with this DrawTarget from an
+ * arbitrary SourceSurface type supported by this backend. This may return
+ * aSourceSurface or some other existing surface.
+ */
+ virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(
+ SourceSurface* aSurface) const = 0;
+ virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(
+ SourceSurface* aSurface) const {
+ return OptimizeSourceSurface(aSurface);
+ }
+
+ /**
+ * 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. If this succeeds, the SourceSurface takes the ownersip of the
+ * NativeSurface.
+ */
+ virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const = 0;
+
+ /**
+ * Create a DrawTarget whose snapshot is optimized for use with this
+ * DrawTarget.
+ */
+ virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const = 0;
+
+ /**
+ * Create a DrawTarget whose backing surface is optimized for use with this
+ * DrawTarget.
+ */
+ virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetWithBacking(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ return CreateSimilarDrawTarget(aSize, aFormat);
+ }
+
+ /**
+ * Create a DrawTarget whose snapshot is optimized for use with this
+ * DrawTarget and aFilter.
+ * @param aSource is the FilterNode that that will be attached to this
+ * surface.
+ * @param aSourceRect is the source rect that will be passed to DrawFilter
+ * @param aDestPoint is the dest point that will be passed to DrawFilter.
+ */
+ virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetForFilter(
+ const IntSize& aSize, SurfaceFormat aFormat, FilterNode* aFilter,
+ FilterNode* aSource, const Rect& aSourceRect, const Point& aDestPoint) {
+ return CreateSimilarDrawTarget(aSize, aFormat);
+ }
+
+ /**
+ * Returns false if CreateSimilarDrawTarget would return null with the same
+ * parameters. May return true even in cases where CreateSimilarDrawTarget
+ * return null (i.e. this function returning false has meaning, but returning
+ * true doesn't guarantee anything).
+ */
+ virtual bool CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const {
+ return true;
+ }
+
+ /**
+ * Create a draw target optimized for drawing a shadow.
+ *
+ * Note that aSigma is the blur radius that must be used when we draw the
+ * shadow. Also note that this doesn't affect the size of the allocated
+ * surface, the caller is still responsible for including the shadow area in
+ * its size.
+ */
+ virtual already_AddRefed<DrawTarget> CreateShadowDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const {
+ return CreateSimilarDrawTarget(aSize, aFormat);
+ }
+
+ /**
+ * Create a similar DrawTarget in the same space as this DrawTarget whose
+ * device size may be clipped based on the active clips intersected with
+ * aBounds (if it is not empty).
+ * aRect is a rectangle in user space.
+ */
+ virtual RefPtr<DrawTarget> CreateClippedDrawTarget(const Rect& aBounds,
+ SurfaceFormat aFormat) = 0;
+
+ /**
+ * Create a similar draw target, but if the draw target is not backed by a
+ * raster backend (for example, it is capturing or recording), force it to
+ * create a raster target instead. This is intended for code that wants to
+ * cache pixels, and would have no effect if it were caching a recording.
+ */
+ virtual RefPtr<DrawTarget> CreateSimilarRasterTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ return CreateSimilarDrawTarget(aSize, aFormat);
+ }
+
+ /**
+ * 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 = 0;
+
+ /**
+ * 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.
+ *
+ * @param aStops An array of gradient stops
+ * @param aNumStops Number of stops in the array aStops
+ * @param 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 = 0;
+
+ /**
+ * Create a FilterNode object that can be used to apply a filter to various
+ * inputs.
+ *
+ * @param aType Type of filter node to be created.
+ */
+ virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) = 0;
+
+ Matrix GetTransform() const { return mTransform; }
+
+ /**
+ * Set a transform on the surface, this transform is applied at drawing time
+ * to both the mask and source of the operation.
+ *
+ * Performance note: For some backends it is expensive to change the current
+ * transform (because transforms affect a lot of the parts of the pipeline,
+ * so new transform change can result in a pipeline flush). To get around
+ * this, DrawTarget implementations buffer transform changes and try to only
+ * set the current transform on the backend when required. That tracking has
+ * its own performance impact though, and ideally callers would be smart
+ * enough not to require it. At a future date this method may stop this
+ * doing transform buffering so, if you're a consumer, please try to be smart
+ * about calling this method as little as possible. For example, instead of
+ * concatenating a translation onto the current transform then calling
+ * FillRect, try to integrate the translation into FillRect's aRect
+ * argument's x/y offset.
+ */
+ virtual void SetTransform(const Matrix& aTransform) {
+ mTransform = aTransform;
+ mTransformDirty = true;
+ }
+
+ inline void ConcatTransform(const Matrix& aTransform) {
+ SetTransform(aTransform * Matrix(GetTransform()));
+ }
+
+ SurfaceFormat GetFormat() const { return mFormat; }
+
+ /** 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) { return nullptr; }
+
+ virtual bool IsTiledDrawTarget() const { return false; }
+ virtual bool SupportsRegionClipping() const { return true; }
+
+ void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) {
+ mUserData.Add(key, userData, destroy);
+ }
+ void* GetUserData(UserDataKey* key) const { return mUserData.Get(key); }
+ void* RemoveUserData(UserDataKey* key) { return mUserData.Remove(key); }
+
+ /** Within this rectangle all pixels will be opaque by the time the result of
+ * this DrawTarget is first used for drawing. Either by the underlying surface
+ * being used as an input to external drawing, or Snapshot() being called.
+ * This rectangle is specified in device space.
+ */
+ void SetOpaqueRect(const IntRect& aRect) { mOpaqueRect = aRect; }
+
+ const IntRect& GetOpaqueRect() const { return mOpaqueRect; }
+
+ virtual bool IsCurrentGroupOpaque() {
+ return GetFormat() == SurfaceFormat::B8G8R8X8;
+ }
+
+ virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) {
+ mPermitSubpixelAA = aPermitSubpixelAA;
+ }
+
+ bool GetPermitSubpixelAA() { return mPermitSubpixelAA; }
+
+ /**
+ * Mark the end of an Item in a DrawTargetRecording. These markers
+ * are used for merging recordings together.
+ *
+ * This should only be called on the 'root' DrawTargetRecording.
+ * Calling it on a child DrawTargetRecordings will cause confusion.
+ *
+ * Note: this is a bit of a hack. It might be better to just recreate
+ * the DrawTargetRecording.
+ */
+ virtual void FlushItem(const IntRect& aBounds) {}
+
+ /**
+ * Ensures that no snapshot is still pointing to this DrawTarget's surface
+ * data.
+ *
+ * This can be useful if the DrawTarget is wrapped around data that it does
+ * not own, and for some reason the owner of the data has to make it
+ * temporarily unavailable without the DrawTarget knowing about it. This can
+ * cause costly surface copies, so it should not be used without a a good
+ * reason.
+ */
+ virtual void DetachAllSnapshots() = 0;
+
+ /**
+ * Remove all clips in the DrawTarget.
+ */
+ virtual bool RemoveAllClips() { return false; }
+
+ protected:
+ UserData mUserData;
+ Matrix mTransform;
+ IntRect mOpaqueRect;
+ bool mTransformDirty : 1;
+ bool mPermitSubpixelAA : 1;
+
+ SurfaceFormat mFormat;
+};
+
+class DrawEventRecorder : public RefCounted<DrawEventRecorder> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorder)
+ // returns true if there were any items in the recording
+ virtual bool Finish() = 0;
+ virtual ~DrawEventRecorder() = default;
+};
+
+struct Tile {
+ RefPtr<DrawTarget> mDrawTarget;
+ IntPoint mTileOrigin;
+};
+
+struct TileSet {
+ Tile* mTiles;
+ size_t mTileCount;
+};
+
+struct Config {
+ LogForwarder* mLogForwarder;
+ int32_t mMaxTextureSize;
+ int32_t mMaxAllocSize;
+
+ Config()
+ : mLogForwarder(nullptr),
+ mMaxTextureSize(kReasonableSurfaceSize),
+ mMaxAllocSize(52000000) {}
+};
+
+class GFX2D_API Factory {
+ using char_type = filesystem::Path::value_type;
+
+ public:
+ static void Init(const Config& aConfig);
+ static void ShutDown();
+
+ static bool HasSSE2();
+ static bool HasSSE4();
+
+ /**
+ * Returns false if any of the following are true:
+ *
+ * - the width/height of |sz| are less than or equal to zero
+ * - the width/height of |sz| are greater than |limit|
+ * - the number of bytes that need to be allocated for the surface is too
+ * big to fit in an int32_t, or bigger than |allocLimit|, if specifed
+ *
+ * To calculate the number of bytes that need to be allocated for the surface
+ * this function makes the conservative assumption that there need to be
+ * 4 bytes-per-pixel, and the stride alignment is 16 bytes.
+ *
+ * The reason for using int32_t rather than uint32_t is again to be
+ * conservative; some code has in the past and may in the future use signed
+ * integers to store buffer lengths etc.
+ */
+ static bool CheckSurfaceSize(const IntSize& sz, int32_t limit = 0,
+ int32_t allocLimit = 0);
+
+ /** Make sure the given dimension satisfies the CheckSurfaceSize and is
+ * within 8k limit. The 8k value is chosen a bit randomly.
+ */
+ static bool ReasonableSurfaceSize(const IntSize& aSize);
+
+ static bool AllowedSurfaceSize(const IntSize& aSize);
+
+ static already_AddRefed<DrawTarget> CreateDrawTargetForCairoSurface(
+ cairo_surface_t* aSurface, const IntSize& aSize,
+ SurfaceFormat* aFormat = nullptr);
+
+ static already_AddRefed<SourceSurface> CreateSourceSurfaceForCairoSurface(
+ cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat aFormat);
+
+ static already_AddRefed<DrawTarget> CreateDrawTarget(BackendType aBackend,
+ const IntSize& aSize,
+ SurfaceFormat aFormat);
+
+ static already_AddRefed<PathBuilder> CreatePathBuilder(
+ BackendType aBackend, FillRule aFillRule = FillRule::FILL_WINDING);
+
+ /**
+ * Create a simple PathBuilder, which uses SKIA backend.
+ */
+ static already_AddRefed<PathBuilder> CreateSimplePathBuilder();
+
+ static already_AddRefed<DrawTarget> CreateRecordingDrawTarget(
+ DrawEventRecorder* aRecorder, DrawTarget* aDT, IntRect aRect);
+
+ static already_AddRefed<DrawTarget> CreateDrawTargetForData(
+ BackendType aBackend, unsigned char* aData, const IntSize& aSize,
+ int32_t aStride, SurfaceFormat aFormat, bool aUninitialized = false);
+
+#ifdef XP_DARWIN
+ static already_AddRefed<ScaledFont> CreateScaledFontForMacFont(
+ CGFontRef aCGFont, const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ const DeviceColor& aFontSmoothingBackgroundColor,
+ bool aUseFontSmoothing = true, bool aApplySyntheticBold = false,
+ bool aHasColorGlyphs = false);
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+ static already_AddRefed<ScaledFont> CreateScaledFontForFontconfigFont(
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ RefPtr<SharedFTFace> aFace, FcPattern* aPattern);
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+ static already_AddRefed<ScaledFont> CreateScaledFontForFreeTypeFont(
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ RefPtr<SharedFTFace> aFace, bool aApplySyntheticBold = false);
+#endif
+
+ /**
+ * This creates a NativeFontResource from TrueType data.
+ *
+ * @param aData Pointer to the data
+ * @param aSize Size of the TrueType data
+ * @param aFontType Type of NativeFontResource that should be created.
+ * @param aFontContext Optional native font context to be used to create the
+ * NativeFontResource.
+ * @return a NativeFontResource of nullptr if failed.
+ */
+ static already_AddRefed<NativeFontResource> CreateNativeFontResource(
+ uint8_t* aData, uint32_t aSize, FontType aFontType,
+ void* aFontContext = nullptr);
+
+ /**
+ * This creates an unscaled font of the given type based on font descriptor
+ * data retrieved from ScaledFont::GetFontDescriptor.
+ */
+ static already_AddRefed<UnscaledFont> CreateUnscaledFontFromFontDescriptor(
+ FontType aType, const uint8_t* aData, uint32_t aDataLength,
+ uint32_t aIndex);
+
+ /**
+ * This creates a simple data source surface for a certain size. It allocates
+ * new memory for the surface. This memory is freed when the surface is
+ * destroyed. The caller is responsible for handing the case where nullptr
+ * is returned. The surface is not zeroed unless requested.
+ */
+ static already_AddRefed<DataSourceSurface> CreateDataSourceSurface(
+ const IntSize& aSize, SurfaceFormat aFormat, bool aZero = false);
+
+ /**
+ * This creates a simple data source surface for a certain size with a
+ * specific stride, which must be large enough to fit all pixels.
+ * It allocates new memory for the surface. This memory is freed when
+ * the surface is destroyed. The caller is responsible for handling the case
+ * where nullptr is returned. The surface is not zeroed unless requested.
+ */
+ static already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceWithStride(
+ const IntSize& aSize, SurfaceFormat aFormat, int32_t aStride,
+ bool aZero = false);
+
+ typedef void (*SourceSurfaceDeallocator)(void* aClosure);
+
+ /**
+ * This creates a simple data source surface for some existing data. It will
+ * wrap this data and the data for this source surface.
+ *
+ * We can provide a custom destroying function for |aData|. This will be
+ * called in the surface dtor using |aDeallocator| and the |aClosure|. If
+ * there are errors during construction(return a nullptr surface), the caller
+ * is responsible for the deallocation.
+ *
+ * If there is no destroying function, the caller is responsible for
+ * deallocating the aData memory only after destruction of this
+ * DataSourceSurface.
+ */
+ static already_AddRefed<DataSourceSurface> CreateWrappingDataSourceSurface(
+ uint8_t* aData, int32_t aStride, const IntSize& aSize,
+ SurfaceFormat aFormat, SourceSurfaceDeallocator aDeallocator = nullptr,
+ void* aClosure = nullptr);
+
+ static void CopyDataSourceSurface(DataSourceSurface* aSource,
+ DataSourceSurface* aDest);
+
+ static uint32_t GetMaxSurfaceSize(BackendType aType);
+
+ static LogForwarder* GetLogForwarder() {
+ return sConfig ? sConfig->mLogForwarder : nullptr;
+ }
+
+ private:
+ static Config* sConfig;
+
+ public:
+ static void PurgeAllCaches();
+
+ static already_AddRefed<DrawTarget> CreateOffsetDrawTarget(
+ DrawTarget* aDrawTarget, IntPoint aTileOrigin);
+
+ static bool DoesBackendSupportDataDrawtarget(BackendType aType);
+
+ static void SetBGRSubpixelOrder(bool aBGR);
+ static bool GetBGRSubpixelOrder();
+
+ private:
+ static bool mBGRSubpixelOrder;
+
+ public:
+ static already_AddRefed<DrawTarget> CreateDrawTargetWithSkCanvas(
+ SkCanvas* aCanvas);
+
+#ifdef MOZ_ENABLE_FREETYPE
+ static void SetFTLibrary(FT_Library aFTLibrary);
+ static FT_Library GetFTLibrary();
+
+ static FT_Library NewFTLibrary();
+ static void ReleaseFTLibrary(FT_Library aFTLibrary);
+ static void LockFTLibrary(FT_Library aFTLibrary);
+ static void UnlockFTLibrary(FT_Library aFTLibrary);
+
+ static FT_Face NewFTFace(FT_Library aFTLibrary, const char* aFileName,
+ int aFaceIndex);
+ static already_AddRefed<SharedFTFace> NewSharedFTFace(FT_Library aFTLibrary,
+ const char* aFilename,
+ int aFaceIndex);
+ static FT_Face NewFTFaceFromData(FT_Library aFTLibrary, const uint8_t* aData,
+ size_t aDataSize, int aFaceIndex);
+ static already_AddRefed<SharedFTFace> NewSharedFTFaceFromData(
+ FT_Library aFTLibrary, const uint8_t* aData, size_t aDataSize,
+ int aFaceIndex, SharedFTFaceData* aSharedData = nullptr);
+ static void ReleaseFTFace(FT_Face aFace);
+ static FT_Error LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex,
+ int32_t aFlags);
+
+ private:
+ static FT_Library mFTLibrary;
+ static StaticMutex mFTLock;
+
+ public:
+#endif
+
+#ifdef WIN32
+ static already_AddRefed<DrawTarget> CreateDrawTargetForD3D11Texture(
+ ID3D11Texture2D* aTexture, SurfaceFormat aFormat);
+
+ /*
+ * Attempts to create and install a D2D1 device from the supplied Direct3D11
+ * device. Returns true on success, or false on failure and leaves the
+ * D2D1/Direct3D11 devices unset.
+ */
+ static bool SetDirect3D11Device(ID3D11Device* aDevice);
+ static RefPtr<ID3D11Device> GetDirect3D11Device();
+ static RefPtr<ID2D1Device> GetD2D1Device(uint32_t* aOutSeqNo = nullptr);
+ static bool HasD2D1Device();
+ static RefPtr<IDWriteFactory> GetDWriteFactory();
+ static RefPtr<IDWriteFactory> EnsureDWriteFactory();
+ static bool SupportsD2D1();
+ static RefPtr<IDWriteFontCollection> GetDWriteSystemFonts(
+ bool aUpdate = false);
+ static RefPtr<ID2D1DeviceContext> GetD2DDeviceContext();
+
+ static uint64_t GetD2DVRAMUsageDrawTarget();
+ static uint64_t GetD2DVRAMUsageSourceSurface();
+ static void D2DCleanup();
+
+ static already_AddRefed<ScaledFont> CreateScaledFontForDWriteFont(
+ IDWriteFontFace* aFontFace, const gfxFontStyle* aStyle,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ bool aUseEmbeddedBitmap, bool aUseMultistrikeBold, bool aGDIForced);
+
+ static already_AddRefed<ScaledFont> CreateScaledFontForGDIFont(
+ const void* aLogFont, const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize);
+
+ static void SetSystemTextQuality(uint8_t aQuality);
+
+ static already_AddRefed<DataSourceSurface>
+ CreateBGRA8DataSourceSurfaceForD3D11Texture(ID3D11Texture2D* aSrcTexture,
+ uint32_t aArrayIndex = 0);
+
+ static bool ReadbackTexture(layers::TextureData* aDestCpuTexture,
+ ID3D11Texture2D* aSrcTexture);
+
+ static bool ReadbackTexture(DataSourceSurface* aDestCpuTexture,
+ ID3D11Texture2D* aSrcTexture,
+ uint32_t aArrayIndex = 0);
+
+ private:
+ static StaticRefPtr<ID2D1Device> mD2D1Device;
+ static StaticRefPtr<ID3D11Device> mD3D11Device;
+ static StaticRefPtr<IDWriteFactory> mDWriteFactory;
+ static bool mDWriteFactoryInitialized;
+ static StaticRefPtr<IDWriteFontCollection> mDWriteSystemFonts;
+ static StaticRefPtr<ID2D1DeviceContext> mMTDC;
+ static StaticRefPtr<ID2D1DeviceContext> mOffMTDC;
+
+ static bool ReadbackTexture(uint8_t* aDestData, int32_t aDestStride,
+ ID3D11Texture2D* aSrcTexture);
+
+ // DestTextureT can be TextureData or DataSourceSurface.
+ template <typename DestTextureT>
+ static bool ConvertSourceAndRetryReadback(DestTextureT* aDestCpuTexture,
+ ID3D11Texture2D* aSrcTexture,
+ uint32_t aArrayIndex = 0);
+
+ protected:
+ // This guards access to the singleton devices above, as well as the
+ // singleton devices in DrawTargetD2D1.
+ static StaticMutex mDeviceLock;
+ // This synchronizes access between different D2D drawtargets and their
+ // implied dependency graph.
+ static StaticMutex mDTDependencyLock;
+
+ friend class DrawTargetD2D1;
+#endif // WIN32
+};
+
+class MOZ_RAII AutoSerializeWithMoz2D final {
+ public:
+ explicit AutoSerializeWithMoz2D(BackendType aBackendType);
+ ~AutoSerializeWithMoz2D();
+
+ private:
+#if defined(WIN32)
+ RefPtr<ID2D1Multithread> mMT;
+#endif
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_2D_H
diff --git a/gfx/2d/AutoHelpersWin.h b/gfx/2d/AutoHelpersWin.h
new file mode 100644
index 0000000000..733f8a1e27
--- /dev/null
+++ b/gfx/2d/AutoHelpersWin.h
@@ -0,0 +1,71 @@
+/* -*- 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_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;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_AutoHelpersWin_h
diff --git a/gfx/2d/BaseCoord.h b/gfx/2d/BaseCoord.h
new file mode 100644
index 0000000000..41a82ea047
--- /dev/null
+++ b/gfx/2d/BaseCoord.h
@@ -0,0 +1,101 @@
+/* -*- 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_BASECOORD_H_
+#define MOZILLA_GFX_BASECOORD_H_
+
+#include <ostream>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MathAlgorithms.h"
+
+namespace mozilla::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
+
+ friend constexpr Sub Abs(BaseCoord aCoord) { return Abs(aCoord.value); }
+
+ constexpr operator T() const { return value; }
+
+ friend constexpr bool operator==(Sub aA, Sub aB) {
+ return aA.value == aB.value;
+ }
+ friend constexpr bool operator!=(Sub aA, Sub aB) {
+ return aA.value != aB.value;
+ }
+
+ friend constexpr Sub operator+(Sub aA, Sub aB) {
+ return Sub(aA.value + aB.value);
+ }
+ friend constexpr Sub operator-(Sub aA, Sub aB) {
+ return Sub(aA.value - aB.value);
+ }
+ friend constexpr Sub operator*(Sub aCoord, T aScale) {
+ return Sub(aCoord.value * aScale);
+ }
+ friend constexpr Sub operator*(T aScale, Sub aCoord) {
+ return Sub(aScale * aCoord.value);
+ }
+ friend constexpr Sub operator/(Sub aCoord, T aScale) {
+ return Sub(aCoord.value / aScale);
+ }
+ // 'scale / coord' is intentionally omitted because it doesn't make sense.
+
+ constexpr Sub& operator+=(Sub aCoord) {
+ value += aCoord.value;
+ return *static_cast<Sub*>(this);
+ }
+ constexpr Sub& operator-=(Sub aCoord) {
+ value -= aCoord.value;
+ return *static_cast<Sub*>(this);
+ }
+ constexpr Sub& operator*=(T aScale) {
+ value *= aScale;
+ return *static_cast<Sub*>(this);
+ }
+ constexpr 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 constexpr bool operator==(Sub aA, T aB) { return aA.value == aB; }
+ friend constexpr bool operator==(T aA, Sub aB) { return aA == aB.value; }
+ friend constexpr bool operator!=(Sub aA, T aB) { return aA.value != aB; }
+ friend constexpr bool operator!=(T aA, Sub aB) { return aA != aB.value; }
+ friend constexpr T operator+(Sub aA, T aB) { return aA.value + aB; }
+ friend constexpr T operator+(T aA, Sub aB) { return aA + aB.value; }
+ friend constexpr T operator-(Sub aA, T aB) { return aA.value - aB; }
+ friend constexpr T operator-(T aA, Sub aB) { return aA - aB.value; }
+
+ constexpr Sub operator-() const { return Sub(-value); }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseCoord<T, Sub>& aCoord) {
+ return aStream << aCoord.value;
+ }
+};
+
+} // namespace mozilla::gfx
+
+#endif /* MOZILLA_GFX_BASECOORD_H_ */
diff --git a/gfx/2d/BaseMargin.h b/gfx/2d/BaseMargin.h
new file mode 100644
index 0000000000..a3891a6c81
--- /dev/null
+++ b/gfx/2d/BaseMargin.h
@@ -0,0 +1,164 @@
+/* -*- 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_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(SideBits::eNone) {}
+ explicit Sides(SideBits aSideBits) {
+ MOZ_ASSERT((aSideBits & ~SideBits::eAll) == SideBits::eNone,
+ "illegal side bits");
+ mBits = aSideBits;
+ }
+ bool IsEmpty() const { return mBits == SideBits::eNone; }
+ bool Top() const { return (mBits & SideBits::eTop) == SideBits::eTop; }
+ bool Right() const { return (mBits & SideBits::eRight) == SideBits::eRight; }
+ bool Bottom() const {
+ return (mBits & SideBits::eBottom) == SideBits::eBottom;
+ }
+ bool Left() const { return (mBits & SideBits::eLeft) == SideBits::eLeft; }
+ bool Contains(SideBits aSideBits) const {
+ MOZ_ASSERT(!(aSideBits & ~SideBits::eAll), "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:
+ SideBits 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));
+ }
+
+ Sub& ApplySkipSides(Sides aSkipSides) {
+ if (aSkipSides.Top()) {
+ top = 0;
+ }
+ if (aSkipSides.Right()) {
+ right = 0;
+ }
+ if (aSkipSides.Bottom()) {
+ bottom = 0;
+ }
+ if (aSkipSides.Left()) {
+ left = 0;
+ }
+ return *static_cast<Sub*>(this);
+ }
+
+ // Ensures that all our sides are at least as big as the argument.
+ void EnsureAtLeast(const BaseMargin& aMargin) {
+ top = std::max(top, aMargin.top);
+ right = std::max(right, aMargin.right);
+ bottom = std::max(bottom, aMargin.bottom);
+ left = std::max(left, aMargin.left);
+ }
+
+ // Ensures that all our sides are at most as big as the argument.
+ void EnsureAtMost(const BaseMargin& aMargin) {
+ top = std::min(top, aMargin.top);
+ right = std::min(right, aMargin.right);
+ bottom = std::min(bottom, aMargin.bottom);
+ left = std::min(left, aMargin.left);
+ }
+
+ // 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 { return Sub(-top, -right, -bottom, -left); }
+ Sub& operator+=(const Sub& aMargin) {
+ top += aMargin.top;
+ right += aMargin.right;
+ bottom += aMargin.bottom;
+ left += aMargin.left;
+ return *static_cast<Sub*>(this);
+ }
+ 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 << "(t=" << aMargin.top << ", r=" << aMargin.right
+ << ", b=" << aMargin.bottom << ", l=" << aMargin.left << ')';
+ }
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_BASEMARGIN_H_ */
diff --git a/gfx/2d/BasePoint.h b/gfx/2d/BasePoint.h
new file mode 100644
index 0000000000..9cf9f4d1f6
--- /dev/null
+++ b/gfx/2d/BasePoint.h
@@ -0,0 +1,120 @@
+/* -*- 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_BASEPOINT_H_
+#define MOZILLA_GFX_BASEPOINT_H_
+
+#include <cmath>
+#include <ostream>
+#include <type_traits>
+#include "mozilla/Attributes.h"
+#include "mozilla/FloatingPoint.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 {
+ Coord x, y;
+ };
+ Coord components[2];
+ };
+
+ // Constructors
+ constexpr BasePoint() : x(0), y(0) {}
+ constexpr BasePoint(Coord aX, Coord aY) : x(aX), y(aY) {}
+
+ MOZ_ALWAYS_INLINE Coord X() const { return x; }
+ MOZ_ALWAYS_INLINE Coord Y() const { return y; }
+
+ 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.value * aPoint.x.value + y.value * aPoint.y.value;
+ }
+
+ Coord Length() const { return hypot(x.value, y.value); }
+
+ T LengthSquare() const { return x.value * x.value + y.value * y.value; }
+
+ // 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(std::floor(T(x) + T(0.5f)));
+ y = Coord(std::floor(T(y) + T(0.5f)));
+ return *static_cast<Sub*>(this);
+ }
+
+ // "Finite" means not inf and not NaN
+ bool IsFinite() const {
+ using FloatType =
+ std::conditional_t<std::is_same_v<T, float>, float, double>;
+ return (std::isfinite(FloatType(x)) && std::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_ */
diff --git a/gfx/2d/BasePoint3D.h b/gfx/2d/BasePoint3D.h
new file mode 100644
index 0000000000..36d272c61b
--- /dev/null
+++ b/gfx/2d/BasePoint3D.h
@@ -0,0 +1,142 @@
+/* -*- 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_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(); }
+
+ void RobustNormalize() {
+ // If the distance is infinite, we scale it by 1/(the maximum value of T)
+ // before doing normalization, so we can avoid getting a zero point.
+ T length = Length();
+ if (std::isinf(length)) {
+ *this /= std::numeric_limits<T>::max();
+ length = Length();
+ }
+
+ *this /= length;
+ }
+
+ friend std::ostream& operator<<(std::ostream& stream,
+ const BasePoint3D<T, Sub>& aPoint) {
+ return stream << '(' << aPoint.x << ',' << aPoint.y << ',' << aPoint.z
+ << ')';
+ }
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_BASEPOINT3D_H_ */
diff --git a/gfx/2d/BasePoint4D.h b/gfx/2d/BasePoint4D.h
new file mode 100644
index 0000000000..2dd4cb11d2
--- /dev/null
+++ b/gfx/2d/BasePoint4D.h
@@ -0,0 +1,132 @@
+/* -*- 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_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_ */
diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h
new file mode 100644
index 0000000000..3e3801c936
--- /dev/null
+++ b/gfx/2d/BaseRect.h
@@ -0,0 +1,743 @@
+/* -*- 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_BASERECT_H_
+#define MOZILLA_GFX_BASERECT_H_
+
+#include <algorithm>
+#include <cmath>
+#include <ostream>
+#include <type_traits>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/gfx/ScaleFactors2D.h"
+#include "Types.h"
+
+namespace mozilla::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. Zero rect is the one with height and width set to zero. Note
+ // that SetEmpty() may change a rectangle that identified as IsEmpty().
+ MOZ_ALWAYS_INLINE bool IsZeroArea() const {
+ return height == 0 || width == 0;
+ }
+ MOZ_ALWAYS_INLINE bool IsEmpty() const { return height <= 0 || width <= 0; }
+ void SetEmpty() { width = height = 0; }
+
+ // "Finite" means not inf and not NaN
+ bool IsFinite() const {
+ using FloatType =
+ std::conditional_t<std::is_same_v<T, float>, float, double>;
+ return (std::isfinite(FloatType(x)) && std::isfinite(FloatType(y)) &&
+ std::isfinite(FloatType(width)) &&
+ std::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.
+ MOZ_ALWAYS_INLINE bool Contains(T aX, T aY) const {
+ return x <= aX && aX < XMost() && y <= aY && aY < YMost();
+ }
+ MOZ_ALWAYS_INLINE bool ContainsX(T aX) const {
+ return x <= aX && aX < XMost();
+ }
+ MOZ_ALWAYS_INLINE bool ContainsY(T aY) const {
+ return 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.
+ //
+ // Intersection with an empty Rect may not produce an empty Rect if overflow
+ // occurs. e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives:
+ // the non-emtpy {5000, 0, 500, 20 } instead of {5000, 0, 0, 0}
+ [[nodiscard]] 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);
+ // See bug 1457110, this function expects to -only- size to 0,0 if the
+ // width/height is explicitly negative.
+ if (result.width < 0 || result.height < 0) {
+ result.SizeTo(0, 0);
+ }
+ return result;
+ }
+
+ // Gives the same results as Intersect() but handles integer overflow
+ // better. This comes at a tiny cost in performance.
+ // e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives:
+ // {5000, 0, 0, 0}
+ [[nodiscard]] Sub SafeIntersect(const Sub& aRect) const {
+ Sub result;
+ result.x = std::max<T>(x, aRect.x);
+ result.y = std::max<T>(y, aRect.y);
+ T right = std::min<T>(x + width, aRect.x + aRect.width);
+ T bottom = std::min<T>(y + height, aRect.y + aRect.height);
+ // See bug 1457110, this function expects to -only- size to 0,0 if the
+ // width/height is explicitly negative.
+ if (right < result.x || bottom < result.y) {
+ result.width = 0;
+ result.height = 0;
+ } else {
+ result.width = right - result.x;
+ result.height = bottom - result.y;
+ }
+ 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) {
+ T newX = std::max<T>(aRect1.x, aRect2.x);
+ T newY = std::max<T>(aRect1.y, aRect2.y);
+ width = std::min<T>(aRect1.x - newX + aRect1.width,
+ aRect2.x - newX + aRect2.width);
+ height = std::min<T>(aRect1.y - newY + aRect1.height,
+ aRect2.y - newY + aRect2.height);
+ x = newX;
+ y = newY;
+ if (width <= 0 || height <= 0) {
+ SizeTo(0, 0);
+ return false;
+ }
+ return true;
+ }
+
+ // Returns the smallest rectangle that contains both the area of both
+ // this and aRect. Thus, empty input rectangles are ignored.
+ // Note: if both rectangles are empty, returns aRect.
+ // WARNING! This is not safe against overflow, prefer using SafeUnion instead
+ // when dealing with int-based rects.
+ [[nodiscard]] 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.
+ [[nodiscard]] 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);
+ }
+
+ void OrWith(const Sub& aRect1) {
+ UnionRect(*static_cast<Sub*>(this), aRect1);
+ }
+
+ // 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;
+ }
+ }
+
+ MOZ_ALWAYS_INLINE void SetRect(T aX, T aY, T aWidth, T aHeight) {
+ x = aX;
+ y = aY;
+ width = aWidth;
+ height = aHeight;
+ }
+ MOZ_ALWAYS_INLINE void SetRectX(T aX, T aWidth) {
+ x = aX;
+ width = aWidth;
+ }
+ MOZ_ALWAYS_INLINE void SetRectY(T aY, T aHeight) {
+ y = aY;
+ height = aHeight;
+ }
+ MOZ_ALWAYS_INLINE void SetBox(T aX, T aY, T aXMost, T aYMost) {
+ x = aX;
+ y = aY;
+ width = aXMost - aX;
+ height = aYMost - aY;
+ }
+ MOZ_ALWAYS_INLINE void SetNonEmptyBox(T aX, T aY, T aXMost, T aYMost) {
+ x = aX;
+ y = aY;
+ width = std::max(0, aXMost - aX);
+ height = std::max(0, aYMost - aY);
+ }
+ MOZ_ALWAYS_INLINE void SetBoxX(T aX, T aXMost) {
+ x = aX;
+ width = aXMost - aX;
+ }
+ MOZ_ALWAYS_INLINE void SetBoxY(T aY, T aYMost) {
+ y = aY;
+ height = aYMost - aY;
+ }
+ void SetRect(const Point& aPt, const SizeT& aSize) {
+ SetRect(aPt.x, aPt.y, aSize.width, aSize.height);
+ }
+ MOZ_ALWAYS_INLINE void GetRect(T* aX, T* aY, T* aWidth, T* aHeight) const {
+ *aX = x;
+ *aY = y;
+ *aWidth = width;
+ *aHeight = height;
+ }
+
+ MOZ_ALWAYS_INLINE void MoveTo(T aX, T aY) {
+ x = aX;
+ y = aY;
+ }
+ MOZ_ALWAYS_INLINE void MoveToX(T aX) { x = aX; }
+ MOZ_ALWAYS_INLINE void MoveToY(T aY) { y = aY; }
+ MOZ_ALWAYS_INLINE void MoveTo(const Point& aPoint) {
+ x = aPoint.x;
+ y = aPoint.y;
+ }
+ MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) {
+ x += aDx;
+ y += aDy;
+ }
+ MOZ_ALWAYS_INLINE void MoveByX(T aDx) { x += aDx; }
+ MOZ_ALWAYS_INLINE void MoveByY(T aDy) { y += aDy; }
+ MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) {
+ x += aPoint.x;
+ y += aPoint.y;
+ }
+ MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) {
+ width = aWidth;
+ height = aHeight;
+ }
+ MOZ_ALWAYS_INLINE void SizeTo(const SizeT& aSize) {
+ width = aSize.width;
+ height = aSize.height;
+ }
+
+ // Variant of MoveBy that ensures that even after translation by a point that
+ // the rectangle coordinates will still fit within numeric limits. The origin
+ // and size will be clipped within numeric limits to ensure this.
+ void SafeMoveByX(T aDx) {
+ T x2 = XMost();
+ if (aDx >= T(0)) {
+ T limit = std::numeric_limits<T>::max();
+ x = limit - aDx < x ? limit : x + aDx;
+ width = (limit - aDx < x2 ? limit : x2 + aDx) - x;
+ } else {
+ T limit = std::numeric_limits<T>::min();
+ x = limit - aDx > x ? limit : x + aDx;
+ width = (limit - aDx > x2 ? limit : x2 + aDx) - x;
+ }
+ }
+ void SafeMoveByY(T aDy) {
+ T y2 = YMost();
+ if (aDy >= T(0)) {
+ T limit = std::numeric_limits<T>::max();
+ y = limit - aDy < y ? limit : y + aDy;
+ height = (limit - aDy < y2 ? limit : y2 + aDy) - y;
+ } else {
+ T limit = std::numeric_limits<T>::min();
+ y = limit - aDy > y ? limit : y + aDy;
+ height = (limit - aDy > y2 ? limit : y2 + aDy) - y;
+ }
+ }
+ void SafeMoveBy(T aDx, T aDy) {
+ SafeMoveByX(aDx);
+ SafeMoveByY(aDy);
+ }
+ void SafeMoveBy(const Point& aPoint) { SafeMoveBy(aPoint.x, aPoint.y); }
+
+ 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;
+ }
+ MOZ_ALWAYS_INLINE bool IsEqualRect(T aX, T aY, T aW, T aH) {
+ return x == aX && y == aY && width == aW && height == aH;
+ }
+ MOZ_ALWAYS_INLINE bool IsEqualXY(T aX, T aY) { return x == aX && y == aY; }
+
+ MOZ_ALWAYS_INLINE bool IsEqualSize(T aW, T aH) {
+ return width == aW && height == aH;
+ }
+
+ // 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(Corner aCorner) const {
+ switch (aCorner) {
+ case eCornerTopLeft:
+ return TopLeft();
+ case eCornerTopRight:
+ return TopRight();
+ case eCornerBottomRight:
+ return BottomRight();
+ case eCornerBottomLeft:
+ return BottomLeft();
+ }
+ MOZ_CRASH("GFX: Incomplete switch");
+ }
+ Point CCWCorner(mozilla::Side side) const {
+ switch (side) {
+ case eSideTop:
+ return TopLeft();
+ case eSideRight:
+ return TopRight();
+ case eSideBottom:
+ return BottomRight();
+ case eSideLeft:
+ return BottomLeft();
+ }
+ MOZ_CRASH("GFX: Incomplete switch");
+ }
+ Point CWCorner(mozilla::Side side) const {
+ switch (side) {
+ case eSideTop:
+ return TopRight();
+ case eSideRight:
+ return BottomRight();
+ case eSideBottom:
+ return BottomLeft();
+ case eSideLeft:
+ 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
+ MOZ_ALWAYS_INLINE T X() const { return x; }
+ MOZ_ALWAYS_INLINE T Y() const { return y; }
+ MOZ_ALWAYS_INLINE T Width() const { return width; }
+ MOZ_ALWAYS_INLINE T Height() const { return height; }
+ MOZ_ALWAYS_INLINE T XMost() const { return x + width; }
+ MOZ_ALWAYS_INLINE T YMost() const { return y + height; }
+
+ // Set width and height. SizeTo() sets them together.
+ MOZ_ALWAYS_INLINE void SetWidth(T aWidth) { width = aWidth; }
+ MOZ_ALWAYS_INLINE void SetHeight(T aHeight) { height = aHeight; }
+
+ // Get the coordinate of the edge on the given side.
+ T Edge(mozilla::Side aSide) const {
+ switch (aSide) {
+ case eSideTop:
+ return Y();
+ case eSideRight:
+ return XMost();
+ case eSideBottom:
+ return YMost();
+ case eSideLeft:
+ return X();
+ }
+ MOZ_CRASH("GFX: Incomplete switch");
+ }
+
+ // Moves one edge of the rect without moving the opposite edge.
+ void SetLeftEdge(T aX) {
+ width = XMost() - aX;
+ x = aX;
+ }
+ void SetRightEdge(T aXMost) { width = aXMost - x; }
+ void SetTopEdge(T aY) {
+ height = YMost() - aY;
+ y = aY;
+ }
+ void SetBottomEdge(T aYMost) { height = aYMost - y; }
+ void Swap() {
+ std::swap(x, y);
+ std::swap(width, height);
+ }
+
+ // 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>(std::floor(T(X()) + 0.5f));
+ T y0 = static_cast<T>(std::floor(T(Y()) + 0.5f));
+ T x1 = static_cast<T>(std::floor(T(XMost()) + 0.5f));
+ T y1 = static_cast<T>(std::floor(T(YMost()) + 0.5f));
+
+ 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>(std::ceil(T(X())));
+ T y0 = static_cast<T>(std::ceil(T(Y())));
+ T x1 = static_cast<T>(std::floor(T(XMost())));
+ T y1 = static_cast<T>(std::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>(std::floor(T(X())));
+ T y0 = static_cast<T>(std::floor(T(Y())));
+ T x1 = static_cast<T>(std::ceil(T(XMost())));
+ T y1 = static_cast<T>(std::ceil(T(YMost())));
+
+ x = x0;
+ y = y0;
+
+ width = x1 - x0;
+ height = y1 - y0;
+ }
+
+ // Scale 'this' by aScale.xScale and aScale.yScale without doing any rounding.
+ template <class Src, class Dst>
+ void Scale(const BaseScaleFactors2D<Src, Dst, T>& aScale) {
+ Scale(aScale.xScale, aScale.yScale);
+ }
+ // 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) {
+ x = x * aXScale;
+ y = y * aYScale;
+ width = width * aXScale;
+ height = height * aYScale;
+ }
+ // 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.
+ */
+ [[nodiscard]] Point ClampPoint(const Point& aPoint) const {
+ using Coord = decltype(aPoint.x);
+ return Point(std::max(Coord(x), std::min(Coord(XMost()), aPoint.x)),
+ std::max(Coord(y), std::min(Coord(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.
+ */
+ [[nodiscard]] 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()));
+ };
+
+ // Returns a point representing the distance, along each dimension, of the
+ // given point from this rectangle. The distance along a dimension is defined
+ // as zero if the point is within the bounds of the rectangle in that
+ // dimension; otherwise, it's the distance to the closer endpoint of the
+ // rectangle in that dimension.
+ Point DistanceTo(const Point& aPoint) const {
+ return {DistanceFromInterval(aPoint.x, x, XMost()),
+ DistanceFromInterval(aPoint.y, y, YMost())};
+ }
+
+ friend std::ostream& operator<<(
+ std::ostream& stream,
+ const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) {
+ return stream << "(x=" << aRect.x << ", y=" << aRect.y
+ << ", w=" << aRect.width << ", h=" << 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; }
+
+ // Helper function for DistanceTo() that computes the distance of a
+ // coordinate along one dimension from an interval in that dimension.
+ static T DistanceFromInterval(T aCoord, T aIntervalStart, T aIntervalEnd) {
+ if (aCoord < aIntervalStart) {
+ return aIntervalStart - aCoord;
+ }
+ if (aCoord > aIntervalEnd) {
+ return aCoord - aIntervalEnd;
+ }
+ return 0;
+ }
+};
+
+} // namespace mozilla::gfx
+
+#endif /* MOZILLA_GFX_BASERECT_H_ */
diff --git a/gfx/2d/BaseSize.h b/gfx/2d/BaseSize.h
new file mode 100644
index 0000000000..536a194594
--- /dev/null
+++ b/gfx/2d/BaseSize.h
@@ -0,0 +1,113 @@
+/* -*- 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_BASESIZE_H_
+#define MOZILLA_GFX_BASESIZE_H_
+
+#include <algorithm>
+#include <ostream>
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla::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; }
+
+ MOZ_ALWAYS_INLINE T Width() const { return width; }
+ MOZ_ALWAYS_INLINE T Height() const { return 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);
+ }
+
+ friend Sub Min(const Sub& aA, const Sub& aB) {
+ return Sub(std::min(aA.width, aB.width), std::min(aA.height, aB.height));
+ }
+
+ friend Sub Max(const Sub& aA, const Sub& aB) {
+ return Sub(std::max(aA.width, aB.width), std::max(aA.height, aB.height));
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseSize<T, Sub>& aSize) {
+ return aStream << '(' << aSize.width << " x " << aSize.height << ')';
+ }
+};
+
+} // namespace mozilla::gfx
+
+#endif /* MOZILLA_GFX_BASESIZE_H_ */
diff --git a/gfx/2d/BezierUtils.cpp b/gfx/2d/BezierUtils.cpp
new file mode 100644
index 0000000000..8c80d1c43f
--- /dev/null
+++ b/gfx/2d/BezierUtils.cpp
@@ -0,0 +1,326 @@
+/* -*- 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 "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, 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;
+ // In the quadratic formulat B would be 2*(a*b+c*d), however we factor the 2
+ // out Here which cancels out later.
+ Float B = a * b + c * d;
+ Float C = a * a + c * c - 1.0;
+
+ Float signB = 1.0;
+ if (B < 0.0) {
+ signB = -1.0;
+ }
+
+ // 2nd degree polynomials are typically computed using the formulae
+ // r1 = -(B - sqrt(delta)) / (2 * A)
+ // r2 = -(B + sqrt(delta)) / (2 * A)
+ // However B - sqrt(delta) can be an inportant source of precision loss for
+ // one of the roots when computing the difference between two similar and
+ // large numbers. To avoid that we pick the root with no precision loss in r1
+ // and compute r2 using the Citardauq formula.
+ // Factoring out 2 from B earlier let
+ Float S = B + signB * sqrt(B * B - A * C);
+ Float r1 = -S / A;
+ Float r2 = -C / S;
+
+#ifdef DEBUG
+ Float epsilon = (Float)0.001;
+ MOZ_ASSERT(r1 >= -epsilon);
+ MOZ_ASSERT(r2 >= -epsilon);
+#endif
+
+ return std::max((r1 < r2 ? r1 : r2), (Float)0.0);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/BezierUtils.h b/gfx/2d/BezierUtils.h
new file mode 100644
index 0000000000..3ffcd70214
--- /dev/null
+++ b/gfx/2d/BezierUtils.h
@@ -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_BezierUtils_h_
+#define mozilla_BezierUtils_h_
+
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Types.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::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_ */
diff --git a/gfx/2d/BigEndianInts.h b/gfx/2d/BigEndianInts.h
new file mode 100644
index 0000000000..b50a2e2148
--- /dev/null
+++ b/gfx/2d/BigEndianInts.h
@@ -0,0 +1,66 @@
+/* -*- 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)
+
+} // namespace mozilla
+
+#endif // mozilla_BigEndianInts_h
diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp
new file mode 100644
index 0000000000..5de04f7174
--- /dev/null
+++ b/gfx/2d/Blur.cpp
@@ -0,0 +1,904 @@
+/* -*- 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 "NumericTools.h"
+
+#include "2D.h"
+#include "DataSurfaceHelpers.h"
+#include "Tools.h"
+
+#ifdef USE_NEON
+# include "mozilla/arm.h"
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Helper function to process each row of the box blur.
+ * It takes care of transposing the data on input or output depending
+ * on whether we intend a horizontal or vertical blur, and whether we're
+ * reading from the initial source or writing to the final destination.
+ * It allows starting or ending anywhere within the row to accomodate
+ * a skip rect.
+ */
+template <bool aTransposeInput, bool aTransposeOutput>
+static inline void BoxBlurRow(const uint8_t* aInput, uint8_t* aOutput,
+ int32_t aLeftLobe, int32_t aRightLobe,
+ int32_t aWidth, int32_t aStride, int32_t aStart,
+ int32_t aEnd) {
+ // If the input or output is transposed, then we will move down a row
+ // for each step, instead of moving over a column. Since these values
+ // only depend on a template parameter, they will more easily get
+ // copy-propagated in the non-transposed case, which is why they
+ // are not passed as parameters.
+ const int32_t inputStep = aTransposeInput ? aStride : 1;
+ const int32_t outputStep = aTransposeOutput ? aStride : 1;
+
+ // We need to sample aLeftLobe pixels to the left and aRightLobe pixels
+ // to the right of the current position, then average them. So this is
+ // the size of the total width of this filter.
+ const int32_t boxSize = aLeftLobe + aRightLobe + 1;
+
+ // Instead of dividing the pixel sum by boxSize to average, we can just
+ // compute a scale that will normalize the result so that it can be quickly
+ // shifted into the desired range.
+ const uint32_t reciprocal = (1 << 24) / boxSize;
+
+ // The shift would normally truncate the result, whereas we would rather
+ // prefer to round the result to the closest increment. By adding 0.5 units
+ // to the initial sum, we bias the sum so that it will be rounded by the
+ // truncation instead.
+ uint32_t alphaSum = (boxSize + 1) / 2;
+
+ // We process the row with a moving filter, keeping a sum (alphaSum) of
+ // boxSize pixels. As we move over a pixel, we need to add on a pixel
+ // from the right extreme of the window that moved into range, and subtract
+ // off a pixel from the left extreme of window that moved out of range.
+ // But first, we need to initialization alphaSum to the contents of
+ // the window before we can get going. If the window moves out of bounds
+ // of the row, we clamp each sample to be the closest pixel from within
+ // row bounds, so the 0th and aWidth-1th pixel.
+ int32_t initLeft = aStart - aLeftLobe;
+ if (initLeft < 0) {
+ // If the left lobe samples before the row, add in clamped samples.
+ alphaSum += -initLeft * aInput[0];
+ initLeft = 0;
+ }
+ int32_t initRight = aStart + boxSize - aLeftLobe;
+ if (initRight > aWidth) {
+ // If the right lobe samples after the row, add in clamped samples.
+ alphaSum += (initRight - aWidth) * aInput[(aWidth - 1) * inputStep];
+ initRight = aWidth;
+ }
+ // Finally, add in all the valid, non-clamped samples to fill up the
+ // rest of the window.
+ const uint8_t* src = &aInput[initLeft * inputStep];
+ const uint8_t* iterEnd = &aInput[initRight * inputStep];
+
+#define INIT_ITER \
+ alphaSum += *src; \
+ src += inputStep;
+
+ // We unroll the per-pixel loop here substantially. The amount of work
+ // done per sample is so small that the cost of a loop condition check
+ // and a branch can substantially add to or even dominate the performance
+ // of the loop.
+ while (src + 16 * inputStep <= iterEnd) {
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ INIT_ITER;
+ }
+ while (src < iterEnd) {
+ INIT_ITER;
+ }
+
+ // Now we start moving the window over the row. We will be accessing
+ // pixels form aStart - aLeftLobe up to aEnd + aRightLobe, which may be
+ // out of bounds of the row. To avoid having to check within the inner
+ // loops if we are in bound, we instead compute the points at which
+ // we will move out of bounds of the row on the left side (splitLeft)
+ // and right side (splitRight).
+ int32_t splitLeft = std::min(std::max(aLeftLobe, aStart), aEnd);
+ int32_t splitRight =
+ std::min(std::max(aWidth - (boxSize - aLeftLobe), aStart), aEnd);
+ // If the filter window is actually large than the size of the row,
+ // there will be a middle area of overlap where the leftmost and rightmost
+ // pixel of the filter will both be outside the row. In this case, we need
+ // to invert the splits so that splitLeft <= splitRight.
+ if (boxSize > aWidth) {
+ std::swap(splitLeft, splitRight);
+ }
+
+ // Process all pixels up to splitLeft that would sample before the start of
+ // the row. Note that because inputStep and outputStep may not be a const 1
+ // value, it is more performant to increment pointers here for the source and
+ // destination rather than use a loop counter, since doing so would entail an
+ // expensive multiplication that significantly slows down the loop.
+ uint8_t* dst = &aOutput[aStart * outputStep];
+ iterEnd = &aOutput[splitLeft * outputStep];
+ src = &aInput[(aStart + boxSize - aLeftLobe) * inputStep];
+ uint8_t firstVal = aInput[0];
+
+#define LEFT_ITER \
+ *dst = (alphaSum * reciprocal) >> 24; \
+ alphaSum += *src - firstVal; \
+ dst += outputStep; \
+ src += inputStep;
+
+ while (dst + 16 * outputStep <= iterEnd) {
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ LEFT_ITER;
+ }
+ while (dst < iterEnd) {
+ LEFT_ITER;
+ }
+
+ // Process all pixels between splitLeft and splitRight.
+ iterEnd = &aOutput[splitRight * outputStep];
+ if (boxSize <= aWidth) {
+ // The filter window is smaller than the row size, so the leftmost and
+ // rightmost samples are both within row bounds.
+ src = &aInput[(splitLeft - aLeftLobe) * inputStep];
+ int32_t boxStep = boxSize * inputStep;
+
+#define CENTER_ITER \
+ *dst = (alphaSum * reciprocal) >> 24; \
+ alphaSum += src[boxStep] - *src; \
+ dst += outputStep; \
+ src += inputStep;
+
+ while (dst + 16 * outputStep <= iterEnd) {
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ CENTER_ITER;
+ }
+ while (dst < iterEnd) {
+ CENTER_ITER;
+ }
+ } else {
+ // The filter window is larger than the row size, and we're in the area of
+ // split overlap. So the leftmost and rightmost samples are both out of
+ // bounds and need to be clamped. We can just precompute the difference here
+ // consequently.
+ int32_t firstLastDiff = aInput[(aWidth - 1) * inputStep] - aInput[0];
+ while (dst < iterEnd) {
+ *dst = (alphaSum * reciprocal) >> 24;
+ alphaSum += firstLastDiff;
+ dst += outputStep;
+ }
+ }
+
+ // Process all remaining pixels after splitRight that would sample after the
+ // row end.
+ iterEnd = &aOutput[aEnd * outputStep];
+ src = &aInput[(splitRight - aLeftLobe) * inputStep];
+ uint8_t lastVal = aInput[(aWidth - 1) * inputStep];
+
+#define RIGHT_ITER \
+ *dst = (alphaSum * reciprocal) >> 24; \
+ alphaSum += lastVal - *src; \
+ dst += outputStep; \
+ src += inputStep;
+
+ while (dst + 16 * outputStep <= iterEnd) {
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ RIGHT_ITER;
+ }
+ while (dst < iterEnd) {
+ RIGHT_ITER;
+ }
+}
+
+/**
+ * Box blur involves looking at one pixel, and setting its value to the average
+ * of its neighbouring pixels. This is meant to provide a 3-pass approximation
+ * of a Gaussian blur.
+ * @param aTranspose Whether to transpose the buffer when reading and writing
+ * to it.
+ * @param aData The buffer to be blurred.
+ * @param aLobes The number of pixels to blend on the left and right for each of
+ * 3 passes.
+ * @param aWidth The number of columns in the buffers.
+ * @param aRows The number of rows in the buffers.
+ * @param aStride The stride of the buffer.
+ */
+template <bool aTranspose>
+static void BoxBlur(uint8_t* aData, const int32_t aLobes[3][2], int32_t aWidth,
+ int32_t aRows, int32_t aStride, IntRect aSkipRect) {
+ if (aTranspose) {
+ std::swap(aWidth, aRows);
+ aSkipRect.Swap();
+ }
+
+ MOZ_ASSERT(aWidth > 0);
+
+ // All three passes of the box blur that approximate the Gaussian are done
+ // on each row in turn, so we only need two temporary row buffers to process
+ // each row, instead of a full-sized buffer. Data moves from the source to the
+ // first temporary, from the first temporary to the second, then from the
+ // second back to the destination. This way is more cache-friendly than
+ // processing whe whole buffer in each pass and thus yields a nice speedup.
+ uint8_t* tmpRow = new (std::nothrow) uint8_t[2 * aWidth];
+ if (!tmpRow) {
+ return;
+ }
+ uint8_t* tmpRow2 = tmpRow + aWidth;
+
+ const int32_t stride = aTranspose ? 1 : aStride;
+ 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 = aSkipRect.ContainsY(y);
+ if (inSkipRectY && skipRectCoversWholeRow) {
+ aData += stride * (aSkipRect.YMost() - y);
+ y = aSkipRect.YMost() - 1;
+ continue;
+ }
+
+ // Read in data from the source transposed if necessary.
+ BoxBlurRow<aTranspose, false>(aData, tmpRow, aLobes[0][0], aLobes[0][1],
+ aWidth, aStride, 0, aWidth);
+
+ // For the middle pass, the data is already pre-transposed and does not need
+ // to be post-transposed yet.
+ BoxBlurRow<false, false>(tmpRow, tmpRow2, aLobes[1][0], aLobes[1][1],
+ aWidth, aStride, 0, aWidth);
+
+ // Write back data to the destination transposed if necessary too.
+ // Make sure not to overwrite the skip rect by only outputting to the
+ // destination before and after the skip rect, if requested.
+ int32_t skipStart =
+ inSkipRectY ? std::min(std::max(aSkipRect.X(), 0), aWidth) : aWidth;
+ int32_t skipEnd = std::max(skipStart, aSkipRect.XMost());
+ if (skipStart > 0) {
+ BoxBlurRow<false, aTranspose>(tmpRow2, aData, aLobes[2][0], aLobes[2][1],
+ aWidth, aStride, 0, skipStart);
+ }
+ if (skipEnd < aWidth) {
+ BoxBlurRow<false, aTranspose>(tmpRow2, aData, aLobes[2][0], aLobes[2][1],
+ aWidth, aStride, skipEnd, aWidth);
+ }
+
+ aData += stride;
+ }
+
+ delete[] tmpRow;
+}
+
+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(uint8_t* aInput, uint8_t* 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 = aSkipRect.ContainsY(y);
+ 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 && aSkipRect.ContainsX(x)) {
+ x = aSkipRect.XMost();
+ if (x >= aWidth) break;
+ }
+
+ int32_t sMin = std::max(x - aRadius, 0);
+ int32_t sMax = std::min(x + aRadius, aWidth - 1);
+ int32_t v = 0;
+ for (int32_t s = sMin; s <= sMax; ++s) {
+ v = std::max<int32_t>(v, aInput[aStride * y + s]);
+ }
+ aOutput[aStride * y + x] = v;
+ }
+ }
+}
+
+static void SpreadVertical(uint8_t* aInput, uint8_t* 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 = aSkipRect.ContainsX(x);
+ 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 && aSkipRect.ContainsY(y)) {
+ y = aSkipRect.YMost();
+ if (y >= aRows) break;
+ }
+
+ int32_t sMin = std::max(y - aRadius, 0);
+ int32_t sMax = std::min(y + aRadius, aRows - 1);
+ int32_t v = 0;
+ for (int32_t s = sMin; s <= sMax; ++s) {
+ v = std::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)
+ : mStride(0), mSurfaceAllocationSize(0) {
+ Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect);
+}
+
+AlphaBoxBlur::AlphaBoxBlur()
+ : mStride(0), mSurfaceAllocationSize(0), mHasDirtyRect(false) {}
+
+void AlphaBoxBlur::Init(const Rect& aRect, const IntSize& aSpreadRadius,
+ const IntSize& aBlurRadius, const Rect* aDirtyRect,
+ const Rect* aSkipRect) {
+ mSpreadRadius = aSpreadRadius;
+ mBlurRadius = aBlurRadius;
+
+ 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 = TruncatedToInt(rect);
+ 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.Deflate(Size(aBlurRadius + aSpreadRadius));
+ mSkipRect = RoundedIn(skipRect);
+ mSkipRect = mSkipRect.Intersect(mRect);
+ if (mSkipRect.IsEqualInterior(mRect)) {
+ return;
+ }
+
+ mSkipRect -= mRect.TopLeft();
+ // Ensure the skip rect is 4-pixel-aligned in the x axis, so that all our
+ // accesses later are aligned as well, see bug 1622113.
+ mSkipRect.SetLeftEdge(RoundUpToMultiple(mSkipRect.X(), 4));
+ mSkipRect.SetRightEdge(RoundDownToMultiple(mSkipRect.XMost(), 4));
+ if (mSkipRect.IsEmpty()) {
+ mSkipRect = IntRect();
+ }
+ } else {
+ mSkipRect = IntRect();
+ }
+
+ 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(TruncatedToInt(aRect)),
+ mSpreadRadius(),
+ mBlurRadius(CalculateBlurRadius(Point(aSigmaX, aSigmaY))),
+ mStride(aStride),
+ mSurfaceAllocationSize(0),
+ mHasDirtyRect(false) {
+ IntRect intRect;
+ if (aRect.ToIntRect(&intRect)) {
+ size_t minDataSize =
+ BufferSizeFromStrideAndHeight(intRect.Width(), intRect.Height());
+ if (minDataSize != 0) {
+ mSurfaceAllocationSize = minDataSize;
+ }
+ }
+}
+
+AlphaBoxBlur::~AlphaBoxBlur() = default;
+
+IntSize AlphaBoxBlur::GetSize() const {
+ IntSize size(mRect.Width(), mRect.Height());
+ return size;
+}
+
+int32_t AlphaBoxBlur::GetStride() const { return mStride; }
+
+IntRect AlphaBoxBlur::GetRect() const { return mRect; }
+
+Rect* AlphaBoxBlur::GetDirtyRect() {
+ if (mHasDirtyRect) {
+ return &mDirtyRect;
+ }
+
+ return nullptr;
+}
+
+size_t AlphaBoxBlur::GetSurfaceAllocationSize() const {
+ return mSurfaceAllocationSize;
+}
+
+void AlphaBoxBlur::Blur(uint8_t* aData) const {
+ 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;
+ uint8_t* tmpData = new (std::nothrow) uint8_t[szB];
+
+ if (!tmpData) {
+ return;
+ }
+
+ memset(tmpData, 0, szB);
+
+ SpreadHorizontal(aData, tmpData, mSpreadRadius.width, size.width,
+ size.height, stride, mSkipRect);
+ SpreadVertical(tmpData, aData, mSpreadRadius.height, size.width,
+ size.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!
+ if (mBlurRadius.width > 0) {
+ BoxBlur<false>(aData, horizontalLobes, size.width, size.height, stride,
+ mSkipRect);
+ }
+ if (mBlurRadius.height > 0) {
+ BoxBlur<true>(aData, verticalLobes, size.width, size.height, stride,
+ mSkipRect);
+ }
+ } 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 USE_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) const {
+ 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++) {
+ // Not using ContainsY(y) because we do not skip y == skipRect.Y()
+ // although that may not be done on purpose
+ 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++) {
+ // Not using ContainsX(x) because we do not skip x == skipRect.X()
+ // although that may not be done on purpose
+ 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;
+}
+
+Float AlphaBoxBlur::CalculateBlurSigma(int32_t aBlurRadius) {
+ return aBlurRadius / GAUSSIAN_SCALE_FACTOR;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h
new file mode 100644
index 0000000000..e0f18c4c39
--- /dev/null
+++ b/gfx/2d/Blur.h
@@ -0,0 +1,198 @@
+/* -*- 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 final {
+ 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();
+
+ void Init(const Rect& aRect, const IntSize& aSpreadRadius,
+ const IntSize& aBlurRadius, const Rect* aDirtyRect,
+ const Rect* aSkipRect);
+
+ ~AlphaBoxBlur();
+
+ /**
+ * Return the size, in pixels, of the 8-bit alpha surface we'd use.
+ */
+ IntSize GetSize() const;
+
+ /**
+ * Return the stride, in bytes, of the 8-bit alpha surface we'd use.
+ */
+ int32_t GetStride() const;
+
+ /**
+ * Returns the device-space rectangle the 8-bit alpha surface covers.
+ */
+ IntRect GetRect() const;
+
+ /**
+ * Return a pointer to a dirty rect, as passed in to the constructor, or
+ * nullptr if none was passed in.
+ */
+ Rect* GetDirtyRect();
+
+ /**
+ * Return the spread radius, in pixels.
+ */
+ IntSize GetSpreadRadius() const { return mSpreadRadius; }
+
+ /**
+ * Return the blur radius, in pixels.
+ */
+ IntSize GetBlurRadius() const { return mBlurRadius; }
+
+ /**
+ * 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) const;
+
+ /**
+ * 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);
+ static Float CalculateBlurSigma(int32_t aBlurRadius);
+
+ 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) const;
+ void BoxBlur_SSE2(uint8_t* aData, int32_t aLeftLobe, int32_t aRightLobe,
+ int32_t aTopLobe, int32_t aBottomLobe,
+ uint32_t* aIntegralImage,
+ size_t aIntegralImageStride) const;
+ void BoxBlur_NEON(uint8_t* aData, int32_t aLeftLobe, int32_t aRightLobe,
+ int32_t aTopLobe, int32_t aBottomLobe,
+ uint32_t* aIntegralImage,
+ size_t aIntegralImageStride) const;
+#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) const;
+#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.
+ *
+ * This is guaranteed to be 4-pixel aligned in the x axis.
+ */
+ 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_ */
diff --git a/gfx/2d/BlurLS3.cpp b/gfx/2d/BlurLS3.cpp
new file mode 100644
index 0000000000..f4b96e5cf3
--- /dev/null
+++ b/gfx/2d/BlurLS3.cpp
@@ -0,0 +1,569 @@
+/* -*- 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 <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) const {
+ 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"
+ );
+ }
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* _MIPS_ARCH_LOONGSON3A */
diff --git a/gfx/2d/BlurNEON.cpp b/gfx/2d/BlurNEON.cpp
new file mode 100644
index 0000000000..601b6f363d
--- /dev/null
+++ b/gfx/2d/BlurNEON.cpp
@@ -0,0 +1,303 @@
+/* -*- 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 <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) const {
+ 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);
+ }
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/BlurSSE2.cpp b/gfx/2d/BlurSSE2.cpp
new file mode 100644
index 0000000000..69a30367ba
--- /dev/null
+++ b/gfx/2d/BlurSSE2.cpp
@@ -0,0 +1,345 @@
+/* -*- 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 "SSEHelpers.h"
+
+#include <string.h>
+
+namespace mozilla::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) const {
+ 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++) {
+ // Not using ContainsY(y) because we do not skip y == skipRect.Y()
+ // although that may not be done on purpose
+ 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) {
+ // Not using ContainsX(x) because we do not skip x == skipRect.X()
+ // although that may not be done on purpose
+ 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) {
+ // Not using Containsx(x) because we do not skip x == skipRect.X()
+ // although that may not be done on purpose
+ 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 mozilla::gfx
diff --git a/gfx/2d/BorrowedContext.h b/gfx/2d/BorrowedContext.h
new file mode 100644
index 0000000000..a908b0497c
--- /dev/null
+++ b/gfx/2d/BorrowedContext.h
@@ -0,0 +1,131 @@
+/* -*- 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_BORROWED_CONTEXT_H
+#define _MOZILLA_GFX_BORROWED_CONTEXT_H
+
+#include "2D.h"
+
+#ifdef MOZ_X11
+# include <X11/Xlib.h>
+# include "X11UndefineNone.h"
+#endif
+
+namespace mozilla {
+
+namespace gfx {
+
+#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) {}
+
+ explicit BorrowedXlibDrawable(DrawTarget* aDT)
+ : mDT(nullptr),
+ mDisplay(nullptr),
+ mDrawable(X11None),
+ mScreen(nullptr),
+ mVisual(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; }
+
+ private:
+ DrawTarget* mDT;
+ Display* mDisplay;
+ Drawable mDrawable;
+ Screen* mScreen;
+ Visual* mVisual;
+ 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:
+ static CGContextRef BorrowCGContextFromDrawTarget(DrawTarget* aDT);
+ static void ReturnCGContextToDrawTarget(DrawTarget* aDT, CGContextRef cg);
+ DrawTarget* mDT;
+};
+#endif
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_BORROWED_CONTEXT_H
diff --git a/gfx/2d/BufferEdgePad.cpp b/gfx/2d/BufferEdgePad.cpp
new file mode 100644
index 0000000000..69f508f788
--- /dev/null
+++ b/gfx/2d/BufferEdgePad.cpp
@@ -0,0 +1,104 @@
+/* -*- 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 "BufferEdgePad.h"
+
+#include "2D.h" // for DrawTarget
+#include "Point.h" // for IntSize
+#include "Types.h" // for SurfaceFormat
+
+#include "nsRegion.h"
+
+namespace mozilla {
+namespace gfx {
+
+void PadDrawTargetOutFromRegion(DrawTarget* aDrawTarget,
+ const nsIntRegion& aRegion) {
+ struct LockedBits {
+ uint8_t* data;
+ IntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+ static int clamp(int x, int min, int max) {
+ if (x < min) x = min;
+ if (x > max) x = max;
+ return x;
+ }
+
+ static void ensure_memcpy(uint8_t* dst, uint8_t* src, size_t n,
+ uint8_t* bitmap, int stride, int height) {
+ if (src + n > bitmap + stride * height) {
+ MOZ_CRASH("GFX: long src memcpy");
+ }
+ if (src < bitmap) {
+ MOZ_CRASH("GFX: short src memcpy");
+ }
+ if (dst + n > bitmap + stride * height) {
+ MOZ_CRASH("GFX: long dst mempcy");
+ }
+ if (dst < bitmap) {
+ MOZ_CRASH("GFX: short dst mempcy");
+ }
+ }
+
+ static void visitor(void* closure, VisitSide side, int x1, int y1, int x2,
+ int y2) {
+ LockedBits* lb = static_cast<LockedBits*>(closure);
+ uint8_t* bitmap = lb->data;
+ const int bpp = gfx::BytesPerPixel(lb->format);
+ const int stride = lb->stride;
+ const int width = lb->size.width;
+ const int height = lb->size.height;
+
+ if (side == VisitSide::TOP) {
+ if (y1 > 0) {
+ x1 = clamp(x1, 0, width - 1);
+ x2 = clamp(x2, 0, width - 1);
+ ensure_memcpy(&bitmap[x1 * bpp + (y1 - 1) * stride],
+ &bitmap[x1 * bpp + y1 * stride], (x2 - x1) * bpp,
+ bitmap, stride, height);
+ memcpy(&bitmap[x1 * bpp + (y1 - 1) * stride],
+ &bitmap[x1 * bpp + y1 * stride], (x2 - x1) * bpp);
+ }
+ } else if (side == VisitSide::BOTTOM) {
+ if (y1 < height) {
+ x1 = clamp(x1, 0, width - 1);
+ x2 = clamp(x2, 0, width - 1);
+ ensure_memcpy(&bitmap[x1 * bpp + y1 * stride],
+ &bitmap[x1 * bpp + (y1 - 1) * stride], (x2 - x1) * bpp,
+ bitmap, stride, height);
+ memcpy(&bitmap[x1 * bpp + y1 * stride],
+ &bitmap[x1 * bpp + (y1 - 1) * stride], (x2 - x1) * bpp);
+ }
+ } else if (side == VisitSide::LEFT) {
+ if (x1 > 0) {
+ while (y1 != y2) {
+ memcpy(&bitmap[(x1 - 1) * bpp + y1 * stride],
+ &bitmap[x1 * bpp + y1 * stride], bpp);
+ y1++;
+ }
+ }
+ } else if (side == VisitSide::RIGHT) {
+ if (x1 < width) {
+ while (y1 != y2) {
+ memcpy(&bitmap[x1 * bpp + y1 * stride],
+ &bitmap[(x1 - 1) * bpp + y1 * stride], bpp);
+ y1++;
+ }
+ }
+ }
+ }
+ } lb;
+
+ if (aDrawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) {
+ // we can only pad software targets so if we can't lock the bits don't pad
+ aRegion.VisitEdges(lb.visitor, &lb);
+ aDrawTarget->ReleaseBits(lb.data);
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/BufferEdgePad.h b/gfx/2d/BufferEdgePad.h
new file mode 100644
index 0000000000..6381a5f2fa
--- /dev/null
+++ b/gfx/2d/BufferEdgePad.h
@@ -0,0 +1,23 @@
+/* -*- 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_BUFFER_EDGE_PAD_H
+#define MOZILLA_GFX_BUFFER_EDGE_PAD_H
+
+#include "nsRegionFwd.h"
+
+namespace mozilla {
+namespace gfx {
+
+class DrawTarget;
+
+void PadDrawTargetOutFromRegion(DrawTarget* aDrawTarget,
+ const nsIntRegion& aRegion);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_BUFFER_EDGE_PAD_H
diff --git a/gfx/2d/BufferUnrotate.cpp b/gfx/2d/BufferUnrotate.cpp
new file mode 100644
index 0000000000..7b6768fdbd
--- /dev/null
+++ b/gfx/2d/BufferUnrotate.cpp
@@ -0,0 +1,74 @@
+/* -*- 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 "BufferUnrotate.h"
+
+#include <algorithm> // min & max
+#include <cstdlib>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace mozilla {
+namespace gfx {
+
+void BufferUnrotate(uint8_t* aBuffer, int aByteWidth, int aHeight,
+ int aByteStride, int aXBoundary, int aYBoundary) {
+ if (aXBoundary != 0) {
+ uint8_t* line = new uint8_t[aByteWidth];
+ uint32_t smallStart = 0;
+ uint32_t smallLen = aXBoundary;
+ uint32_t smallDest = aByteWidth - aXBoundary;
+ uint32_t largeStart = aXBoundary;
+ uint32_t largeLen = aByteWidth - aXBoundary;
+ uint32_t largeDest = 0;
+ if (aXBoundary > aByteWidth / 2) {
+ smallStart = aXBoundary;
+ smallLen = aByteWidth - aXBoundary;
+ smallDest = 0;
+ largeStart = 0;
+ largeLen = aXBoundary;
+ largeDest = smallLen;
+ }
+
+ for (int y = 0; y < aHeight; y++) {
+ int yOffset = y * aByteStride;
+ memcpy(line, &aBuffer[yOffset + smallStart], smallLen);
+ memmove(&aBuffer[yOffset + largeDest], &aBuffer[yOffset + largeStart],
+ largeLen);
+ memcpy(&aBuffer[yOffset + smallDest], line, smallLen);
+ }
+
+ delete[] line;
+ }
+
+ if (aYBoundary != 0) {
+ uint32_t smallestHeight = std::min(aHeight - aYBoundary, aYBoundary);
+ uint32_t largestHeight = std::max(aHeight - aYBoundary, aYBoundary);
+ uint32_t smallOffset = 0;
+ uint32_t largeOffset = aYBoundary * aByteStride;
+ uint32_t largeDestOffset = 0;
+ uint32_t smallDestOffset = largestHeight * aByteStride;
+ if (aYBoundary > aHeight / 2) {
+ smallOffset = aYBoundary * aByteStride;
+ largeOffset = 0;
+ largeDestOffset = smallestHeight * aByteStride;
+ smallDestOffset = 0;
+ }
+
+ uint8_t* smallestSide = new uint8_t[aByteStride * smallestHeight];
+ memcpy(smallestSide, &aBuffer[smallOffset], aByteStride * smallestHeight);
+ memmove(&aBuffer[largeDestOffset], &aBuffer[largeOffset],
+ aByteStride * largestHeight);
+ memcpy(&aBuffer[smallDestOffset], smallestSide,
+ aByteStride * smallestHeight);
+ delete[] smallestSide;
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/BufferUnrotate.h b/gfx/2d/BufferUnrotate.h
new file mode 100644
index 0000000000..f0422255f9
--- /dev/null
+++ b/gfx/2d/BufferUnrotate.h
@@ -0,0 +1,21 @@
+/* -*- 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_BUFFER_UNROTATE_H
+#define MOZILLA_GFX_BUFFER_UNROTATE_H
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace gfx {
+
+void BufferUnrotate(uint8_t* aBuffer, int aByteWidth, int aHeight,
+ int aByteStride, int aXByteBoundary, int aYBoundary);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_BUFFER_UNROTATE_H
diff --git a/gfx/2d/ConicGradientEffectD2D1.cpp b/gfx/2d/ConicGradientEffectD2D1.cpp
new file mode 100644
index 0000000000..d94059e3d2
--- /dev/null
+++ b/gfx/2d/ConicGradientEffectD2D1.cpp
@@ -0,0 +1,375 @@
+/* -*- 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 "ConicGradientEffectD2D1.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='ConicGradientEffect'/>
+ <Property name='Author' type='string' value='Mozilla'/>
+ <Property name='Category' type='string' value='Pattern effects'/>
+ <Property name='Description' type='string' value='This effect is used to render CSS conic gradients.'/>
+ <Inputs>
+ <Input name='Geometry'/>
+ </Inputs>
+ <Property name='StopCollection' type='iunknown'>
+ <Property name='DisplayName' type='string' value='Gradient stop collection'/>
+ </Property>
+ <Property name='Center' type='vector2'>
+ <Property name='DisplayName' type='string' value='Gradient center'/>
+ </Property>
+ <Property name='Angle' type='vector2'>
+ <Property name='DisplayName' type='string' value='Gradient angle'/>
+ </Property>
+ <Property name='StartOffset' type='float'>
+ <Property name='DisplayName' type='string' value='Start stop offset'/>
+ </Property>
+ <Property name='EndOffset' type='float'>
+ <Property name='DisplayName' type='string' value='End stop offset'/>
+ </Property>
+ <Property name='Transform' type='matrix3x2'>
+ <Property name='DisplayName' type='string' value='Transform applied to the pattern'/>
+ </Property>
+
+ </Effect>
+ );
+
+// {091fda1d-857e-4b1e-828f-1c839d9b7897}
+static const GUID GUID_SampleConicGradientPS = {
+ 0x091fda1d,
+ 0x857e,
+ 0x4b1e,
+ {0x82, 0x8f, 0x1c, 0x83, 0x9d, 0x9b, 0x78, 0x97}};
+
+namespace mozilla {
+namespace gfx {
+
+ConicGradientEffectD2D1::ConicGradientEffectD2D1()
+ : mRefCount(0),
+ mCenter(D2D1::Vector2F(0, 0)),
+ mAngle(0),
+ mStartOffset(0),
+ mEndOffset(0),
+ mTransform(D2D1::IdentityMatrix())
+
+{}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal,
+ ID2D1TransformGraph* pTransformGraph) {
+ HRESULT hr;
+
+ hr = pContextInternal->LoadPixelShader(GUID_SampleConicGradientPS,
+ SampleConicGradientPS,
+ sizeof(SampleConicGradientPS));
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ hr = pTransformGraph->SetSingleTransformNode(this);
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ mEffectContext = pContextInternal;
+
+ return S_OK;
+}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) {
+ if (changeType == D2D1_CHANGE_TYPE_NONE) {
+ return S_OK;
+ }
+
+ // We'll need to inverse transform our pixel, precompute inverse here.
+ Matrix mat = ToMatrix(mTransform);
+ if (!mat.Invert()) {
+ // Singular
+ return S_OK;
+ }
+
+ if (!mStopCollection) {
+ return S_OK;
+ }
+
+ HRESULT hr = mDrawInfo->SetPixelShader(GUID_SampleConicGradientPS);
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ RefPtr<ID2D1ResourceTexture> tex = CreateGradientTexture();
+ hr = mDrawInfo->SetResourceTexture(1, tex);
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ struct PSConstantBuffer {
+ float center[2];
+ float angle;
+ float start_offset;
+ float end_offset;
+ float repeat_correct;
+ float allow_odd;
+ float padding[1];
+ float transform[8];
+ };
+
+ PSConstantBuffer buffer = {
+ {mCenter.x, mCenter.y},
+ mAngle,
+ mStartOffset,
+ mEndOffset,
+ mStopCollection->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP ? 1.0f : 0.0f,
+ mStopCollection->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR ? 1.0f : 0.0f,
+ {0.0f},
+ {mat._11, mat._21, mat._31, 0.0f, mat._12, mat._22, mat._32, 0.0f}};
+
+ hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer));
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ return S_OK;
+}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) {
+ return pGraph->SetSingleTransformNode(this);
+}
+
+IFACEMETHODIMP_(ULONG)
+ConicGradientEffectD2D1::AddRef() { return ++mRefCount; }
+
+IFACEMETHODIMP_(ULONG)
+ConicGradientEffectD2D1::Release() {
+ if (!--mRefCount) {
+ delete this;
+ return 0;
+ }
+ return mRefCount;
+}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::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;
+}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::MapInputRectsToOutputRect(
+ const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects,
+ UINT32 inputRectCount, D2D1_RECT_L* pOutputRect,
+ D2D1_RECT_L* pOutputOpaqueSubRect) {
+ if (inputRectCount != 1) {
+ return E_INVALIDARG;
+ }
+
+ *pOutputRect = *pInputRects;
+ *pOutputOpaqueSubRect = *pInputOpaqueSubRects;
+ return S_OK;
+}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::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
+ConicGradientEffectD2D1::MapInvalidRect(UINT32 inputIndex,
+ D2D1_RECT_L invalidInputRect,
+ D2D1_RECT_L* pInvalidOutputRect) const {
+ MOZ_ASSERT(inputIndex == 0);
+
+ *pInvalidOutputRect = invalidInputRect;
+ return S_OK;
+}
+
+IFACEMETHODIMP
+ConicGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo* pDrawInfo) {
+ mDrawInfo = pDrawInfo;
+ return S_OK;
+}
+
+HRESULT
+ConicGradientEffectD2D1::Register(ID2D1Factory1* aFactory) {
+ D2D1_PROPERTY_BINDING bindings[] = {
+ D2D1_VALUE_TYPE_BINDING(L"StopCollection",
+ &ConicGradientEffectD2D1::SetStopCollection,
+ &ConicGradientEffectD2D1::GetStopCollection),
+ D2D1_VALUE_TYPE_BINDING(L"Center", &ConicGradientEffectD2D1::SetCenter,
+ &ConicGradientEffectD2D1::GetCenter),
+ D2D1_VALUE_TYPE_BINDING(L"Angle", &ConicGradientEffectD2D1::SetAngle,
+ &ConicGradientEffectD2D1::GetAngle),
+ D2D1_VALUE_TYPE_BINDING(L"StartOffset",
+ &ConicGradientEffectD2D1::SetStartOffset,
+ &ConicGradientEffectD2D1::GetStartOffset),
+ D2D1_VALUE_TYPE_BINDING(L"EndOffset",
+ &ConicGradientEffectD2D1::SetEndOffset,
+ &ConicGradientEffectD2D1::GetEndOffset),
+ D2D1_VALUE_TYPE_BINDING(L"Transform",
+ &ConicGradientEffectD2D1::SetTransform,
+ &ConicGradientEffectD2D1::GetTransform)};
+ HRESULT hr = aFactory->RegisterEffectFromString(
+ CLSID_ConicGradientEffect, kXmlDescription, bindings, ARRAYSIZE(bindings),
+ CreateEffect);
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to register radial gradient effect.";
+ }
+ return hr;
+}
+
+void ConicGradientEffectD2D1::Unregister(ID2D1Factory1* aFactory) {
+ aFactory->UnregisterEffect(CLSID_ConicGradientEffect);
+}
+
+HRESULT __stdcall ConicGradientEffectD2D1::CreateEffect(
+ IUnknown** aEffectImpl) {
+ *aEffectImpl = static_cast<ID2D1EffectImpl*>(new ConicGradientEffectD2D1());
+ (*aEffectImpl)->AddRef();
+
+ return S_OK;
+}
+
+HRESULT
+ConicGradientEffectD2D1::SetStopCollection(IUnknown* aStopCollection) {
+ if (SUCCEEDED(aStopCollection->QueryInterface(
+ (ID2D1GradientStopCollection**)getter_AddRefs(mStopCollection)))) {
+ return S_OK;
+ }
+
+ return E_INVALIDARG;
+}
+
+already_AddRefed<ID2D1ResourceTexture>
+ConicGradientEffectD2D1::CreateGradientTexture() {
+ std::vector<D2D1_GRADIENT_STOP> rawStops;
+ rawStops.resize(mStopCollection->GetGradientStopCount());
+ mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size());
+
+ std::vector<unsigned char> textureData;
+ textureData.resize(4096 * 4);
+ unsigned char* texData = &textureData.front();
+
+ float prevColorPos = 0;
+ float nextColorPos = rawStops[0].position;
+ D2D1_COLOR_F prevColor = rawStops[0].color;
+ D2D1_COLOR_F nextColor = prevColor;
+ uint32_t stopPosition = 1;
+
+ // Not the most optimized way but this will do for now.
+ for (int i = 0; i < 4096; i++) {
+ // The 4095 seems a little counter intuitive, but we want the gradient
+ // color at offset 0 at the first pixel, and at offset 1.0f at the last
+ // pixel.
+ float pos = float(i) / 4095;
+
+ while (pos > nextColorPos) {
+ prevColor = nextColor;
+ prevColorPos = nextColorPos;
+ if (rawStops.size() > stopPosition) {
+ nextColor = rawStops[stopPosition].color;
+ nextColorPos = rawStops[stopPosition++].position;
+ } else {
+ nextColorPos = 1.0f;
+ }
+ }
+
+ float interp;
+
+ if (nextColorPos != prevColorPos) {
+ interp = (pos - prevColorPos) / (nextColorPos - prevColorPos);
+ } else {
+ interp = 0;
+ }
+
+ DeviceColor newColor(prevColor.r + (nextColor.r - prevColor.r) * interp,
+ prevColor.g + (nextColor.g - prevColor.g) * interp,
+ prevColor.b + (nextColor.b - prevColor.b) * interp,
+ prevColor.a + (nextColor.a - prevColor.a) * interp);
+
+ // Note D2D expects RGBA here!!
+ texData[i * 4] = (char)(255.0f * newColor.r);
+ texData[i * 4 + 1] = (char)(255.0f * newColor.g);
+ texData[i * 4 + 2] = (char)(255.0f * newColor.b);
+ texData[i * 4 + 3] = (char)(255.0f * newColor.a);
+ }
+
+ RefPtr<ID2D1ResourceTexture> tex;
+
+ UINT32 width = 4096;
+ UINT32 stride = 4096 * 4;
+ D2D1_RESOURCE_TEXTURE_PROPERTIES props;
+ // Older shader models do not support 1D textures. So just use a width x 1
+ // texture.
+ props.dimensions = 2;
+ UINT32 dims[] = {width, 1};
+ props.extents = dims;
+ props.channelDepth = D2D1_CHANNEL_DEPTH_4;
+ props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM;
+ props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR;
+ D2D1_EXTEND_MODE extendMode[] = {mStopCollection->GetExtendMode(),
+ mStopCollection->GetExtendMode()};
+ props.extendModes = extendMode;
+
+ HRESULT hr = mEffectContext->CreateResourceTexture(
+ nullptr, &props, &textureData.front(), &stride, 4096 * 4,
+ getter_AddRefs(tex));
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to create resource texture: " << hexa(hr);
+ }
+
+ return tex.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ConicGradientEffectD2D1.h b/gfx/2d/ConicGradientEffectD2D1.h
new file mode 100644
index 0000000000..b0e168417b
--- /dev/null
+++ b/gfx/2d/ConicGradientEffectD2D1.h
@@ -0,0 +1,103 @@
+/* -*- 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_CONICGRADIENTEFFECTD2D1_H_
+#define MOZILLA_GFX_CONICGRADIENTEFFECTD2D1_H_
+
+#include <d2d1_1.h>
+#include <d2d1effectauthor.h>
+#include <d2d1effecthelpers.h>
+
+#include "2D.h"
+#include "mozilla/Attributes.h"
+
+// {fa4e3246-be57-4052-8c8b-881cc3633287}
+DEFINE_GUID(CLSID_ConicGradientEffect, 0xfa4e3246, 0xbe57, 0x4052, 0x8c, 0x8b,
+ 0x88, 0x1c, 0xc3, 0x63, 0x32, 0x87);
+
+// Macro to keep our class nice and clean.
+#define SIMPLE_PROP(type, name) \
+ public: \
+ HRESULT Set##name(type a##name) { \
+ m##name = a##name; \
+ return S_OK; \
+ } \
+ type Get##name() const { return m##name; } \
+ \
+ private: \
+ type m##name;
+
+namespace mozilla {
+namespace gfx {
+
+enum {
+ CONIC_PROP_STOP_COLLECTION = 0,
+ CONIC_PROP_CENTER,
+ CONIC_PROP_ANGLE,
+ CONIC_PROP_START_OFFSET,
+ CONIC_PROP_END_OFFSET,
+ CONIC_PROP_TRANSFORM
+};
+
+class ConicGradientEffectD2D1 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);
+
+ static HRESULT Register(ID2D1Factory1* aFactory);
+ static void Unregister(ID2D1Factory1* aFactory);
+ static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl);
+
+ HRESULT SetStopCollection(IUnknown* aStopCollection);
+ IUnknown* GetStopCollection() const { return mStopCollection; }
+
+ private:
+ already_AddRefed<ID2D1ResourceTexture> CreateGradientTexture();
+
+ ConicGradientEffectD2D1();
+
+ uint32_t mRefCount;
+ RefPtr<ID2D1GradientStopCollection> mStopCollection;
+ RefPtr<ID2D1EffectContext> mEffectContext;
+ RefPtr<ID2D1DrawInfo> mDrawInfo;
+ SIMPLE_PROP(D2D1_VECTOR_2F, Center);
+ SIMPLE_PROP(FLOAT, Angle);
+ SIMPLE_PROP(FLOAT, StartOffset);
+ SIMPLE_PROP(FLOAT, EndOffset);
+ SIMPLE_PROP(D2D_MATRIX_3X2_F, Transform);
+};
+
+} // namespace gfx
+} // namespace mozilla
+#undef SIMPLE_PROP
+
+#endif
diff --git a/gfx/2d/ConvolutionFilter.cpp b/gfx/2d/ConvolutionFilter.cpp
new file mode 100644
index 0000000000..31ace83dc7
--- /dev/null
+++ b/gfx/2d/ConvolutionFilter.cpp
@@ -0,0 +1,114 @@
+/* -*- 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 "ConvolutionFilter.h"
+#include "HelpersSkia.h"
+#include "SkConvolver.h"
+#include "skia/include/core/SkBitmap.h"
+
+namespace mozilla::gfx {
+
+ConvolutionFilter::ConvolutionFilter()
+ : mFilter(MakeUnique<skia::SkConvolutionFilter1D>()) {}
+
+ConvolutionFilter::~ConvolutionFilter() = default;
+
+int32_t ConvolutionFilter::MaxFilter() const { return mFilter->maxFilter(); }
+
+int32_t ConvolutionFilter::NumValues() const { return mFilter->numValues(); }
+
+bool ConvolutionFilter::GetFilterOffsetAndLength(int32_t aRowIndex,
+ int32_t* aResultOffset,
+ int32_t* aResultLength) {
+ if (aRowIndex >= mFilter->numValues()) {
+ return false;
+ }
+ mFilter->FilterForValue(aRowIndex, aResultOffset, aResultLength);
+ return true;
+}
+
+void ConvolutionFilter::ConvolveHorizontally(const uint8_t* aSrc, uint8_t* aDst,
+ bool aHasAlpha) {
+ skia::convolve_horizontally(aSrc, *mFilter, aDst, aHasAlpha);
+}
+
+void ConvolutionFilter::ConvolveVertically(uint8_t* const* aSrc, uint8_t* aDst,
+ int32_t aRowIndex, int32_t aRowSize,
+ bool aHasAlpha) {
+ MOZ_ASSERT(aRowIndex < mFilter->numValues());
+
+ int32_t filterOffset;
+ int32_t filterLength;
+ auto filterValues =
+ mFilter->FilterForValue(aRowIndex, &filterOffset, &filterLength);
+ skia::convolve_vertically(filterValues, filterLength, aSrc, aRowSize, aDst,
+ aHasAlpha);
+}
+
+bool ConvolutionFilter::ComputeResizeFilter(ResizeMethod aResizeMethod,
+ int32_t aSrcSize,
+ int32_t aDstSize) {
+ if (aSrcSize < 0 || aDstSize < 0) {
+ return false;
+ }
+
+ switch (aResizeMethod) {
+ case ResizeMethod::BOX:
+ return mFilter->ComputeFilterValues(skia::SkBoxFilter(), aSrcSize,
+ aDstSize);
+ case ResizeMethod::LANCZOS3:
+ return mFilter->ComputeFilterValues(skia::SkLanczosFilter(), aSrcSize,
+ aDstSize);
+ default:
+ return false;
+ }
+}
+
+bool Scale(uint8_t* srcData, int32_t srcWidth, int32_t srcHeight,
+ int32_t srcStride, uint8_t* dstData, int32_t dstWidth,
+ int32_t dstHeight, int32_t dstStride, SurfaceFormat format) {
+ if (!srcData || !dstData || srcWidth < 1 || srcHeight < 1 || dstWidth < 1 ||
+ dstHeight < 1) {
+ return false;
+ }
+
+ SkPixmap srcPixmap(MakeSkiaImageInfo(IntSize(srcWidth, srcHeight), format),
+ srcData, srcStride);
+
+ // Rescaler is compatible with N32 only. Convert to N32 if needed.
+ SkBitmap tmpBitmap;
+ if (srcPixmap.colorType() != kN32_SkColorType) {
+ if (!tmpBitmap.tryAllocPixels(
+ SkImageInfo::MakeN32Premul(srcWidth, srcHeight)) ||
+ !tmpBitmap.writePixels(srcPixmap) ||
+ !tmpBitmap.peekPixels(&srcPixmap)) {
+ return false;
+ }
+ }
+
+ ConvolutionFilter xFilter;
+ ConvolutionFilter yFilter;
+ ConvolutionFilter* xOrYFilter = &xFilter;
+ bool isSquare = srcWidth == srcHeight && dstWidth == dstHeight;
+ if (!xFilter.ComputeResizeFilter(ConvolutionFilter::ResizeMethod::LANCZOS3,
+ srcWidth, dstWidth)) {
+ return false;
+ }
+ if (!isSquare) {
+ if (!yFilter.ComputeResizeFilter(ConvolutionFilter::ResizeMethod::LANCZOS3,
+ srcHeight, dstHeight)) {
+ return false;
+ }
+ xOrYFilter = &yFilter;
+ }
+
+ return skia::BGRAConvolve2D(
+ static_cast<const uint8_t*>(srcPixmap.addr()), int(srcPixmap.rowBytes()),
+ !srcPixmap.isOpaque(), xFilter.GetSkiaFilter(),
+ xOrYFilter->GetSkiaFilter(), int(dstStride), dstData);
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/ConvolutionFilter.h b/gfx/2d/ConvolutionFilter.h
new file mode 100644
index 0000000000..282222b8d6
--- /dev/null
+++ b/gfx/2d/ConvolutionFilter.h
@@ -0,0 +1,54 @@
+/* -*- 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_CONVOLUTION_FILTER_H_
+#define MOZILLA_GFX_CONVOLUTION_FILTER_H_
+
+#include "mozilla/UniquePtr.h"
+
+namespace skia {
+class SkConvolutionFilter1D;
+}
+
+namespace mozilla {
+namespace gfx {
+
+class ConvolutionFilter final {
+ public:
+ ConvolutionFilter();
+ ~ConvolutionFilter();
+
+ int32_t MaxFilter() const;
+ int32_t NumValues() const;
+
+ bool GetFilterOffsetAndLength(int32_t aRowIndex, int32_t* aResultOffset,
+ int32_t* aResultLength);
+
+ void ConvolveHorizontally(const uint8_t* aSrc, uint8_t* aDst, bool aHasAlpha);
+ void ConvolveVertically(uint8_t* const* aSrc, uint8_t* aDst,
+ int32_t aRowIndex, int32_t aRowSize, bool aHasAlpha);
+
+ enum class ResizeMethod { BOX, LANCZOS3 };
+
+ bool ComputeResizeFilter(ResizeMethod aResizeMethod, int32_t aSrcSize,
+ int32_t aDstSize);
+
+ static inline size_t PadBytesForSIMD(size_t aBytes) {
+ return (aBytes + 31) & ~31;
+ }
+
+ const skia::SkConvolutionFilter1D& GetSkiaFilter() const {
+ return *mFilter.get();
+ }
+
+ private:
+ UniquePtr<skia::SkConvolutionFilter1D> mFilter;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_CONVOLUTION_FILTER_H_ */
diff --git a/gfx/2d/ConvolutionFilterAVX2.cpp b/gfx/2d/ConvolutionFilterAVX2.cpp
new file mode 100644
index 0000000000..633e95b830
--- /dev/null
+++ b/gfx/2d/ConvolutionFilterAVX2.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+// Copyright (c) 2011-2016 Google Inc.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the gfx/skia/LICENSE file.
+
+#include "SkConvolver.h"
+#include <immintrin.h>
+
+namespace skia {
+
+void convolve_vertically_avx2(
+ const SkConvolutionFilter1D::ConvolutionFixed* filter, int filterLen,
+ unsigned char* const* srcRows, int width, unsigned char* out,
+ bool hasAlpha) {
+ // It's simpler to work with the output array in terms of 4-byte pixels.
+ auto* dst = (int*)out;
+
+ // Output up to eight pixels per iteration.
+ for (int x = 0; x < width; x += 8) {
+ // Accumulated result for 4 (non-adjacent) pairs of pixels,
+ // with each channel in signed 17.14 fixed point.
+ auto accum04 = _mm256_setzero_si256(), accum15 = _mm256_setzero_si256(),
+ accum26 = _mm256_setzero_si256(), accum37 = _mm256_setzero_si256();
+
+ // Convolve with the filter. (This inner loop is where we spend ~all our
+ // time.) While we can, we consume 2 filter coefficients and 2 rows of 8
+ // pixels each at a time.
+ auto convolve_16_pixels = [&](__m256i interlaced_coeffs,
+ __m256i pixels_01234567,
+ __m256i pixels_89ABCDEF) {
+ // Interlaced R0R8 G0G8 B0B8 A0A8 R1R9 G1G9... 32 8-bit values each.
+ auto _08194C5D = _mm256_unpacklo_epi8(pixels_01234567, pixels_89ABCDEF),
+ _2A3B6E7F = _mm256_unpackhi_epi8(pixels_01234567, pixels_89ABCDEF);
+
+ // Still interlaced R0R8 G0G8... as above, each channel expanded to 16-bit
+ // lanes.
+ auto _084C = _mm256_unpacklo_epi8(_08194C5D, _mm256_setzero_si256()),
+ _195D = _mm256_unpackhi_epi8(_08194C5D, _mm256_setzero_si256()),
+ _2A6E = _mm256_unpacklo_epi8(_2A3B6E7F, _mm256_setzero_si256()),
+ _3B7F = _mm256_unpackhi_epi8(_2A3B6E7F, _mm256_setzero_si256());
+
+ // accum0_R += R0*coeff0 + R8*coeff1, etc.
+ accum04 = _mm256_add_epi32(accum04,
+ _mm256_madd_epi16(_084C, interlaced_coeffs));
+ accum15 = _mm256_add_epi32(accum15,
+ _mm256_madd_epi16(_195D, interlaced_coeffs));
+ accum26 = _mm256_add_epi32(accum26,
+ _mm256_madd_epi16(_2A6E, interlaced_coeffs));
+ accum37 = _mm256_add_epi32(accum37,
+ _mm256_madd_epi16(_3B7F, interlaced_coeffs));
+ };
+
+ int i = 0;
+ for (; i < filterLen / 2 * 2; i += 2) {
+ convolve_16_pixels(
+ _mm256_set1_epi32(*(const int32_t*)(filter + i)),
+ _mm256_loadu_si256((const __m256i*)(srcRows[i + 0] + x * 4)),
+ _mm256_loadu_si256((const __m256i*)(srcRows[i + 1] + x * 4)));
+ }
+ if (i < filterLen) {
+ convolve_16_pixels(
+ _mm256_set1_epi32(*(const int16_t*)(filter + i)),
+ _mm256_loadu_si256((const __m256i*)(srcRows[i] + x * 4)),
+ _mm256_setzero_si256());
+ }
+
+ // Trim the fractional parts off the accumulators.
+ accum04 = _mm256_srai_epi32(accum04, 14);
+ accum15 = _mm256_srai_epi32(accum15, 14);
+ accum26 = _mm256_srai_epi32(accum26, 14);
+ accum37 = _mm256_srai_epi32(accum37, 14);
+
+ // Pack back down to 8-bit channels.
+ auto pixels = _mm256_packus_epi16(_mm256_packs_epi32(accum04, accum15),
+ _mm256_packs_epi32(accum26, accum37));
+
+ if (hasAlpha) {
+ // Clamp alpha to the max of r,g,b to make sure we stay premultiplied.
+ __m256i max_rg = _mm256_max_epu8(pixels, _mm256_srli_epi32(pixels, 8)),
+ max_rgb = _mm256_max_epu8(max_rg, _mm256_srli_epi32(pixels, 16));
+ pixels = _mm256_max_epu8(pixels, _mm256_slli_epi32(max_rgb, 24));
+ } else {
+ // Force opaque.
+ pixels = _mm256_or_si256(pixels, _mm256_set1_epi32(0xff000000));
+ }
+
+ // Normal path to store 8 pixels.
+ if (x + 8 <= width) {
+ _mm256_storeu_si256((__m256i*)dst, pixels);
+ dst += 8;
+ continue;
+ }
+
+ // Store one pixel at a time on the last iteration.
+ for (int i = x; i < width; i++) {
+ *dst++ = _mm_cvtsi128_si32(_mm256_castsi256_si128(pixels));
+ pixels = _mm256_permutevar8x32_epi32(
+ pixels, _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 0));
+ }
+ }
+}
+
+} // namespace skia
diff --git a/gfx/2d/ConvolutionFilterNEON.cpp b/gfx/2d/ConvolutionFilterNEON.cpp
new file mode 100644
index 0000000000..9983a0681a
--- /dev/null
+++ b/gfx/2d/ConvolutionFilterNEON.cpp
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+// Copyright (c) 2011-2016 Google Inc.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the gfx/skia/LICENSE file.
+
+#include "SkConvolver.h"
+#include "mozilla/Attributes.h"
+#include <arm_neon.h>
+
+namespace skia {
+
+static MOZ_ALWAYS_INLINE void AccumRemainder(
+ const unsigned char* pixelsLeft,
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int32x4_t& accum, int r) {
+ int remainder[4] = {0};
+ for (int i = 0; i < r; i++) {
+ SkConvolutionFilter1D::ConvolutionFixed coeff = filterValues[i];
+ remainder[0] += coeff * pixelsLeft[i * 4 + 0];
+ remainder[1] += coeff * pixelsLeft[i * 4 + 1];
+ remainder[2] += coeff * pixelsLeft[i * 4 + 2];
+ remainder[3] += coeff * pixelsLeft[i * 4 + 3];
+ }
+ int32x4_t t = {remainder[0], remainder[1], remainder[2], remainder[3]};
+ accum += t;
+}
+
+// Convolves horizontally along a single row. The row data is given in
+// |srcData| and continues for the numValues() of the filter.
+void convolve_horizontally_neon(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow, bool /*hasAlpha*/) {
+ // Loop over each pixel on this row in the output image.
+ int numValues = filter.numValues();
+ for (int outX = 0; outX < numValues; outX++) {
+ uint8x8_t coeff_mask0 = vcreate_u8(0x0100010001000100);
+ uint8x8_t coeff_mask1 = vcreate_u8(0x0302030203020302);
+ uint8x8_t coeff_mask2 = vcreate_u8(0x0504050405040504);
+ uint8x8_t coeff_mask3 = vcreate_u8(0x0706070607060706);
+ // Get the filter that determines the current output pixel.
+ int filterOffset, filterLength;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filter.FilterForValue(outX, &filterOffset, &filterLength);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filterLength| pixels (4 bytes each) after this.
+ const unsigned char* rowToFilter = &srcData[filterOffset * 4];
+
+ // Apply the filter to the row to get the destination pixel in |accum|.
+ int32x4_t accum = vdupq_n_s32(0);
+ for (int filterX = 0; filterX < filterLength >> 2; filterX++) {
+ // Load 4 coefficients
+ int16x4_t coeffs, coeff0, coeff1, coeff2, coeff3;
+ coeffs = vld1_s16(filterValues);
+ coeff0 = vreinterpret_s16_u8(
+ vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask0));
+ coeff1 = vreinterpret_s16_u8(
+ vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask1));
+ coeff2 = vreinterpret_s16_u8(
+ vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask2));
+ coeff3 = vreinterpret_s16_u8(
+ vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask3));
+
+ // Load pixels and calc
+ uint8x16_t pixels = vld1q_u8(rowToFilter);
+ int16x8_t p01_16 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(pixels)));
+ int16x8_t p23_16 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(pixels)));
+
+ int16x4_t p0_src = vget_low_s16(p01_16);
+ int16x4_t p1_src = vget_high_s16(p01_16);
+ int16x4_t p2_src = vget_low_s16(p23_16);
+ int16x4_t p3_src = vget_high_s16(p23_16);
+
+ int32x4_t p0 = vmull_s16(p0_src, coeff0);
+ int32x4_t p1 = vmull_s16(p1_src, coeff1);
+ int32x4_t p2 = vmull_s16(p2_src, coeff2);
+ int32x4_t p3 = vmull_s16(p3_src, coeff3);
+
+ accum += p0;
+ accum += p1;
+ accum += p2;
+ accum += p3;
+
+ // Advance the pointers
+ rowToFilter += 16;
+ filterValues += 4;
+ }
+
+ int r = filterLength & 3;
+ if (r) {
+ int remainder_offset = (filterOffset + filterLength - r) * 4;
+ AccumRemainder(srcData + remainder_offset, filterValues, accum, r);
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of fractional part.
+ accum = vshrq_n_s32(accum, SkConvolutionFilter1D::kShiftBits);
+
+ // Pack and store the new pixel.
+ int16x4_t accum16 = vqmovn_s32(accum);
+ uint8x8_t accum8 = vqmovun_s16(vcombine_s16(accum16, accum16));
+ vst1_lane_u32(reinterpret_cast<uint32_t*>(outRow),
+ vreinterpret_u32_u8(accum8), 0);
+ outRow += 4;
+ }
+}
+
+// Does vertical convolution to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |sourceDataRows| array, with each row
+// being |pixelWidth| wide.
+//
+// The output must have room for |pixelWidth * 4| bytes.
+template <bool hasAlpha>
+static void ConvolveVertically(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow) {
+ int width = pixelWidth & ~3;
+
+ // Output four pixels per iteration (16 bytes).
+ for (int outX = 0; outX < width; outX += 4) {
+ // Accumulated result for each pixel. 32 bits per RGBA channel.
+ int32x4_t accum0 = vdupq_n_s32(0);
+ int32x4_t accum1 = vdupq_n_s32(0);
+ int32x4_t accum2 = vdupq_n_s32(0);
+ int32x4_t accum3 = vdupq_n_s32(0);
+
+ // Convolve with one filter coefficient per iteration.
+ for (int filterY = 0; filterY < filterLength; filterY++) {
+ // Duplicate the filter coefficient 4 times.
+ // [16] cj cj cj cj
+ int16x4_t coeff16 = vdup_n_s16(filterValues[filterY]);
+
+ // Load four pixels (16 bytes) together.
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ uint8x16_t src8 = vld1q_u8(&sourceDataRows[filterY][outX << 2]);
+
+ int16x8_t src16_01 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(src8)));
+ int16x8_t src16_23 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(src8)));
+ int16x4_t src16_0 = vget_low_s16(src16_01);
+ int16x4_t src16_1 = vget_high_s16(src16_01);
+ int16x4_t src16_2 = vget_low_s16(src16_23);
+ int16x4_t src16_3 = vget_high_s16(src16_23);
+
+ accum0 += vmull_s16(src16_0, coeff16);
+ accum1 += vmull_s16(src16_1, coeff16);
+ accum2 += vmull_s16(src16_2, coeff16);
+ accum3 += vmull_s16(src16_3, coeff16);
+ }
+
+ // Shift right for fixed point implementation.
+ accum0 = vshrq_n_s32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum1 = vshrq_n_s32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum2 = vshrq_n_s32(accum2, SkConvolutionFilter1D::kShiftBits);
+ accum3 = vshrq_n_s32(accum3, SkConvolutionFilter1D::kShiftBits);
+
+ // Packing 32 bits |accum| to 16 bits per channel (signed saturation).
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ int16x8_t accum16_0 = vcombine_s16(vqmovn_s32(accum0), vqmovn_s32(accum1));
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ int16x8_t accum16_1 = vcombine_s16(vqmovn_s32(accum2), vqmovn_s32(accum3));
+
+ // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ uint8x16_t accum8 =
+ vcombine_u8(vqmovun_s16(accum16_0), vqmovun_s16(accum16_1));
+
+ if (hasAlpha) {
+ // Compute the max(ri, gi, bi) for each pixel.
+ // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
+ uint8x16_t a =
+ vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 8));
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ uint8x16_t b = vmaxq_u8(a, accum8); // Max of r and g
+ // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
+ a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 16));
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ b = vmaxq_u8(a, b); // Max of r and g and b.
+ // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
+ b = vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(b), 24));
+
+ // Make sure the value of alpha channel is always larger than maximum
+ // value of color channels.
+ accum8 = vmaxq_u8(b, accum8);
+ } else {
+ // Set value of alpha channels to 0xFF.
+ accum8 = vreinterpretq_u8_u32(vreinterpretq_u32_u8(accum8) |
+ vdupq_n_u32(0xFF000000));
+ }
+
+ // Store the convolution result (16 bytes) and advance the pixel pointers.
+ vst1q_u8(outRow, accum8);
+ outRow += 16;
+ }
+
+ // Process the leftovers when the width of the output is not divisible
+ // by 4, that is at most 3 pixels.
+ int r = pixelWidth & 3;
+ if (r) {
+ int32x4_t accum0 = vdupq_n_s32(0);
+ int32x4_t accum1 = vdupq_n_s32(0);
+ int32x4_t accum2 = vdupq_n_s32(0);
+
+ for (int filterY = 0; filterY < filterLength; ++filterY) {
+ int16x4_t coeff16 = vdup_n_s16(filterValues[filterY]);
+
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ uint8x16_t src8 = vld1q_u8(&sourceDataRows[filterY][width << 2]);
+
+ int16x8_t src16_01 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(src8)));
+ int16x8_t src16_23 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(src8)));
+ int16x4_t src16_0 = vget_low_s16(src16_01);
+ int16x4_t src16_1 = vget_high_s16(src16_01);
+ int16x4_t src16_2 = vget_low_s16(src16_23);
+
+ accum0 += vmull_s16(src16_0, coeff16);
+ accum1 += vmull_s16(src16_1, coeff16);
+ accum2 += vmull_s16(src16_2, coeff16);
+ }
+
+ accum0 = vshrq_n_s32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum1 = vshrq_n_s32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum2 = vshrq_n_s32(accum2, SkConvolutionFilter1D::kShiftBits);
+
+ int16x8_t accum16_0 = vcombine_s16(vqmovn_s32(accum0), vqmovn_s32(accum1));
+ int16x8_t accum16_1 = vcombine_s16(vqmovn_s32(accum2), vqmovn_s32(accum2));
+
+ uint8x16_t accum8 =
+ vcombine_u8(vqmovun_s16(accum16_0), vqmovun_s16(accum16_1));
+
+ if (hasAlpha) {
+ // Compute the max(ri, gi, bi) for each pixel.
+ // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
+ uint8x16_t a =
+ vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 8));
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ uint8x16_t b = vmaxq_u8(a, accum8); // Max of r and g
+ // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
+ a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 16));
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ b = vmaxq_u8(a, b); // Max of r and g and b.
+ // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
+ b = vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(b), 24));
+ // Make sure the value of alpha channel is always larger than maximum
+ // value of color channels.
+ accum8 = vmaxq_u8(b, accum8);
+ } else {
+ // Set value of alpha channels to 0xFF.
+ accum8 = vreinterpretq_u8_u32(vreinterpretq_u32_u8(accum8) |
+ vdupq_n_u32(0xFF000000));
+ }
+
+ switch (r) {
+ case 1:
+ vst1q_lane_u32(reinterpret_cast<uint32_t*>(outRow),
+ vreinterpretq_u32_u8(accum8), 0);
+ break;
+ case 2:
+ vst1_u32(reinterpret_cast<uint32_t*>(outRow),
+ vreinterpret_u32_u8(vget_low_u8(accum8)));
+ break;
+ case 3:
+ vst1_u32(reinterpret_cast<uint32_t*>(outRow),
+ vreinterpret_u32_u8(vget_low_u8(accum8)));
+ vst1q_lane_u32(reinterpret_cast<uint32_t*>(outRow + 8),
+ vreinterpretq_u32_u8(accum8), 2);
+ break;
+ }
+ }
+}
+
+void convolve_vertically_neon(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow, bool hasAlpha) {
+ if (hasAlpha) {
+ ConvolveVertically<true>(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow);
+ } else {
+ ConvolveVertically<false>(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow);
+ }
+}
+
+} // namespace skia
diff --git a/gfx/2d/ConvolutionFilterSSE2.cpp b/gfx/2d/ConvolutionFilterSSE2.cpp
new file mode 100644
index 0000000000..c0aadb2245
--- /dev/null
+++ b/gfx/2d/ConvolutionFilterSSE2.cpp
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+// Copyright (c) 2011-2016 Google Inc.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the gfx/skia/LICENSE file.
+
+#include "SkConvolver.h"
+#include "mozilla/Attributes.h"
+#include <immintrin.h>
+
+namespace skia {
+
+static MOZ_ALWAYS_INLINE void AccumRemainder(
+ const unsigned char* pixelsLeft,
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues, __m128i& accum,
+ int r) {
+ int remainder[4] = {0};
+ for (int i = 0; i < r; i++) {
+ SkConvolutionFilter1D::ConvolutionFixed coeff = filterValues[i];
+ remainder[0] += coeff * pixelsLeft[i * 4 + 0];
+ remainder[1] += coeff * pixelsLeft[i * 4 + 1];
+ remainder[2] += coeff * pixelsLeft[i * 4 + 2];
+ remainder[3] += coeff * pixelsLeft[i * 4 + 3];
+ }
+ __m128i t =
+ _mm_setr_epi32(remainder[0], remainder[1], remainder[2], remainder[3]);
+ accum = _mm_add_epi32(accum, t);
+}
+
+// Convolves horizontally along a single row. The row data is given in
+// |srcData| and continues for the numValues() of the filter.
+void convolve_horizontally_sse2(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow, bool /*hasAlpha*/) {
+ // Output one pixel each iteration, calculating all channels (RGBA) together.
+ int numValues = filter.numValues();
+ for (int outX = 0; outX < numValues; outX++) {
+ // Get the filter that determines the current output pixel.
+ int filterOffset, filterLength;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filter.FilterForValue(outX, &filterOffset, &filterLength);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filterLength| pixels (4 bytes each) after this.
+ const unsigned char* rowToFilter = &srcData[filterOffset * 4];
+
+ __m128i zero = _mm_setzero_si128();
+ __m128i accum = _mm_setzero_si128();
+
+ // We will load and accumulate with four coefficients per iteration.
+ for (int filterX = 0; filterX < filterLength >> 2; filterX++) {
+ // Load 4 coefficients => duplicate 1st and 2nd of them for all channels.
+ __m128i coeff, coeff16;
+ // [16] xx xx xx xx c3 c2 c1 c0
+ coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filterValues));
+ // [16] xx xx xx xx c1 c1 c0 c0
+ coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
+ // [16] c1 c1 c1 c1 c0 c0 c0 c0
+ coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
+
+ // Load four pixels => unpack the first two pixels to 16 bits =>
+ // multiply with coefficients => accumulate the convolution result.
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src8 =
+ _mm_loadu_si128(reinterpret_cast<const __m128i*>(rowToFilter));
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a0*c0 b0*c0 g0*c0 r0*c0
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+ // [32] a1*c1 b1*c1 g1*c1 r1*c1
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+
+ // Duplicate 3rd and 4th coefficients for all channels =>
+ // unpack the 3rd and 4th pixels to 16 bits => multiply with coefficients
+ // => accumulate the convolution results.
+ // [16] xx xx xx xx c3 c3 c2 c2
+ coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
+ // [16] c3 c3 c3 c3 c2 c2 c2 c2
+ coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
+ // [16] a3 g3 b3 r3 a2 g2 b2 r2
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a2*c2 b2*c2 g2*c2 r2*c2
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+ // [32] a3*c3 b3*c3 g3*c3 r3*c3
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+
+ // Advance the pixel and coefficients pointers.
+ rowToFilter += 16;
+ filterValues += 4;
+ }
+
+ // When |filterLength| is not divisible by 4, we accumulate the last 1 - 3
+ // coefficients one at a time.
+ int r = filterLength & 3;
+ if (r) {
+ int remainderOffset = (filterOffset + filterLength - r) * 4;
+ AccumRemainder(srcData + remainderOffset, filterValues, accum, r);
+ }
+
+ // Shift right for fixed point implementation.
+ accum = _mm_srai_epi32(accum, SkConvolutionFilter1D::kShiftBits);
+
+ // Packing 32 bits |accum| to 16 bits per channel (signed saturation).
+ accum = _mm_packs_epi32(accum, zero);
+ // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
+ accum = _mm_packus_epi16(accum, zero);
+
+ // Store the pixel value of 32 bits.
+ *(reinterpret_cast<int*>(outRow)) = _mm_cvtsi128_si32(accum);
+ outRow += 4;
+ }
+}
+
+// Does vertical convolution to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |sourceDataRows| array, with each row
+// being |pixelWidth| wide.
+//
+// The output must have room for |pixelWidth * 4| bytes.
+template <bool hasAlpha>
+static void ConvolveVertically(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow) {
+ // Output four pixels per iteration (16 bytes).
+ int width = pixelWidth & ~3;
+ __m128i zero = _mm_setzero_si128();
+ for (int outX = 0; outX < width; outX += 4) {
+ // Accumulated result for each pixel. 32 bits per RGBA channel.
+ __m128i accum0 = _mm_setzero_si128();
+ __m128i accum1 = _mm_setzero_si128();
+ __m128i accum2 = _mm_setzero_si128();
+ __m128i accum3 = _mm_setzero_si128();
+
+ // Convolve with one filter coefficient per iteration.
+ for (int filterY = 0; filterY < filterLength; filterY++) {
+ // Duplicate the filter coefficient 8 times.
+ // [16] cj cj cj cj cj cj cj cj
+ __m128i coeff16 = _mm_set1_epi16(filterValues[filterY]);
+
+ // Load four pixels (16 bytes) together.
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ const __m128i* src =
+ reinterpret_cast<const __m128i*>(&sourceDataRows[filterY][outX << 2]);
+ __m128i src8 = _mm_loadu_si128(src);
+
+ // Unpack 1st and 2nd pixels from 8 bits to 16 bits for each channels =>
+ // multiply with current coefficient => accumulate the result.
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a0 b0 g0 r0
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum0 = _mm_add_epi32(accum0, t);
+ // [32] a1 b1 g1 r1
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum1 = _mm_add_epi32(accum1, t);
+
+ // Unpack 3rd and 4th pixels from 8 bits to 16 bits for each channels =>
+ // multiply with current coefficient => accumulate the result.
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a2 b2 g2 r2
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum2 = _mm_add_epi32(accum2, t);
+ // [32] a3 b3 g3 r3
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum3 = _mm_add_epi32(accum3, t);
+ }
+
+ // Shift right for fixed point implementation.
+ accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits);
+ accum3 = _mm_srai_epi32(accum3, SkConvolutionFilter1D::kShiftBits);
+
+ // Packing 32 bits |accum| to 16 bits per channel (signed saturation).
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packs_epi32(accum0, accum1);
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ accum2 = _mm_packs_epi32(accum2, accum3);
+
+ // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packus_epi16(accum0, accum2);
+
+ if (hasAlpha) {
+ // Compute the max(ri, gi, bi) for each pixel.
+ // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
+ __m128i a = _mm_srli_epi32(accum0, 8);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ __m128i b = _mm_max_epu8(a, accum0); // Max of r and g.
+ // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
+ a = _mm_srli_epi32(accum0, 16);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ b = _mm_max_epu8(a, b); // Max of r and g and b.
+ // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
+ b = _mm_slli_epi32(b, 24);
+
+ // Make sure the value of alpha channel is always larger than maximum
+ // value of color channels.
+ accum0 = _mm_max_epu8(b, accum0);
+ } else {
+ // Set value of alpha channels to 0xFF.
+ __m128i mask = _mm_set1_epi32(0xff000000);
+ accum0 = _mm_or_si128(accum0, mask);
+ }
+
+ // Store the convolution result (16 bytes) and advance the pixel pointers.
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(outRow), accum0);
+ outRow += 16;
+ }
+
+ // When the width of the output is not divisible by 4, We need to save one
+ // pixel (4 bytes) each time. And also the fourth pixel is always absent.
+ int r = pixelWidth & 3;
+ if (r) {
+ __m128i accum0 = _mm_setzero_si128();
+ __m128i accum1 = _mm_setzero_si128();
+ __m128i accum2 = _mm_setzero_si128();
+ for (int filterY = 0; filterY < filterLength; ++filterY) {
+ __m128i coeff16 = _mm_set1_epi16(filterValues[filterY]);
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ const __m128i* src = reinterpret_cast<const __m128i*>(
+ &sourceDataRows[filterY][width << 2]);
+ __m128i src8 = _mm_loadu_si128(src);
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a0 b0 g0 r0
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum0 = _mm_add_epi32(accum0, t);
+ // [32] a1 b1 g1 r1
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum1 = _mm_add_epi32(accum1, t);
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a2 b2 g2 r2
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum2 = _mm_add_epi32(accum2, t);
+ }
+
+ accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits);
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packs_epi32(accum0, accum1);
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ accum2 = _mm_packs_epi32(accum2, zero);
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packus_epi16(accum0, accum2);
+ if (hasAlpha) {
+ // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
+ __m128i a = _mm_srli_epi32(accum0, 8);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ __m128i b = _mm_max_epu8(a, accum0); // Max of r and g.
+ // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
+ a = _mm_srli_epi32(accum0, 16);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ b = _mm_max_epu8(a, b); // Max of r and g and b.
+ // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
+ b = _mm_slli_epi32(b, 24);
+ accum0 = _mm_max_epu8(b, accum0);
+ } else {
+ __m128i mask = _mm_set1_epi32(0xff000000);
+ accum0 = _mm_or_si128(accum0, mask);
+ }
+
+ for (int i = 0; i < r; i++) {
+ *(reinterpret_cast<int*>(outRow)) = _mm_cvtsi128_si32(accum0);
+ accum0 = _mm_srli_si128(accum0, 4);
+ outRow += 4;
+ }
+ }
+}
+
+void convolve_vertically_sse2(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow, bool hasAlpha) {
+ if (hasAlpha) {
+ ConvolveVertically<true>(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow);
+ } else {
+ ConvolveVertically<false>(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow);
+ }
+}
+
+} // namespace skia
diff --git a/gfx/2d/Coord.h b/gfx/2d/Coord.h
new file mode 100644
index 0000000000..643cf4969a
--- /dev/null
+++ b/gfx/2d/Coord.h
@@ -0,0 +1,176 @@
+/* -*- 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_COORD_H_
+#define MOZILLA_GFX_COORD_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/FloatingPoint.h"
+#include "Types.h"
+#include "BaseCoord.h"
+
+#include <cmath>
+#include <type_traits>
+
+namespace mozilla {
+
+template <typename>
+struct IsPixel;
+
+namespace gfx {
+
+// Should only be used to define generic typedefs like Coord, Point, etc.
+struct UnknownUnits {};
+
+template <class Units, class Rep = int32_t>
+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 Rep, class Primitive>
+struct CommonType<IntCoordTyped<Units, Rep>, Primitive> {
+ using type = decltype(Rep() + Primitive());
+};
+
+template <class Units, class F, class Primitive>
+struct CommonType<CoordTyped<Units, F>, Primitive> {
+ using type = decltype(F() + Primitive());
+};
+
+// 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; }
+
+ using result_type = typename CommonType<Coord, Primitive>::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.
+};
+
+template <class Units, class Rep>
+struct MOZ_EMPTY_BASES IntCoordTyped
+ : public BaseCoord<Rep, IntCoordTyped<Units, Rep>>,
+ public CoordOperatorsHelper<true, IntCoordTyped<Units, Rep>, float>,
+ public CoordOperatorsHelper<true, IntCoordTyped<Units, Rep>, double> {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ using Super = BaseCoord<Rep, IntCoordTyped<Units, Rep>>;
+
+ constexpr IntCoordTyped() : Super() {
+ static_assert(sizeof(IntCoordTyped) == sizeof(Rep),
+ "Would be unfortunate otherwise!");
+ }
+ constexpr MOZ_IMPLICIT IntCoordTyped(Rep aValue) : Super(aValue) {
+ static_assert(sizeof(IntCoordTyped) == sizeof(Rep),
+ "Would be unfortunate otherwise!");
+ }
+};
+
+template <class Units, class F>
+struct MOZ_EMPTY_BASES CoordTyped
+ : public BaseCoord<F, CoordTyped<Units, F>>,
+ public CoordOperatorsHelper<!std::is_same_v<F, int32_t>,
+ CoordTyped<Units, F>, int32_t>,
+ public CoordOperatorsHelper<!std::is_same_v<F, uint32_t>,
+ CoordTyped<Units, F>, uint32_t>,
+ public CoordOperatorsHelper<!std::is_same_v<F, double>,
+ CoordTyped<Units, F>, double>,
+ public CoordOperatorsHelper<!std::is_same_v<F, float>,
+ CoordTyped<Units, F>, float> {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ using Super = BaseCoord<F, CoordTyped<Units, F>>;
+
+ constexpr CoordTyped() : Super() {
+ static_assert(sizeof(CoordTyped) == sizeof(F),
+ "Would be unfortunate otherwise!");
+ }
+ constexpr MOZ_IMPLICIT CoordTyped(F aValue) : Super(aValue) {
+ static_assert(sizeof(CoordTyped) == sizeof(F),
+ "Would be unfortunate otherwise!");
+ }
+ explicit constexpr CoordTyped(const IntCoordTyped<Units>& aCoord)
+ : Super(F(aCoord.value)) {
+ static_assert(sizeof(CoordTyped) == sizeof(F),
+ "Would be unfortunate otherwise!");
+ }
+
+ 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));
+ }
+};
+
+typedef CoordTyped<UnknownUnits> Coord;
+
+} // namespace gfx
+
+template <class Units, class F>
+static MOZ_ALWAYS_INLINE bool FuzzyEqualsAdditive(
+ gfx::CoordTyped<Units, F> aValue1, gfx::CoordTyped<Units, F> aValue2,
+ gfx::CoordTyped<Units, F> aEpsilon =
+ detail::FuzzyEqualsEpsilon<F>::value()) {
+ return FuzzyEqualsAdditive(aValue1.value, aValue2.value, aEpsilon.value);
+}
+
+template <class Units, class F>
+static MOZ_ALWAYS_INLINE bool FuzzyEqualsMultiplicative(
+ gfx::CoordTyped<Units, F> aValue1, gfx::CoordTyped<Units, F> aValue2,
+ gfx::CoordTyped<Units, F> aEpsilon =
+ detail::FuzzyEqualsEpsilon<F>::value()) {
+ return FuzzyEqualsMultiplicative(aValue1.value, aValue2.value,
+ aEpsilon.value);
+}
+
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_COORD_H_ */
diff --git a/gfx/2d/CriticalSection.h b/gfx/2d/CriticalSection.h
new file mode 100644
index 0000000000..4ecb9d26e8
--- /dev/null
+++ b/gfx/2d/CriticalSection.h
@@ -0,0 +1,84 @@
+/* -*- 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_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 final {
+ explicit CriticalSectionAutoEnter(CriticalSection* aSection)
+ : mSection(aSection) {
+ mSection->Enter();
+ }
+ ~CriticalSectionAutoEnter() { mSection->Leave(); }
+
+ protected:
+ CriticalSection* mSection;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/DWriteSettings.cpp b/gfx/2d/DWriteSettings.cpp
new file mode 100644
index 0000000000..acd26b4e5b
--- /dev/null
+++ b/gfx/2d/DWriteSettings.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */
+
+#include "DWriteSettings.h"
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static std::atomic<Float> sClearTypeLevel{1.0f};
+static std::atomic<Float> sEnhancedContrast{1.0f};
+static std::atomic<Float> sGamma{2.2f};
+static Atomic<DWRITE_PIXEL_GEOMETRY> sPixelGeometry;
+static Atomic<DWRITE_RENDERING_MODE> sRenderingMode;
+static Atomic<DWRITE_MEASURING_MODE> sMeasuringMode;
+static std::atomic<Float> sGDIGamma{1.4f};
+StaticDataMutex<StaticRefPtr<IDWriteRenderingParams>> sStandardRenderingParams(
+ "StandardRenderingParams");
+StaticDataMutex<StaticRefPtr<IDWriteRenderingParams>> sGDIRenderingParams(
+ "GDIRenderingParams");
+
+static void ClearStandardRenderingParams() {
+ auto lockedParams = sStandardRenderingParams.Lock();
+ lockedParams.ref() = nullptr;
+}
+
+static void ClearGDIRenderingParams() {
+ auto lockedParams = sGDIRenderingParams.Lock();
+ lockedParams.ref() = nullptr;
+}
+
+static void UpdateClearTypeLevel() {
+ sClearTypeLevel = gfxVars::SystemTextClearTypeLevel();
+}
+static void ClearTypeLevelVarUpdated() {
+ UpdateClearTypeLevel();
+ ClearStandardRenderingParams();
+ ClearGDIRenderingParams();
+}
+
+static void UpdateEnhancedContrast() {
+ sEnhancedContrast = gfxVars::SystemTextEnhancedContrast();
+}
+static void EnhancedContrastVarUpdated() {
+ UpdateEnhancedContrast();
+ ClearStandardRenderingParams();
+}
+
+static void UpdateGamma() { sGamma = gfxVars::SystemTextGamma(); }
+static void GammaVarUpdated() {
+ UpdateGamma();
+ ClearStandardRenderingParams();
+}
+
+static void UpdateGDIGamma() { sGDIGamma = gfxVars::SystemGDIGamma(); }
+static void GDIGammaVarUpdated() {
+ UpdateGDIGamma();
+ ClearGDIRenderingParams();
+}
+
+static void UpdatePixelGeometry() {
+ sPixelGeometry =
+ static_cast<DWRITE_PIXEL_GEOMETRY>(gfxVars::SystemTextPixelGeometry());
+ Factory::SetBGRSubpixelOrder(sPixelGeometry == DWRITE_PIXEL_GEOMETRY_BGR);
+}
+static void PixelGeometryVarUpdated() {
+ UpdatePixelGeometry();
+ ClearStandardRenderingParams();
+ ClearGDIRenderingParams();
+}
+
+static void UpdateRenderingMode() {
+ sRenderingMode =
+ static_cast<DWRITE_RENDERING_MODE>(gfxVars::SystemTextRenderingMode());
+ switch (sRenderingMode) {
+ case DWRITE_RENDERING_MODE_ALIASED:
+ case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC:
+ sMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
+ break;
+ case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL:
+ sMeasuringMode = DWRITE_MEASURING_MODE_GDI_NATURAL;
+ break;
+ default:
+ sMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
+ break;
+ }
+}
+static void RenderingModeVarUpdated() {
+ UpdateRenderingMode();
+ ClearStandardRenderingParams();
+}
+
+DWriteSettings::DWriteSettings(bool aUseGDISettings)
+ : mUseGDISettings(aUseGDISettings) {}
+
+/* static */
+void DWriteSettings::Initialize() {
+ UpdateClearTypeLevel();
+ gfxVars::SetSystemTextClearTypeLevelListener(ClearTypeLevelVarUpdated);
+
+ UpdateEnhancedContrast();
+ gfxVars::SetSystemTextEnhancedContrastListener(EnhancedContrastVarUpdated);
+
+ UpdateGamma();
+ gfxVars::SetSystemTextGammaListener(GammaVarUpdated);
+
+ UpdateGDIGamma();
+ gfxVars::SetSystemGDIGammaListener(GDIGammaVarUpdated);
+
+ UpdateRenderingMode();
+ gfxVars::SetSystemTextRenderingModeListener(RenderingModeVarUpdated);
+
+ UpdatePixelGeometry();
+ gfxVars::SetSystemTextPixelGeometryListener(PixelGeometryVarUpdated);
+}
+
+/* static */
+DWriteSettings& DWriteSettings::Get(bool aGDISettings) {
+ DWriteSettings* settings;
+ if (aGDISettings) {
+ static DWriteSettings* sGDISettings =
+ new DWriteSettings(/* aUseGDISettings */ true);
+ settings = sGDISettings;
+ } else {
+ static DWriteSettings* sStandardSettings =
+ new DWriteSettings(/* aUseGDISettings */ false);
+ settings = sStandardSettings;
+ }
+ return *settings;
+}
+
+Float DWriteSettings::ClearTypeLevel() { return sClearTypeLevel; }
+
+Float DWriteSettings::EnhancedContrast() {
+ return mUseGDISettings ? 0.0f : sEnhancedContrast.load();
+}
+
+Float DWriteSettings::Gamma() { return mUseGDISettings ? sGDIGamma : sGamma; }
+
+DWRITE_PIXEL_GEOMETRY DWriteSettings::PixelGeometry() { return sPixelGeometry; }
+
+DWRITE_RENDERING_MODE DWriteSettings::RenderingMode() {
+ return mUseGDISettings ? DWRITE_RENDERING_MODE_GDI_CLASSIC : sRenderingMode;
+}
+
+DWRITE_MEASURING_MODE DWriteSettings::MeasuringMode() {
+ return mUseGDISettings ? DWRITE_MEASURING_MODE_GDI_CLASSIC : sMeasuringMode;
+}
+
+already_AddRefed<IDWriteRenderingParams> DWriteSettings::RenderingParams() {
+ auto lockedParams = mUseGDISettings ? sGDIRenderingParams.Lock()
+ : sStandardRenderingParams.Lock();
+ if (!lockedParams.ref()) {
+ RefPtr<IDWriteRenderingParams> params;
+ HRESULT hr = Factory::GetDWriteFactory()->CreateCustomRenderingParams(
+ Gamma(), EnhancedContrast(), ClearTypeLevel(), PixelGeometry(),
+ RenderingMode(), getter_AddRefs(params));
+ if (SUCCEEDED(hr)) {
+ lockedParams.ref() = params.forget();
+ } else {
+ gfxWarning() << "Failed to create DWrite custom rendering params.";
+ }
+ }
+
+ return do_AddRef(lockedParams.ref());
+}
diff --git a/gfx/2d/DWriteSettings.h b/gfx/2d/DWriteSettings.h
new file mode 100644
index 0000000000..39a46cfb6d
--- /dev/null
+++ b/gfx/2d/DWriteSettings.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_2D_DWRITESETTINGS_H_
+#define MOZILLA_GFX_2D_DWRITESETTINGS_H_
+
+#include <dwrite.h>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "Types.h"
+
+namespace mozilla {
+namespace gfx {
+
+class DWriteSettings final {
+ public:
+ static void Initialize();
+
+ static DWriteSettings& Get(bool aGDISettings);
+
+ Float ClearTypeLevel();
+ Float EnhancedContrast();
+ Float Gamma();
+ DWRITE_PIXEL_GEOMETRY PixelGeometry();
+ DWRITE_RENDERING_MODE RenderingMode();
+ DWRITE_MEASURING_MODE MeasuringMode();
+ already_AddRefed<IDWriteRenderingParams> RenderingParams();
+
+ private:
+ explicit DWriteSettings(bool aUseGDISettings);
+
+ const bool mUseGDISettings;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_2D_DWRITESETTINGS_H_
diff --git a/gfx/2d/DataSourceSurface.cpp b/gfx/2d/DataSourceSurface.cpp
new file mode 100644
index 0000000000..cf892d2eab
--- /dev/null
+++ b/gfx/2d/DataSourceSurface.cpp
@@ -0,0 +1,20 @@
+/* -*- 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 "2D.h"
+#include "DataSourceSurfaceWrapper.h"
+
+namespace mozilla {
+namespace gfx {
+
+already_AddRefed<DataSourceSurface> DataSourceSurface::GetDataSurface() {
+ RefPtr<DataSourceSurface> surface =
+ IsDataSourceSurface() ? this : new DataSourceSurfaceWrapper(this);
+ return surface.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DataSourceSurfaceWrapper.h b/gfx/2d/DataSourceSurfaceWrapper.h
new file mode 100644
index 0000000000..b5d3466147
--- /dev/null
+++ b/gfx/2d/DataSourceSurfaceWrapper.h
@@ -0,0 +1,49 @@
+/* -*- 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_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 final : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceWrapper, override)
+ explicit DataSourceSurfaceWrapper(DataSourceSurface* aSurface)
+ : mSurface(aSurface) {}
+
+ bool Equals(SourceSurface* aOther, bool aSymmetric = true) override {
+ return DataSourceSurface::Equals(aOther, aSymmetric) ||
+ mSurface->Equals(aOther, aSymmetric);
+ }
+
+ SurfaceType GetType() const override { return SurfaceType::DATA; }
+
+ uint8_t* GetData() override { return mSurface->GetData(); }
+ int32_t Stride() override { return mSurface->Stride(); }
+ IntSize GetSize() const override { return mSurface->GetSize(); }
+ SurfaceFormat GetFormat() const override { return mSurface->GetFormat(); }
+ bool IsValid() const override { return mSurface->IsValid(); }
+
+ bool Map(MapType aType, MappedSurface* aMappedSurface) override {
+ return mSurface->Map(aType, aMappedSurface);
+ }
+
+ void Unmap() override { mSurface->Unmap(); }
+
+ private:
+ RefPtr<DataSourceSurface> mSurface;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ */
diff --git a/gfx/2d/DataSurfaceHelpers.cpp b/gfx/2d/DataSurfaceHelpers.cpp
new file mode 100644
index 0000000000..0c3863b850
--- /dev/null
+++ b/gfx/2d/DataSurfaceHelpers.cpp
@@ -0,0 +1,343 @@
+/* -*- 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 <cstring>
+
+#include "2D.h"
+#include "DataSurfaceHelpers.h"
+#include "Logging.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PodOperations.h"
+#include "Swizzle.h"
+#include "Tools.h"
+
+namespace mozilla {
+namespace gfx {
+
+int32_t StrideForFormatAndWidth(SurfaceFormat aFormat, int32_t aWidth) {
+ MOZ_ASSERT(aFormat <= SurfaceFormat::UNKNOWN);
+ MOZ_ASSERT(aWidth > 0);
+
+ // There's nothing special about this alignment, other than that it's what
+ // cairo_format_stride_for_width uses.
+ static const int32_t alignment = sizeof(int32_t);
+
+ const int32_t bpp = BytesPerPixel(aFormat);
+
+ if (aWidth >= (INT32_MAX - alignment) / bpp) {
+ return -1; // too big
+ }
+
+ return (bpp * aWidth + alignment - 1) & ~(alignment - 1);
+}
+
+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 + size_t(aPoint.y) * size_t(aMap->mStride) +
+ size_t(aPoint.x) * size_t(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 CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst,
+ IntSize aSrcSize, int32_t aSrcStride,
+ int32_t aBytesPerPixel) {
+ CheckedInt<size_t> packedStride(aBytesPerPixel);
+ packedStride *= aSrcSize.width;
+ if (!packedStride.isValid()) {
+ MOZ_ASSERT(false, "Invalid stride");
+ return;
+ }
+
+ CheckedInt<size_t> totalSize(aSrcStride);
+ totalSize *= aSrcSize.height;
+ if (!totalSize.isValid()) {
+ MOZ_ASSERT(false, "Invalid surface size");
+ return;
+ }
+
+ if (size_t(aSrcStride) == packedStride.value()) {
+ // aSrc is already packed, so we can copy with a single memcpy.
+ memcpy(aDst, aSrc, totalSize.value());
+ } else {
+ // memcpy one row at a time.
+ for (int row = 0; row < aSrcSize.height; ++row) {
+ memcpy(aDst, aSrc, packedStride.value());
+ aSrc += aSrcStride;
+ aDst += packedStride.value();
+ }
+ }
+}
+
+UniquePtr<uint8_t[]> SurfaceToPackedBGRA(DataSourceSurface* aSurface) {
+ SurfaceFormat format = aSurface->GetFormat();
+ if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) {
+ return nullptr;
+ }
+
+ IntSize size = aSurface->GetSize();
+ if (size.width < 0 || size.width >= INT32_MAX / 4) {
+ return nullptr;
+ }
+ int32_t stride = size.width * 4;
+ CheckedInt<size_t> bufferSize =
+ CheckedInt<size_t>(stride) * CheckedInt<size_t>(size.height);
+ if (!bufferSize.isValid()) {
+ return nullptr;
+ }
+ UniquePtr<uint8_t[]> imageBuffer(new (std::nothrow)
+ uint8_t[bufferSize.value()]);
+ 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);
+
+ aSurface->Unmap();
+
+ if (format == SurfaceFormat::B8G8R8X8) {
+ // Convert BGRX to BGRA by setting a to 255.
+ SwizzleData(imageBuffer.get(), stride, SurfaceFormat::X8R8G8B8_UINT32,
+ imageBuffer.get(), stride, SurfaceFormat::A8R8G8B8_UINT32,
+ size);
+ }
+
+ 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();
+ if (size.width < 0 || size.width >= INT32_MAX / 3) {
+ return nullptr;
+ }
+ int32_t stride = size.width * 3;
+ CheckedInt<size_t> bufferSize =
+ CheckedInt<size_t>(stride) * CheckedInt<size_t>(size.height);
+ if (!bufferSize.isValid()) {
+ return nullptr;
+ }
+ uint8_t* imageBuffer = new (std::nothrow) uint8_t[bufferSize.value()];
+ if (!imageBuffer) {
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ delete[] imageBuffer;
+ return nullptr;
+ }
+
+ SwizzleData(map.mData, map.mStride, SurfaceFormat::B8G8R8X8, imageBuffer,
+ stride, SurfaceFormat::B8G8R8, size);
+
+ 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
+ *
+ * aSrcRect and aDestPoint are in internal local coordinates.
+ * i.e. locations of pixels and not in the same coordinate space
+ * as aSrc->GetRect()
+ */
+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());
+ uint8_t* destData =
+ DataAtOffset(aDest, destMap.GetMappedSurface(), aDestPoint);
+
+ SwizzleData(sourceData, srcMap.GetStride(), aSrc->GetFormat(), destData,
+ destMap.GetStride(), aDest->GetFormat(), aSrcRect.Size());
+
+ 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
diff --git a/gfx/2d/DataSurfaceHelpers.h b/gfx/2d/DataSurfaceHelpers.h
new file mode 100644
index 0000000000..34bf36e8c8
--- /dev/null
+++ b/gfx/2d/DataSurfaceHelpers.h
@@ -0,0 +1,131 @@
+/* -*- 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_DATASURFACEHELPERS_H
+#define _MOZILLA_GFX_DATASURFACEHELPERS_H
+
+#include "2D.h"
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace gfx {
+
+int32_t StrideForFormatAndWidth(SurfaceFormat aFormat, int32_t aWidth);
+
+/**
+ * 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);
+
+/**
+ * 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
diff --git a/gfx/2d/DrawEventRecorder.cpp b/gfx/2d/DrawEventRecorder.cpp
new file mode 100644
index 0000000000..894eb1045c
--- /dev/null
+++ b/gfx/2d/DrawEventRecorder.cpp
@@ -0,0 +1,159 @@
+/* -*- 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 "DrawEventRecorder.h"
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "PathRecording.h"
+#include "RecordingTypes.h"
+#include "RecordedEventImpl.h"
+
+namespace mozilla {
+namespace gfx {
+
+DrawEventRecorderPrivate::DrawEventRecorderPrivate() : mExternalFonts(false) {}
+
+void DrawEventRecorderPrivate::StoreExternalSurfaceRecording(
+ SourceSurface* aSurface, uint64_t aKey) {
+ RecordEvent(RecordedExternalSurfaceCreation(aSurface, aKey));
+ mExternalSurfaces.push_back(aSurface);
+}
+
+void DrawEventRecorderPrivate::StoreSourceSurfaceRecording(
+ SourceSurface* aSurface, const char* aReason) {
+ RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
+ IntSize surfaceSize = aSurface->GetSize();
+ Maybe<DataSourceSurface::ScopedMap> map;
+ if (dataSurf) {
+ map.emplace(dataSurf, DataSourceSurface::READ);
+ }
+ if (!dataSurf || !map->IsMapped() ||
+ !Factory::AllowedSurfaceSize(surfaceSize)) {
+ gfxWarning() << "Recording failed to record SourceSurface for " << aReason;
+
+ // If surface size is not allowed, replace with reasonable size.
+ if (!Factory::AllowedSurfaceSize(surfaceSize)) {
+ surfaceSize.width = std::min(surfaceSize.width, kReasonableSurfaceSize);
+ surfaceSize.height = std::min(surfaceSize.height, kReasonableSurfaceSize);
+ }
+
+ // Insert a dummy source surface.
+ int32_t stride = surfaceSize.width * BytesPerPixel(aSurface->GetFormat());
+ UniquePtr<uint8_t[]> sourceData =
+ MakeUniqueFallible<uint8_t[]>(stride * surfaceSize.height);
+ if (!sourceData) {
+ // If the surface is too big just create a 1 x 1 dummy.
+ surfaceSize.width = 1;
+ surfaceSize.height = 1;
+ stride = surfaceSize.width * BytesPerPixel(aSurface->GetFormat());
+ sourceData = MakeUnique<uint8_t[]>(stride * surfaceSize.height);
+ }
+
+ RecordEvent(RecordedSourceSurfaceCreation(aSurface, sourceData.get(),
+ stride, surfaceSize,
+ aSurface->GetFormat()));
+ return;
+ }
+
+ RecordEvent(RecordedSourceSurfaceCreation(
+ aSurface, map->GetData(), map->GetStride(), dataSurf->GetSize(),
+ dataSurf->GetFormat()));
+}
+
+void DrawEventRecorderPrivate::RecordSourceSurfaceDestruction(void* aSurface) {
+ RemoveSourceSurface(static_cast<SourceSurface*>(aSurface));
+ RemoveStoredObject(aSurface);
+ RecordEvent(RecordedSourceSurfaceDestruction(ReferencePtr(aSurface)));
+}
+
+void DrawEventRecorderPrivate::DecrementUnscaledFontRefCount(
+ const ReferencePtr aUnscaledFont) {
+ auto element = mUnscaledFontRefs.find(aUnscaledFont);
+ MOZ_DIAGNOSTIC_ASSERT(element != mUnscaledFontRefs.end(),
+ "DecrementUnscaledFontRefCount calls should balance "
+ "with IncrementUnscaledFontRefCount calls");
+ if (--(element->second) <= 0) {
+ RecordEvent(RecordedUnscaledFontDestruction(aUnscaledFont));
+ mUnscaledFontRefs.erase(aUnscaledFont);
+ }
+}
+
+void DrawEventRecorderMemory::RecordEvent(const RecordedEvent& aEvent) {
+ aEvent.RecordToStream(mOutputStream);
+}
+
+void DrawEventRecorderMemory::AddDependentSurface(uint64_t aDependencyId) {
+ mDependentSurfaces.Insert(aDependencyId);
+}
+
+nsTHashSet<uint64_t>&& DrawEventRecorderMemory::TakeDependentSurfaces() {
+ return std::move(mDependentSurfaces);
+}
+
+DrawEventRecorderMemory::DrawEventRecorderMemory() {
+ WriteHeader(mOutputStream);
+}
+
+DrawEventRecorderMemory::DrawEventRecorderMemory(
+ const SerializeResourcesFn& aFn)
+ : mSerializeCallback(aFn) {
+ mExternalFonts = !!mSerializeCallback;
+ WriteHeader(mOutputStream);
+}
+
+void DrawEventRecorderMemory::Flush() {}
+
+void DrawEventRecorderMemory::FlushItem(IntRect aRect) {
+ MOZ_RELEASE_ASSERT(!aRect.IsEmpty());
+ // Detaching our existing resources will add some
+ // destruction events to our stream so we need to do that
+ // first.
+ DetachResources();
+
+ // See moz2d_renderer.rs for a description of the stream format
+ WriteElement(mIndex, mOutputStream.mLength);
+
+ // write out the fonts into the extra data section
+ mSerializeCallback(mOutputStream, mScaledFonts);
+ WriteElement(mIndex, mOutputStream.mLength);
+
+ WriteElement(mIndex, aRect.x);
+ WriteElement(mIndex, aRect.y);
+ WriteElement(mIndex, aRect.XMost());
+ WriteElement(mIndex, aRect.YMost());
+ ClearResources();
+
+ // write out a new header for the next recording in the stream
+ WriteHeader(mOutputStream);
+}
+
+bool DrawEventRecorderMemory::Finish() {
+ // this length might be 0, and things should still work.
+ // for example if there are no items in a particular area
+ size_t indexOffset = mOutputStream.mLength;
+ // write out the index
+ mOutputStream.write(mIndex.mData, mIndex.mLength);
+ bool hasItems = mIndex.mLength != 0;
+ mIndex.reset();
+ // write out the offset of the Index to the end of the output stream
+ WriteElement(mOutputStream, indexOffset);
+ ClearResources();
+ return hasItems;
+}
+
+size_t DrawEventRecorderMemory::RecordingSize() {
+ return mOutputStream.mLength;
+}
+
+void DrawEventRecorderMemory::WipeRecording() {
+ mOutputStream.reset();
+ mIndex.reset();
+
+ WriteHeader(mOutputStream);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DrawEventRecorder.h b/gfx/2d/DrawEventRecorder.h
new file mode 100644
index 0000000000..9b19006338
--- /dev/null
+++ b/gfx/2d/DrawEventRecorder.h
@@ -0,0 +1,277 @@
+/* -*- 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_DRAWEVENTRECORDER_H_
+#define MOZILLA_GFX_DRAWEVENTRECORDER_H_
+
+#include "2D.h"
+#include "RecordedEvent.h"
+#include "RecordingTypes.h"
+
+#include <unordered_set>
+#include <unordered_map>
+#include <functional>
+#include <vector>
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+namespace gfx {
+
+class PathRecording;
+
+class DrawEventRecorderPrivate : public DrawEventRecorder {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPrivate, override)
+
+ DrawEventRecorderPrivate();
+ virtual ~DrawEventRecorderPrivate() = default;
+ bool Finish() override {
+ ClearResources();
+ return true;
+ }
+ virtual void FlushItem(IntRect) {}
+ void DetachResources() {
+ std::unordered_set<ScaledFont*> fonts;
+ fonts.swap(mStoredFonts);
+ std::for_each(fonts.begin(), fonts.end(), [this](auto font) {
+ font->RemoveUserData(reinterpret_cast<UserDataKey*>(this));
+ });
+
+ // SourceSurfaces can be deleted off the main thread, so we use
+ // ThreadSafeWeakPtrs to allow for this. RemoveUserData is thread safe.
+ std::unordered_map<void*, ThreadSafeWeakPtr<SourceSurface>> surfaces;
+ surfaces.swap(mStoredSurfaces);
+ std::for_each(surfaces.begin(), surfaces.end(), [this](auto surfacePair) {
+ RefPtr<SourceSurface> strongRef(surfacePair.second);
+ if (strongRef) {
+ strongRef->RemoveUserData(reinterpret_cast<UserDataKey*>(this));
+ }
+ });
+
+ // Now that we've detached we can't get any more pending deletions, so
+ // processing now should mean we include all clean up operations.
+ ProcessPendingDeletions();
+ }
+
+ void ClearResources() {
+ mStoredObjects.clear();
+ mStoredFontData.clear();
+ mScaledFonts.clear();
+ }
+
+ template <class S>
+ void WriteHeader(S& aStream) {
+ WriteElement(aStream, kMagicInt);
+ WriteElement(aStream, kMajorRevision);
+ WriteElement(aStream, kMinorRevision);
+ }
+
+ virtual void RecordEvent(const RecordedEvent& aEvent) = 0;
+
+ void AddStoredObject(const ReferencePtr aObject) {
+ ProcessPendingDeletions();
+ mStoredObjects.insert(aObject);
+ }
+
+ /**
+ * This is a combination of HasStoredObject and AddStoredObject, so that we
+ * only have to call ProcessPendingDeletions once, which involves locking.
+ * @param aObject the object to store if not already stored
+ * @return true if the object was not already stored, false if it was
+ */
+ bool TryAddStoredObject(const ReferencePtr aObject) {
+ ProcessPendingDeletions();
+ if (mStoredObjects.find(aObject) != mStoredObjects.end()) {
+ return false;
+ }
+
+ mStoredObjects.insert(aObject);
+ return true;
+ }
+
+ void AddPendingDeletion(std::function<void()>&& aPendingDeletion) {
+ auto lockedPendingDeletions = mPendingDeletions.Lock();
+ lockedPendingDeletions->emplace_back(std::move(aPendingDeletion));
+ }
+
+ void RemoveStoredObject(const ReferencePtr aObject) {
+ mStoredObjects.erase(aObject);
+ }
+
+ /**
+ * @param aUnscaledFont the UnscaledFont to increment the reference count for
+ * @return the previous reference count
+ */
+ int32_t IncrementUnscaledFontRefCount(const ReferencePtr aUnscaledFont) {
+ int32_t& count = mUnscaledFontRefs[aUnscaledFont];
+ return count++;
+ }
+
+ /**
+ * Decrements the reference count for aUnscaledFont and, if count is now zero,
+ * records its destruction.
+ * @param aUnscaledFont the UnscaledFont to decrement the reference count for
+ */
+ void DecrementUnscaledFontRefCount(const ReferencePtr aUnscaledFont);
+
+ void AddScaledFont(ScaledFont* aFont) {
+ if (mStoredFonts.insert(aFont).second && WantsExternalFonts()) {
+ mScaledFonts.push_back(aFont);
+ }
+ }
+
+ void RemoveScaledFont(ScaledFont* aFont) { mStoredFonts.erase(aFont); }
+
+ void AddSourceSurface(SourceSurface* aSurface) {
+ mStoredSurfaces.emplace(aSurface, aSurface);
+ }
+
+ void RemoveSourceSurface(SourceSurface* aSurface) {
+ mStoredSurfaces.erase(aSurface);
+ }
+
+#if defined(DEBUG)
+ // Only used within debug assertions.
+ bool HasStoredObject(const ReferencePtr aObject) {
+ ProcessPendingDeletions();
+ return mStoredObjects.find(aObject) != mStoredObjects.end();
+ }
+#endif
+
+ void AddStoredFontData(const uint64_t aFontDataKey) {
+ mStoredFontData.insert(aFontDataKey);
+ }
+
+ bool HasStoredFontData(const uint64_t aFontDataKey) {
+ return mStoredFontData.find(aFontDataKey) != mStoredFontData.end();
+ }
+
+ bool WantsExternalFonts() const { return mExternalFonts; }
+
+ void TakeExternalSurfaces(std::vector<RefPtr<SourceSurface>>& aSurfaces) {
+ aSurfaces = std::move(mExternalSurfaces);
+ }
+
+ virtual void StoreSourceSurfaceRecording(SourceSurface* aSurface,
+ const char* aReason);
+
+ /**
+ * Used when a source surface is destroyed, aSurface is a void* instead of a
+ * SourceSurface* because this is called during the SourceSurface destructor,
+ * so it is partially destructed and should not be accessed.
+ * @param aSurface the surface whose destruction is being recorded
+ */
+ void RecordSourceSurfaceDestruction(void* aSurface);
+
+ virtual void AddDependentSurface(uint64_t aDependencyId) {
+ MOZ_CRASH("GFX: AddDependentSurface");
+ }
+
+ protected:
+ void StoreExternalSurfaceRecording(SourceSurface* aSurface, uint64_t aKey);
+
+ void ProcessPendingDeletions() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PendingDeletionsVector pendingDeletions;
+ {
+ auto lockedPendingDeletions = mPendingDeletions.Lock();
+ pendingDeletions.swap(*lockedPendingDeletions);
+ }
+ for (const auto& pendingDeletion : pendingDeletions) {
+ pendingDeletion();
+ }
+ }
+
+ virtual void Flush() = 0;
+
+ std::unordered_set<const void*> mStoredObjects;
+
+ using PendingDeletionsVector = std::vector<std::function<void()>>;
+ DataMutex<PendingDeletionsVector> mPendingDeletions{
+ "DrawEventRecorderPrivate::mPendingDeletions"};
+
+ // It's difficult to track the lifetimes of UnscaledFonts directly, so we
+ // instead track the number of recorded ScaledFonts that hold a reference to
+ // an Unscaled font and use that as a proxy to the real lifetime. An
+ // UnscaledFonts lifetime could be longer than this, but we only use the
+ // ScaledFonts directly and if another uses an UnscaledFont we have destroyed
+ // on the translation side, it will be recreated.
+ std::unordered_map<const void*, int32_t> mUnscaledFontRefs;
+
+ std::unordered_set<uint64_t> mStoredFontData;
+ std::unordered_set<ScaledFont*> mStoredFonts;
+ std::vector<RefPtr<ScaledFont>> mScaledFonts;
+
+ // SourceSurfaces can get deleted off the main thread, so we hold a map of the
+ // raw pointer to a ThreadSafeWeakPtr to protect against this.
+ std::unordered_map<void*, ThreadSafeWeakPtr<SourceSurface>> mStoredSurfaces;
+ std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
+ bool mExternalFonts;
+};
+
+typedef std::function<void(MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts)>
+ SerializeResourcesFn;
+
+// WARNING: This should not be used in its existing state because
+// it is likely to OOM because of large continguous allocations.
+class DrawEventRecorderMemory : public DrawEventRecorderPrivate {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderMemory, override)
+
+ /**
+ * Constructs a DrawEventRecorder that stores the recording in memory.
+ */
+ DrawEventRecorderMemory();
+ explicit DrawEventRecorderMemory(const SerializeResourcesFn& aSerialize);
+
+ void RecordEvent(const RecordedEvent& aEvent) override;
+
+ void AddDependentSurface(uint64_t aDependencyId) override;
+
+ nsTHashSet<uint64_t>&& TakeDependentSurfaces();
+
+ /**
+ * @return the current size of the recording (in chars).
+ */
+ size_t RecordingSize();
+
+ /**
+ * 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();
+ bool Finish() override;
+ void FlushItem(IntRect) override;
+
+ MemStream mOutputStream;
+ /* The index stream is of the form:
+ * ItemIndex { size_t dataEnd; size_t extraDataEnd; }
+ * It gets concatenated to the end of mOutputStream in Finish()
+ * The last size_t in the stream is offset of the begining of the
+ * index.
+ */
+ MemStream mIndex;
+
+ protected:
+ virtual ~DrawEventRecorderMemory() = default;
+
+ private:
+ SerializeResourcesFn mSerializeCallback;
+ nsTHashSet<uint64_t> mDependentSurfaces;
+
+ void Flush() override;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_DRAWEVENTRECORDER_H_ */
diff --git a/gfx/2d/DrawTarget.cpp b/gfx/2d/DrawTarget.cpp
new file mode 100644
index 0000000000..eb7b23f3be
--- /dev/null
+++ b/gfx/2d/DrawTarget.cpp
@@ -0,0 +1,339 @@
+/* -*- 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 "2D.h"
+#include "Blur.h"
+#include "Logging.h"
+#include "PathHelpers.h"
+#include "SourceSurfaceRawData.h"
+#include "Tools.h"
+
+#include "BufferEdgePad.h"
+#include "BufferUnrotate.h"
+
+#ifdef USE_NEON
+# include "mozilla/arm.h"
+# include "LuminanceNEON.h"
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Byte offsets of channels in a native packed gfxColor or cairo image surface.
+ */
+#ifdef IS_BIG_ENDIAN
+# define GFX_ARGB32_OFFSET_A 0
+# define GFX_ARGB32_OFFSET_R 1
+# define GFX_ARGB32_OFFSET_G 2
+# define GFX_ARGB32_OFFSET_B 3
+#else
+# define GFX_ARGB32_OFFSET_A 3
+# define GFX_ARGB32_OFFSET_R 2
+# define GFX_ARGB32_OFFSET_G 1
+# define GFX_ARGB32_OFFSET_B 0
+#endif
+
+// c = n / 255
+// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5
+static const uint8_t gsRGBToLinearRGBMap[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
+ 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,
+ 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11,
+ 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17,
+ 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25,
+ 26, 27, 27, 28, 29, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35,
+ 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47,
+ 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77,
+ 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 95,
+ 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, 111, 112, 114, 115,
+ 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 134, 136, 138,
+ 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, 161, 163,
+ 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190,
+ 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,
+ 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253,
+ 255};
+
+static void ComputesRGBLuminanceMask(const uint8_t* aSourceData,
+ int32_t aSourceStride, uint8_t* aDestData,
+ int32_t aDestStride, const IntSize& aSize,
+ float aOpacity) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride, aDestData,
+ aDestStride, aSize, aOpacity);
+ return;
+ }
+#endif
+
+ int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+ int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+ int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ const uint8_t* sourcePixel = aSourceData;
+ int32_t destOffset = aDestStride - aSize.width;
+ uint8_t* destPixel = aDestData;
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x++) {
+ uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
+
+ if (a) {
+ *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
+ greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+ blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >>
+ 8;
+ } else {
+ *destPixel = 0;
+ }
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
+
+static void ComputeLinearRGBLuminanceMask(
+ const uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData,
+ int32_t aDestStride, const IntSize& aSize, float aOpacity) {
+ int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+ int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+ int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ const uint8_t* sourcePixel = aSourceData;
+ int32_t destOffset = aDestStride - aSize.width;
+ uint8_t* destPixel = aDestData;
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x++) {
+ uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
+
+ // unpremultiply
+ if (a) {
+ if (a == 255) {
+ /* sRGB -> linearRGB -> intensity */
+ *destPixel = static_cast<uint8_t>(
+ (gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] *
+ redFactor +
+ gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] *
+ greenFactor +
+ gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] *
+ blueFactor) >>
+ 8);
+ } else {
+ uint8_t tempPixel[4];
+ tempPixel[GFX_ARGB32_OFFSET_B] =
+ (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a;
+ tempPixel[GFX_ARGB32_OFFSET_G] =
+ (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a;
+ tempPixel[GFX_ARGB32_OFFSET_R] =
+ (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a;
+
+ /* sRGB -> linearRGB -> intensity */
+ *destPixel = static_cast<uint8_t>(
+ ((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] *
+ redFactor +
+ gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] *
+ greenFactor +
+ gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] *
+ blueFactor) >>
+ 8) *
+ (a / 255.0f));
+ }
+ } else {
+ *destPixel = 0;
+ }
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
+
+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);
+}
+
+void DrawTarget::FillRoundedRect(const RoundedRect& aRect,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ RefPtr<Path> path = MakePathForRoundedRect(*this, aRect.rect, aRect.corners);
+ Fill(path, aPattern, aOptions);
+}
+
+void DrawTarget::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ RefPtr<Path> path = aFont->GetPathForGlyphs(aBuffer, this);
+ Stroke(path, aPattern, aStrokeOptions, aOptions);
+}
+
+already_AddRefed<SourceSurface> DrawTarget::IntoLuminanceSource(
+ LuminanceType aMaskType, float aOpacity) {
+ // The default IntoLuminanceSource implementation needs a format of B8G8R8A8.
+ if (mFormat != SurfaceFormat::B8G8R8A8) {
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> surface = Snapshot();
+ if (!surface) {
+ return nullptr;
+ }
+
+ IntSize size = surface->GetSize();
+
+ RefPtr<DataSourceSurface> maskSurface = surface->GetDataSurface();
+ if (!maskSurface) {
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return nullptr;
+ }
+
+ // Create alpha channel mask for output
+ RefPtr<SourceSurfaceAlignedRawData> destMaskSurface =
+ new SourceSurfaceAlignedRawData;
+ if (!destMaskSurface->Init(size, SurfaceFormat::A8, false, 0)) {
+ return nullptr;
+ }
+ DataSourceSurface::MappedSurface destMap;
+ if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
+ return nullptr;
+ }
+
+ switch (aMaskType) {
+ case LuminanceType::LUMINANCE: {
+ ComputesRGBLuminanceMask(map.mData, map.mStride, destMap.mData,
+ destMap.mStride, size, aOpacity);
+ break;
+ }
+ case LuminanceType::LINEARRGB: {
+ ComputeLinearRGBLuminanceMask(map.mData, map.mStride, destMap.mData,
+ destMap.mStride, size, aOpacity);
+ break;
+ }
+ }
+
+ maskSurface->Unmap();
+ destMaskSurface->Unmap();
+
+ return destMaskSurface.forget();
+}
+
+void DrawTarget::Blur(const AlphaBoxBlur& aBlur) {
+ uint8_t* data;
+ IntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+ if (!LockBits(&data, &size, &stride, &format)) {
+ gfxWarning() << "Cannot perform in-place blur on non-data DrawTarget";
+ return;
+ }
+
+ // Sanity check that the blur size matches the draw target.
+ MOZ_ASSERT(size == aBlur.GetSize());
+ MOZ_ASSERT(stride == aBlur.GetStride());
+ aBlur.Blur(data);
+
+ ReleaseBits(data);
+}
+
+void DrawTarget::PadEdges(const IntRegion& aRegion) {
+ PadDrawTargetOutFromRegion(this, aRegion);
+}
+
+bool DrawTarget::Unrotate(IntPoint aRotation) {
+ unsigned char* data;
+ IntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+
+ if (LockBits(&data, &size, &stride, &format)) {
+ uint8_t bytesPerPixel = BytesPerPixel(format);
+ BufferUnrotate(data, size.width * bytesPerPixel, size.height, stride,
+ aRotation.x * bytesPerPixel, aRotation.y);
+ ReleaseBits(data);
+ return true;
+ }
+ return false;
+}
+
+int32_t ShadowOptions::BlurRadius() const {
+ return AlphaBoxBlur::CalculateBlurRadius(Point(mSigma, mSigma)).width;
+}
+
+void DrawTarget::DrawShadow(const Path* aPath, const Pattern& aPattern,
+ const ShadowOptions& aShadow,
+ const DrawOptions& aOptions,
+ const StrokeOptions* aStrokeOptions) {
+ // Get the approximate bounds of the source path
+ Rect bounds = aPath->GetFastBounds(GetTransform(), aStrokeOptions);
+ if (bounds.IsEmpty()) {
+ return;
+ }
+ // Inflate the bounds by the blur radius
+ bounds += aShadow.mOffset;
+ int32_t blurRadius = aShadow.BlurRadius();
+ bounds.Inflate(blurRadius);
+ bounds.RoundOut();
+ // Check if the bounds intersect the viewport
+ Rect viewport(GetRect());
+ viewport.Inflate(blurRadius);
+ bounds = bounds.Intersect(viewport);
+ IntRect intBounds;
+ if (bounds.IsEmpty() || !bounds.ToIntRect(&intBounds) ||
+ !CanCreateSimilarDrawTarget(intBounds.Size(), SurfaceFormat::A8)) {
+ return;
+ }
+ // Create a draw target for drawing the shadow mask with enough room for blur
+ RefPtr<DrawTarget> shadowTarget = CreateShadowDrawTarget(
+ intBounds.Size(), SurfaceFormat::A8, aShadow.mSigma);
+ if (shadowTarget) {
+ // See bug 1524554.
+ shadowTarget->ClearRect(Rect());
+ }
+ if (!shadowTarget || !shadowTarget->IsValid()) {
+ return;
+ }
+ // Draw the path into the target for the initial shadow mask
+ Point offset = Point(intBounds.TopLeft()) - aShadow.mOffset;
+ shadowTarget->SetTransform(GetTransform().PostTranslate(-offset));
+ DrawOptions shadowDrawOptions(
+ aOptions.mAlpha, CompositionOp::OP_OVER,
+ blurRadius > 1 ? AntialiasMode::NONE : aOptions.mAntialiasMode);
+ if (aStrokeOptions) {
+ shadowTarget->Stroke(aPath, aPattern, *aStrokeOptions, shadowDrawOptions);
+ } else {
+ shadowTarget->Fill(aPath, aPattern, shadowDrawOptions);
+ }
+ RefPtr<SourceSurface> snapshot = shadowTarget->Snapshot();
+ // Finally, hand a snapshot of the mask to DrawSurfaceWithShadow for the
+ // final shadow blur
+ if (snapshot) {
+ DrawSurfaceWithShadow(snapshot, offset, aShadow, aOptions.mCompositionOp);
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp
new file mode 100644
index 0000000000..2121839e4b
--- /dev/null
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -0,0 +1,2010 @@
+/* -*- 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 "DrawTargetCairo.h"
+
+#include "SourceSurfaceCairo.h"
+#include "PathCairo.h"
+#include "HelpersCairo.h"
+#include "BorrowedContext.h"
+#include "FilterNodeSoftware.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "nsPrintfCString.h"
+
+#include "cairo.h"
+#include "cairo-tee.h"
+#include <string.h>
+
+#include "Blur.h"
+#include "Logging.h"
+#include "Tools.h"
+
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+# include "cairo-quartz.h"
+# ifdef MOZ_WIDGET_COCOA
+# include <ApplicationServices/ApplicationServices.h>
+# endif
+#endif
+
+#ifdef CAIRO_HAS_XLIB_SURFACE
+# include "cairo-xlib.h"
+#endif
+
+#ifdef CAIRO_HAS_WIN32_SURFACE
+# include "cairo-win32.h"
+#endif
+
+#define PIXMAN_DONT_DEFINE_STDINT
+#include "pixman.h"
+
+#include <algorithm>
+
+// 2^23
+#define CAIRO_COORD_MAX (Float(0x7fffff))
+
+namespace mozilla {
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCairoSurface, cairo_surface_t,
+ cairo_surface_destroy);
+
+namespace gfx {
+
+cairo_surface_t* DrawTargetCairo::mDummySurface;
+
+namespace {
+
+// An RAII class to prepare to draw a context and optional path. Saves and
+// restores the context on construction/destruction.
+class AutoPrepareForDrawing {
+ public:
+ AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx) : mCtx(ctx) {
+ dt->PrepareForDrawing(ctx);
+ cairo_save(mCtx);
+ MOZ_ASSERT(cairo_status(mCtx) ||
+ dt->GetTransform().FuzzyEquals(GetTransform()));
+ }
+
+ AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path)
+ : mCtx(ctx) {
+ dt->PrepareForDrawing(ctx, path);
+ cairo_save(mCtx);
+ MOZ_ASSERT(cairo_status(mCtx) ||
+ dt->GetTransform().FuzzyEquals(GetTransform()));
+ }
+
+ ~AutoPrepareForDrawing() {
+ cairo_restore(mCtx);
+ cairo_status_t status = cairo_status(mCtx);
+ if (status) {
+ gfxWarning() << "DrawTargetCairo context in error state: "
+ << cairo_status_to_string(status) << "(" << status << ")";
+ }
+ }
+
+ private:
+#ifdef DEBUG
+ Matrix GetTransform() {
+ cairo_matrix_t mat;
+ cairo_get_matrix(mCtx, &mat);
+ return Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
+ }
+#endif
+
+ cairo_t* mCtx;
+};
+
+/* Clamp r to (0,0) (2^23,2^23)
+ * these are to be device coordinates.
+ *
+ * Returns false if the rectangle is completely out of bounds,
+ * true otherwise.
+ *
+ * This function assumes that it will be called with a rectangle being
+ * drawn into a surface with an identity transformation matrix; that
+ * is, anything above or to the left of (0,0) will be offscreen.
+ *
+ * First it checks if the rectangle is entirely beyond
+ * CAIRO_COORD_MAX; if so, it can't ever appear on the screen --
+ * false is returned.
+ *
+ * Then it shifts any rectangles with x/y < 0 so that x and y are = 0,
+ * and adjusts the width and height appropriately. For example, a
+ * rectangle from (0,-5) with dimensions (5,10) will become a
+ * rectangle from (0,0) with dimensions (5,5).
+ *
+ * If after negative x/y adjustment to 0, either the width or height
+ * is negative, then the rectangle is completely offscreen, and
+ * nothing is drawn -- false is returned.
+ *
+ * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX,
+ * the width and height are clamped such x+width or y+height are equal
+ * to CAIRO_COORD_MAX, and true is returned.
+ */
+static bool ConditionRect(Rect& r) {
+ // if either x or y is way out of bounds;
+ // note that we don't handle negative w/h here
+ if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) return false;
+
+ if (r.X() < 0.f) {
+ r.SetWidth(r.XMost());
+ if (r.Width() < 0.f) return false;
+ r.MoveToX(0.f);
+ }
+
+ if (r.XMost() > CAIRO_COORD_MAX) {
+ r.SetRightEdge(CAIRO_COORD_MAX);
+ }
+
+ if (r.Y() < 0.f) {
+ r.SetHeight(r.YMost());
+ if (r.Height() < 0.f) return false;
+
+ r.MoveToY(0.f);
+ }
+
+ if (r.YMost() > CAIRO_COORD_MAX) {
+ r.SetBottomEdge(CAIRO_COORD_MAX);
+ }
+ return true;
+}
+
+} // end anonymous namespace
+
+static bool SupportsSelfCopy(cairo_surface_t* surface) {
+ switch (cairo_surface_get_type(surface)) {
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+ case CAIRO_SURFACE_TYPE_QUARTZ:
+ return true;
+#endif
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ case CAIRO_SURFACE_TYPE_WIN32:
+ case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
+ return true;
+#endif
+ default:
+ return false;
+ }
+}
+
+static bool PatternIsCompatible(const Pattern& aPattern) {
+ switch (aPattern.GetType()) {
+ case PatternType::LINEAR_GRADIENT: {
+ const LinearGradientPattern& pattern =
+ static_cast<const LinearGradientPattern&>(aPattern);
+ return pattern.mStops->GetBackendType() == BackendType::CAIRO;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ const RadialGradientPattern& pattern =
+ static_cast<const RadialGradientPattern&>(aPattern);
+ return pattern.mStops->GetBackendType() == BackendType::CAIRO;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ const ConicGradientPattern& pattern =
+ static_cast<const ConicGradientPattern&>(aPattern);
+ return pattern.mStops->GetBackendType() == BackendType::CAIRO;
+ }
+ default:
+ return true;
+ }
+}
+
+static cairo_user_data_key_t surfaceDataKey;
+
+static void ReleaseData(void* aData) {
+ DataSourceSurface* data = static_cast<DataSourceSurface*>(aData);
+ data->Unmap();
+ data->Release();
+}
+
+static cairo_surface_t* CopyToImageSurface(unsigned char* aData,
+ const IntRect& aRect,
+ int32_t aStride,
+ SurfaceFormat aFormat) {
+ MOZ_ASSERT(aData);
+
+ auto aRectWidth = aRect.Width();
+ auto aRectHeight = aRect.Height();
+
+ cairo_surface_t* surf = cairo_image_surface_create(
+ GfxFormatToCairoFormat(aFormat), aRectWidth, aRectHeight);
+ // In certain scenarios, requesting larger than 8k image fails. Bug 803568
+ // covers the details of how to run into it, but the full detailed
+ // investigation hasn't been done to determine the underlying cause. We
+ // will just handle the failure to allocate the surface to avoid a crash.
+ if (cairo_surface_status(surf)) {
+ gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf);
+ return nullptr;
+ }
+
+ unsigned char* surfData = cairo_image_surface_get_data(surf);
+ int surfStride = cairo_image_surface_get_stride(surf);
+ int32_t pixelWidth = BytesPerPixel(aFormat);
+
+ unsigned char* source = aData + aRect.Y() * aStride + aRect.X() * pixelWidth;
+
+ MOZ_ASSERT(aStride >= aRectWidth * pixelWidth);
+ for (int32_t y = 0; y < aRectHeight; ++y) {
+ memcpy(surfData + y * surfStride, source + y * aStride,
+ aRectWidth * pixelWidth);
+ }
+ cairo_surface_mark_dirty(surf);
+ return surf;
+}
+
+/**
+ * If aSurface can be represented as a surface of type
+ * CAIRO_SURFACE_TYPE_IMAGE then returns that surface. Does
+ * not add a reference.
+ */
+static cairo_surface_t* GetAsImageSurface(cairo_surface_t* aSurface) {
+ if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
+ return aSurface;
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ } else if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) {
+ return cairo_win32_surface_get_image(aSurface);
+#endif
+ }
+
+ return nullptr;
+}
+
+static cairo_surface_t* CreateSubImageForData(unsigned char* aData,
+ const IntRect& aRect, int aStride,
+ SurfaceFormat aFormat) {
+ if (!aData) {
+ gfxWarning() << "DrawTargetCairo.CreateSubImageForData null aData";
+ return nullptr;
+ }
+ unsigned char* data =
+ aData + aRect.Y() * aStride + aRect.X() * BytesPerPixel(aFormat);
+
+ cairo_surface_t* image = cairo_image_surface_create_for_data(
+ data, GfxFormatToCairoFormat(aFormat), aRect.Width(), aRect.Height(),
+ aStride);
+ // Set the subimage's device offset so that in remains in the same place
+ // relative to the parent
+ cairo_surface_set_device_offset(image, -aRect.X(), -aRect.Y());
+ return image;
+}
+
+/**
+ * Returns a referenced cairo_surface_t representing the
+ * sub-image specified by aSubImage.
+ */
+static cairo_surface_t* ExtractSubImage(cairo_surface_t* aSurface,
+ const IntRect& aSubImage,
+ SurfaceFormat aFormat) {
+ // No need to worry about retaining a reference to the original
+ // surface since the only caller of this function guarantees
+ // that aSurface will stay alive as long as the result
+
+ cairo_surface_t* image = GetAsImageSurface(aSurface);
+ if (image) {
+ image =
+ CreateSubImageForData(cairo_image_surface_get_data(image), aSubImage,
+ cairo_image_surface_get_stride(image), aFormat);
+ return image;
+ }
+
+ cairo_surface_t* similar = cairo_surface_create_similar(
+ aSurface, cairo_surface_get_content(aSurface), aSubImage.Width(),
+ aSubImage.Height());
+
+ cairo_t* ctx = cairo_create(similar);
+ cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface(ctx, aSurface, -aSubImage.X(), -aSubImage.Y());
+ cairo_paint(ctx);
+ cairo_destroy(ctx);
+
+ cairo_surface_set_device_offset(similar, -aSubImage.X(), -aSubImage.Y());
+ return similar;
+}
+
+/**
+ * Returns cairo surface for the given SourceSurface.
+ * If possible, it will use the cairo_surface associated with aSurface,
+ * otherwise, it will create a new cairo_surface.
+ * In either case, the caller must call cairo_surface_destroy on the
+ * result when it is done with it.
+ */
+static cairo_surface_t* GetCairoSurfaceForSourceSurface(
+ SourceSurface* aSurface, bool aExistingOnly = false,
+ const IntRect& aSubImage = IntRect()) {
+ if (!aSurface) {
+ return nullptr;
+ }
+
+ IntRect subimage = IntRect(IntPoint(), aSurface->GetSize());
+ if (!aSubImage.IsEmpty()) {
+ MOZ_ASSERT(!aExistingOnly);
+ MOZ_ASSERT(subimage.Contains(aSubImage));
+ subimage = aSubImage;
+ }
+
+ if (aSurface->GetType() == SurfaceType::CAIRO) {
+ cairo_surface_t* surf =
+ static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface();
+ if (aSubImage.IsEmpty()) {
+ cairo_surface_reference(surf);
+ } else {
+ surf = ExtractSubImage(surf, subimage, aSurface->GetFormat());
+ }
+ return surf;
+ }
+
+ if (aSurface->GetType() == SurfaceType::CAIRO_IMAGE) {
+ cairo_surface_t* surf =
+ static_cast<const DataSourceSurfaceCairo*>(aSurface)->GetSurface();
+ if (aSubImage.IsEmpty()) {
+ cairo_surface_reference(surf);
+ } else {
+ surf = ExtractSubImage(surf, subimage, aSurface->GetFormat());
+ }
+ return surf;
+ }
+
+ if (aExistingOnly) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+ if (!data) {
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!data->Map(DataSourceSurface::READ, &map)) {
+ return nullptr;
+ }
+
+ cairo_surface_t* surf = CreateSubImageForData(map.mData, subimage,
+ map.mStride, data->GetFormat());
+
+ // In certain scenarios, requesting larger than 8k image fails. Bug 803568
+ // covers the details of how to run into it, but the full detailed
+ // investigation hasn't been done to determine the underlying cause. We
+ // will just handle the failure to allocate the surface to avoid a crash.
+ if (!surf || cairo_surface_status(surf)) {
+ if (surf && (cairo_surface_status(surf) == CAIRO_STATUS_INVALID_STRIDE)) {
+ // If we failed because of an invalid stride then copy into
+ // a new surface with a stride that cairo chooses. No need to
+ // set user data since we're not dependent on the original
+ // data.
+ cairo_surface_t* result = CopyToImageSurface(
+ map.mData, subimage, map.mStride, data->GetFormat());
+ data->Unmap();
+ return result;
+ }
+ data->Unmap();
+ return nullptr;
+ }
+
+ cairo_surface_set_user_data(surf, &surfaceDataKey, data.forget().take(),
+ ReleaseData);
+ return surf;
+}
+
+// An RAII class to temporarily clear any device offset set
+// on a surface. Note that this does not take a reference to the
+// surface.
+class AutoClearDeviceOffset final {
+ public:
+ explicit AutoClearDeviceOffset(SourceSurface* aSurface)
+ : mSurface(nullptr), mX(0), mY(0) {
+ Init(aSurface);
+ }
+
+ explicit AutoClearDeviceOffset(const Pattern& aPattern)
+ : mSurface(nullptr), mX(0.0), mY(0.0) {
+ if (aPattern.GetType() == PatternType::SURFACE) {
+ const SurfacePattern& pattern =
+ static_cast<const SurfacePattern&>(aPattern);
+ Init(pattern.mSurface);
+ }
+ }
+
+ ~AutoClearDeviceOffset() {
+ if (mSurface) {
+ cairo_surface_set_device_offset(mSurface, mX, mY);
+ }
+ }
+
+ private:
+ void Init(SourceSurface* aSurface) {
+ cairo_surface_t* surface = GetCairoSurfaceForSourceSurface(aSurface, true);
+ if (surface) {
+ Init(surface);
+ cairo_surface_destroy(surface);
+ }
+ }
+
+ void Init(cairo_surface_t* aSurface) {
+ mSurface = aSurface;
+ cairo_surface_get_device_offset(mSurface, &mX, &mY);
+ cairo_surface_set_device_offset(mSurface, 0, 0);
+ }
+
+ cairo_surface_t* mSurface;
+ double mX;
+ double mY;
+};
+
+static inline void CairoPatternAddGradientStop(cairo_pattern_t* aPattern,
+ const GradientStop& aStop,
+ Float aNudge = 0) {
+ cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge,
+ aStop.color.r, aStop.color.g, aStop.color.b,
+ aStop.color.a);
+}
+
+// Never returns nullptr. As such, you must always pass in Cairo-compatible
+// patterns, most notably gradients with a GradientStopCairo.
+// The pattern returned must have cairo_pattern_destroy() called on it by the
+// caller.
+// As the cairo_pattern_t returned may depend on the Pattern passed in, the
+// lifetime of the cairo_pattern_t returned must not exceed the lifetime of the
+// Pattern passed in.
+static cairo_pattern_t* GfxPatternToCairoPattern(const Pattern& aPattern,
+ Float aAlpha,
+ const Matrix& aTransform) {
+ cairo_pattern_t* pat;
+ const Matrix* matrix = nullptr;
+
+ switch (aPattern.GetType()) {
+ case PatternType::COLOR: {
+ DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor;
+ pat = cairo_pattern_create_rgba(color.r, color.g, color.b,
+ color.a * aAlpha);
+ break;
+ }
+
+ case PatternType::SURFACE: {
+ const SurfacePattern& pattern =
+ static_cast<const SurfacePattern&>(aPattern);
+ cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(
+ pattern.mSurface, false, pattern.mSamplingRect);
+ if (!surf) return nullptr;
+
+ pat = cairo_pattern_create_for_surface(surf);
+
+ matrix = &pattern.mMatrix;
+
+ cairo_pattern_set_filter(
+ pat, GfxSamplingFilterToCairoFilter(pattern.mSamplingFilter));
+ cairo_pattern_set_extend(pat,
+ GfxExtendToCairoExtend(pattern.mExtendMode));
+
+ cairo_surface_destroy(surf);
+ break;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ const LinearGradientPattern& pattern =
+ static_cast<const LinearGradientPattern&>(aPattern);
+
+ pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y,
+ pattern.mEnd.x, pattern.mEnd.y);
+
+ MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
+ GradientStopsCairo* cairoStops =
+ static_cast<GradientStopsCairo*>(pattern.mStops.get());
+ cairo_pattern_set_extend(
+ pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
+
+ matrix = &pattern.mMatrix;
+
+ const std::vector<GradientStop>& stops = cairoStops->GetStops();
+ for (size_t i = 0; i < stops.size(); ++i) {
+ CairoPatternAddGradientStop(pat, stops[i]);
+ }
+
+ break;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ const RadialGradientPattern& pattern =
+ static_cast<const RadialGradientPattern&>(aPattern);
+
+ pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y,
+ pattern.mRadius1, pattern.mCenter2.x,
+ pattern.mCenter2.y, pattern.mRadius2);
+
+ MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
+ GradientStopsCairo* cairoStops =
+ static_cast<GradientStopsCairo*>(pattern.mStops.get());
+ cairo_pattern_set_extend(
+ pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
+
+ matrix = &pattern.mMatrix;
+
+ const std::vector<GradientStop>& stops = cairoStops->GetStops();
+ for (size_t i = 0; i < stops.size(); ++i) {
+ CairoPatternAddGradientStop(pat, stops[i]);
+ }
+
+ break;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ // XXX(ntim): Bug 1617039 - Implement conic-gradient for Cairo
+ pat = cairo_pattern_create_rgba(0.0, 0.0, 0.0, 0.0);
+
+ break;
+ }
+ default: {
+ // We should support all pattern types!
+ MOZ_ASSERT(false);
+ }
+ }
+
+ // The pattern matrix is a matrix that transforms the pattern into user
+ // space. Cairo takes a matrix that converts from user space to pattern
+ // space. Cairo therefore needs the inverse.
+ if (matrix) {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(*matrix, mat);
+ cairo_matrix_invert(&mat);
+ cairo_pattern_set_matrix(pat, &mat);
+ }
+
+ return pat;
+}
+
+static bool NeedIntermediateSurface(const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ // We pre-multiply colours' alpha by the global alpha, so we don't need to
+ // use an intermediate surface for them.
+ if (aPattern.GetType() == PatternType::COLOR) return false;
+
+ if (aOptions.mAlpha == 1.0) return false;
+
+ return true;
+}
+
+DrawTargetCairo::DrawTargetCairo()
+ : mContext(nullptr),
+ mSurface(nullptr),
+ mTransformSingular(false),
+ mLockedBits(nullptr),
+ mFontOptions(nullptr) {}
+
+DrawTargetCairo::~DrawTargetCairo() {
+ cairo_destroy(mContext);
+ if (mSurface) {
+ cairo_surface_destroy(mSurface);
+ mSurface = nullptr;
+ }
+ if (mFontOptions) {
+ cairo_font_options_destroy(mFontOptions);
+ mFontOptions = nullptr;
+ }
+ MOZ_ASSERT(!mLockedBits);
+}
+
+bool DrawTargetCairo::IsValid() const {
+ return mSurface && !cairo_surface_status(mSurface) && mContext &&
+ !cairo_surface_status(cairo_get_group_target(mContext));
+}
+
+DrawTargetType DrawTargetCairo::GetType() const {
+ if (mContext) {
+ cairo_surface_type_t type = cairo_surface_get_type(mSurface);
+ if (type == CAIRO_SURFACE_TYPE_TEE) {
+ type = cairo_surface_get_type(cairo_tee_surface_index(mSurface, 0));
+ MOZ_ASSERT(type != CAIRO_SURFACE_TYPE_TEE, "C'mon!");
+ MOZ_ASSERT(
+ type == cairo_surface_get_type(cairo_tee_surface_index(mSurface, 1)),
+ "What should we do here?");
+ }
+ switch (type) {
+ case CAIRO_SURFACE_TYPE_PDF:
+ case CAIRO_SURFACE_TYPE_PS:
+ case CAIRO_SURFACE_TYPE_SVG:
+ case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
+ case CAIRO_SURFACE_TYPE_XML:
+ return DrawTargetType::VECTOR;
+
+ case CAIRO_SURFACE_TYPE_VG:
+ case CAIRO_SURFACE_TYPE_GL:
+ case CAIRO_SURFACE_TYPE_GLITZ:
+ case CAIRO_SURFACE_TYPE_QUARTZ:
+ case CAIRO_SURFACE_TYPE_DIRECTFB:
+ return DrawTargetType::HARDWARE_RASTER;
+
+ case CAIRO_SURFACE_TYPE_SKIA:
+ case CAIRO_SURFACE_TYPE_QT:
+ MOZ_FALLTHROUGH_ASSERT(
+ "Can't determine actual DrawTargetType for DrawTargetCairo - "
+ "assuming SOFTWARE_RASTER");
+ case CAIRO_SURFACE_TYPE_IMAGE:
+ case CAIRO_SURFACE_TYPE_XLIB:
+ case CAIRO_SURFACE_TYPE_XCB:
+ case CAIRO_SURFACE_TYPE_WIN32:
+ case CAIRO_SURFACE_TYPE_BEOS:
+ case CAIRO_SURFACE_TYPE_OS2:
+ case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE:
+ case CAIRO_SURFACE_TYPE_SCRIPT:
+ case CAIRO_SURFACE_TYPE_RECORDING:
+ case CAIRO_SURFACE_TYPE_DRM:
+ case CAIRO_SURFACE_TYPE_SUBSURFACE:
+ case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about
+ // unhandled enum value
+ return DrawTargetType::SOFTWARE_RASTER;
+ default:
+ MOZ_CRASH("GFX: Unsupported cairo surface type");
+ }
+ }
+ MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo");
+ return DrawTargetType::SOFTWARE_RASTER;
+}
+
+IntSize DrawTargetCairo::GetSize() const { return mSize; }
+
+SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface) {
+ cairo_surface_type_t type = cairo_surface_get_type(surface);
+ if (type == CAIRO_SURFACE_TYPE_IMAGE) {
+ return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface));
+ }
+#ifdef CAIRO_HAS_XLIB_SURFACE
+ // xlib is currently the only Cairo backend that creates 16bpp surfaces
+ if (type == CAIRO_SURFACE_TYPE_XLIB &&
+ cairo_xlib_surface_get_depth(surface) == 16) {
+ return SurfaceFormat::R5G6B5_UINT16;
+ }
+#endif
+ return CairoContentToGfxFormat(cairo_surface_get_content(surface));
+}
+
+void DrawTargetCairo::Link(const char* aDestination, const Rect& aRect) {
+ if (!aDestination || !*aDestination) {
+ // No destination? Just bail out.
+ return;
+ }
+
+ // We need to \-escape any single-quotes in the destination string, in order
+ // to pass it via the attributes arg to cairo_tag_begin.
+ //
+ // We also need to escape any backslashes (bug 1748077), as per doc at
+ // https://www.cairographics.org/manual/cairo-Tags-and-Links.html#cairo-tag-begin
+ // The cairo-pdf-interchange backend (used on all platforms EXCEPT macOS)
+ // actually requires that we *doubly* escape the backslashes (this may be a
+ // cairo bug), while the quartz backend is fine with them singly-escaped.
+ //
+ // (Encoding of non-ASCII chars etc gets handled later by the PDF backend.)
+ nsAutoCString dest(aDestination);
+ for (size_t i = dest.Length(); i > 0;) {
+ --i;
+ if (dest[i] == '\'') {
+ dest.ReplaceLiteral(i, 1, "\\'");
+ } else if (dest[i] == '\\') {
+#ifdef XP_MACOSX
+ dest.ReplaceLiteral(i, 1, "\\\\");
+#else
+ dest.ReplaceLiteral(i, 1, "\\\\\\\\");
+#endif
+ }
+ }
+
+ double x = aRect.x, y = aRect.y, w = aRect.width, h = aRect.height;
+ cairo_user_to_device(mContext, &x, &y);
+ cairo_user_to_device_distance(mContext, &w, &h);
+
+ nsPrintfCString attributes("rect=[%f %f %f %f] ", x, y, w, h);
+ if (dest[0] == '#') {
+ // The actual destination does not have a leading '#'.
+ attributes.AppendPrintf("dest='%s'", dest.get() + 1);
+ } else {
+ attributes.AppendPrintf("uri='%s'", dest.get());
+ }
+
+ // We generate a begin/end pair with no content in between, because we are
+ // using the rect attribute of the begin tag to specify the link region
+ // rather than depending on cairo to accumulate the painted area.
+ cairo_tag_begin(mContext, CAIRO_TAG_LINK, attributes.get());
+ cairo_tag_end(mContext, CAIRO_TAG_LINK);
+}
+
+void DrawTargetCairo::Destination(const char* aDestination,
+ const Point& aPoint) {
+ if (!aDestination || !*aDestination) {
+ // No destination? Just bail out.
+ return;
+ }
+
+ nsAutoCString dest(aDestination);
+ for (size_t i = dest.Length(); i > 0;) {
+ --i;
+ if (dest[i] == '\'') {
+ dest.ReplaceLiteral(i, 1, "\\'");
+ }
+ }
+
+ double x = aPoint.x, y = aPoint.y;
+ cairo_user_to_device(mContext, &x, &y);
+
+ nsPrintfCString attributes("name='%s' x=%f y=%f internal", dest.get(), x, y);
+ cairo_tag_begin(mContext, CAIRO_TAG_DEST, attributes.get());
+ cairo_tag_end(mContext, CAIRO_TAG_DEST);
+}
+
+already_AddRefed<SourceSurface> DrawTargetCairo::Snapshot() {
+ if (!IsValid()) {
+ gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface "
+ << hexa(mSurface) << ", context " << hexa(mContext)
+ << ", status "
+ << (mSurface ? cairo_surface_status(mSurface) : -1);
+ return nullptr;
+ }
+ if (mSnapshot) {
+ RefPtr<SourceSurface> snapshot(mSnapshot);
+ return snapshot.forget();
+ }
+
+ IntSize size = GetSize();
+
+ mSnapshot = new SourceSurfaceCairo(mSurface, size,
+ GfxFormatForCairoSurface(mSurface), this);
+ RefPtr<SourceSurface> snapshot(mSnapshot);
+ return snapshot.forget();
+}
+
+bool DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize,
+ int32_t* aStride, SurfaceFormat* aFormat,
+ IntPoint* aOrigin) {
+ cairo_surface_t* target = cairo_get_group_target(mContext);
+ cairo_surface_t* surf = target;
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) {
+ cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf);
+ if (imgsurf) {
+ surf = imgsurf;
+ }
+ }
+#endif
+ if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE &&
+ cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) {
+ PointDouble offset;
+ cairo_surface_get_device_offset(target, &offset.x.value, &offset.y.value);
+ // verify the device offset can be converted to integers suitable for a
+ // bounds rect
+ IntPoint origin(int32_t(-offset.x), int32_t(-offset.y));
+ if (-PointDouble(origin) != offset || (!aOrigin && origin != IntPoint())) {
+ return false;
+ }
+
+ WillChange();
+ Flush();
+
+ mLockedBits = cairo_image_surface_get_data(surf);
+ *aData = mLockedBits;
+ *aSize = IntSize(cairo_image_surface_get_width(surf),
+ cairo_image_surface_get_height(surf));
+ *aStride = cairo_image_surface_get_stride(surf);
+ *aFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(surf));
+ if (aOrigin) {
+ *aOrigin = origin;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void DrawTargetCairo::ReleaseBits(uint8_t* aData) {
+ MOZ_ASSERT(mLockedBits == aData);
+ mLockedBits = nullptr;
+ cairo_surface_t* surf = cairo_get_group_target(mContext);
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) {
+ cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf);
+ if (imgsurf) {
+ cairo_surface_mark_dirty(imgsurf);
+ }
+ }
+#endif
+ cairo_surface_mark_dirty(surf);
+}
+
+void DrawTargetCairo::Flush() {
+ cairo_surface_t* surf = cairo_get_group_target(mContext);
+ cairo_surface_flush(surf);
+}
+
+void DrawTargetCairo::PrepareForDrawing(cairo_t* aContext,
+ const Path* aPath /* = nullptr */) {
+ WillChange(aPath);
+}
+
+cairo_surface_t* DrawTargetCairo::GetDummySurface() {
+ if (mDummySurface) {
+ return mDummySurface;
+ }
+
+ mDummySurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+
+ return mDummySurface;
+}
+
+static void PaintWithAlpha(cairo_t* aContext, const DrawOptions& aOptions) {
+ if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) {
+ // Cairo treats the source operator like a lerp when alpha is < 1.
+ // Approximate the desired operator by: out = 0; out += src*alpha;
+ if (aOptions.mAlpha == 1) {
+ cairo_set_operator(aContext, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(aContext);
+ } else {
+ cairo_set_operator(aContext, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(aContext);
+ cairo_set_operator(aContext, CAIRO_OPERATOR_ADD);
+ cairo_paint_with_alpha(aContext, aOptions.mAlpha);
+ }
+ } else {
+ cairo_set_operator(aContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+ cairo_paint_with_alpha(aContext, aOptions.mAlpha);
+ }
+}
+
+void DrawTargetCairo::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource,
+ const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) {
+ if (mTransformSingular || aDest.IsEmpty()) {
+ return;
+ }
+
+ if (!IsValid() || !aSurface) {
+ gfxCriticalNote << "DrawSurface with bad surface "
+ << cairo_surface_status(cairo_get_group_target(mContext));
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+ AutoClearDeviceOffset clear(aSurface);
+
+ float sx = aSource.Width() / aDest.Width();
+ float sy = aSource.Height() / aDest.Height();
+
+ cairo_matrix_t src_mat;
+ cairo_matrix_init_translate(&src_mat, aSource.X() - aSurface->GetRect().x,
+ aSource.Y() - aSurface->GetRect().y);
+ cairo_matrix_scale(&src_mat, sx, sy);
+
+ cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
+ if (!surf) {
+ gfxWarning()
+ << "Failed to create cairo surface for DrawTargetCairo::DrawSurface";
+ return;
+ }
+ cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
+ cairo_surface_destroy(surf);
+
+ cairo_pattern_set_matrix(pat, &src_mat);
+ cairo_pattern_set_filter(
+ pat, GfxSamplingFilterToCairoFilter(aSurfOptions.mSamplingFilter));
+ // For PDF output, we avoid using EXTEND_PAD here because floating-point
+ // error accumulation may lead cairo_pdf_surface to conclude that padding
+ // is needed due to an apparent one- or two-pixel mismatch between source
+ // pattern and destination rect sizes when we're rendering a pdf.js page,
+ // and this forces undesirable fallback to the rasterization codepath
+ // instead of simply replaying the recording.
+ // (See bug 1777209.)
+ cairo_pattern_set_extend(
+ pat, cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_PDF
+ ? CAIRO_EXTEND_NONE
+ : CAIRO_EXTEND_PAD);
+
+ cairo_set_antialias(mContext,
+ GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
+
+ // If the destination rect covers the entire clipped area, then unbounded and
+ // bounded operations are identical, and we don't need to push a group.
+ bool needsGroup = !IsOperatorBoundByMask(aOptions.mCompositionOp) &&
+ !aDest.Contains(GetUserSpaceClip());
+
+ cairo_translate(mContext, aDest.X(), aDest.Y());
+
+ if (needsGroup) {
+ cairo_push_group(mContext);
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
+ cairo_set_source(mContext, pat);
+ cairo_fill(mContext);
+ cairo_pop_group_to_source(mContext);
+ } else {
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
+ cairo_clip(mContext);
+ cairo_set_source(mContext, pat);
+ }
+
+ PaintWithAlpha(mContext, aOptions);
+
+ cairo_pattern_destroy(pat);
+}
+
+void DrawTargetCairo::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
+ filter->Draw(this, aSourceRect, aDestPoint, aOptions);
+}
+
+void DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface* aSurface,
+ const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOperator) {
+ if (!IsValid() || !aSurface) {
+ gfxCriticalNote << "DrawSurfaceWithShadow with bad surface "
+ << cairo_surface_status(cairo_get_group_target(mContext));
+ return;
+ }
+
+ if (aSurface->GetType() != SurfaceType::CAIRO) {
+ return;
+ }
+
+ AutoClearDeviceOffset clear(aSurface);
+
+ Float width = Float(aSurface->GetSize().width);
+ Float height = Float(aSurface->GetSize().height);
+
+ SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface);
+ cairo_surface_t* sourcesurf = source->GetSurface();
+ cairo_surface_t* blursurf;
+ cairo_surface_t* surf;
+
+ // We only use the A8 surface for blurred shadows. Unblurred shadows can just
+ // use the RGBA surface directly.
+ if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) {
+ blursurf = cairo_tee_surface_index(sourcesurf, 0);
+ surf = cairo_tee_surface_index(sourcesurf, 1);
+ } else {
+ blursurf = sourcesurf;
+ surf = sourcesurf;
+ }
+
+ if (aShadow.mSigma != 0.0f) {
+ MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE);
+ Rect extents(0, 0, width, height);
+ AlphaBoxBlur blur(extents, cairo_image_surface_get_stride(blursurf),
+ aShadow.mSigma, aShadow.mSigma);
+ blur.Blur(cairo_image_surface_get_data(blursurf));
+ }
+
+ WillChange();
+ ClearSurfaceForUnboundedSource(aOperator);
+
+ cairo_save(mContext);
+ cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
+ cairo_identity_matrix(mContext);
+ cairo_translate(mContext, aDest.x, aDest.y);
+
+ bool needsGroup = !IsOperatorBoundByMask(aOperator);
+ if (needsGroup) {
+ cairo_push_group(mContext);
+ }
+
+ cairo_set_source_rgba(mContext, aShadow.mColor.r, aShadow.mColor.g,
+ aShadow.mColor.b, aShadow.mColor.a);
+ cairo_mask_surface(mContext, blursurf, aShadow.mOffset.x, aShadow.mOffset.y);
+
+ if (blursurf != surf || aSurface->GetFormat() != SurfaceFormat::A8) {
+ // Now that the shadow has been drawn, we can draw the surface on top.
+ cairo_set_source_surface(mContext, surf, 0, 0);
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, 0, 0, width, height);
+ cairo_fill(mContext);
+ }
+
+ if (needsGroup) {
+ cairo_pop_group_to_source(mContext);
+ cairo_paint(mContext);
+ }
+
+ cairo_restore(mContext);
+}
+
+void DrawTargetCairo::DrawPattern(const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions,
+ DrawPatternType aDrawType,
+ bool aPathBoundsClip) {
+ if (!PatternIsCompatible(aPattern)) {
+ return;
+ }
+
+ AutoClearDeviceOffset clear(aPattern);
+
+ cairo_pattern_t* pat =
+ GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
+ if (!pat) {
+ return;
+ }
+ if (cairo_pattern_status(pat)) {
+ cairo_pattern_destroy(pat);
+ gfxWarning() << "Invalid pattern";
+ return;
+ }
+
+ cairo_set_source(mContext, pat);
+
+ cairo_set_antialias(mContext,
+ GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
+
+ if (NeedIntermediateSurface(aPattern, aOptions) ||
+ (!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) {
+ cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
+
+ // Don't want operators to be applied twice
+ cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+
+ if (aDrawType == DRAW_STROKE) {
+ SetCairoStrokeOptions(mContext, aStrokeOptions);
+ cairo_stroke_preserve(mContext);
+ } else {
+ cairo_fill_preserve(mContext);
+ }
+
+ cairo_pop_group_to_source(mContext);
+
+ // Now draw the content using the desired operator
+ PaintWithAlpha(mContext, aOptions);
+ } else {
+ cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+
+ if (aDrawType == DRAW_STROKE) {
+ SetCairoStrokeOptions(mContext, aStrokeOptions);
+ cairo_stroke_preserve(mContext);
+ } else {
+ cairo_fill_preserve(mContext);
+ }
+ }
+
+ cairo_pattern_destroy(pat);
+}
+
+void DrawTargetCairo::FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+
+ bool restoreTransform = false;
+ Matrix mat;
+ Rect r = aRect;
+
+ /* Clamp coordinates to work around a design bug in cairo */
+ if (r.Width() > CAIRO_COORD_MAX || r.Height() > CAIRO_COORD_MAX ||
+ r.X() < -CAIRO_COORD_MAX || r.X() > CAIRO_COORD_MAX ||
+ r.Y() < -CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) {
+ if (!mat.IsRectilinear()) {
+ gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect "
+ "with non-rectilinear transform";
+ }
+
+ mat = GetTransform();
+ r = mat.TransformBounds(r);
+
+ if (!ConditionRect(r)) {
+ gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with "
+ "out-of-bounds Rect";
+ return;
+ }
+
+ restoreTransform = true;
+ SetTransform(Matrix());
+ }
+
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, r.X(), r.Y(), r.Width(), r.Height());
+
+ bool pathBoundsClip = false;
+
+ if (r.Contains(GetUserSpaceClip())) {
+ pathBoundsClip = true;
+ }
+
+ DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip);
+
+ if (restoreTransform) {
+ SetTransform(mat);
+ }
+}
+
+void DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface,
+ const IntRect& aSource,
+ const IntPoint& aDest) {
+ if (cairo_surface_status(aSurface)) {
+ gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface);
+ return;
+ }
+
+ cairo_identity_matrix(mContext);
+
+ cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.X(),
+ aDest.y - aSource.Y());
+ cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE);
+ cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
+
+ cairo_reset_clip(mContext);
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, aDest.x, aDest.y, aSource.Width(),
+ aSource.Height());
+ cairo_fill(mContext);
+}
+
+void DrawTargetCairo::CopySurface(SourceSurface* aSurface,
+ const IntRect& aSource,
+ const IntPoint& aDest) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+ AutoClearDeviceOffset clear(aSurface);
+
+ if (!aSurface) {
+ gfxWarning() << "Unsupported surface type specified";
+ return;
+ }
+
+ cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
+ if (!surf) {
+ gfxWarning() << "Unsupported surface type specified";
+ return;
+ }
+
+ CopySurfaceInternal(surf, aSource, aDest);
+ cairo_surface_destroy(surf);
+}
+
+void DrawTargetCairo::CopyRect(const IntRect& aSource, const IntPoint& aDest) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+
+ IntRect source = aSource;
+ cairo_surface_t* surf = mSurface;
+
+ if (!SupportsSelfCopy(mSurface) && aSource.ContainsY(aDest.y)) {
+ cairo_surface_t* similar = cairo_surface_create_similar(
+ mSurface, GfxFormatToCairoContent(GetFormat()), aSource.Width(),
+ aSource.Height());
+ cairo_t* ctx = cairo_create(similar);
+ cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface(ctx, surf, -aSource.X(), -aSource.Y());
+ cairo_paint(ctx);
+ cairo_destroy(ctx);
+
+ source.MoveTo(0, 0);
+ surf = similar;
+ }
+
+ CopySurfaceInternal(surf, source, aDest);
+
+ if (surf != mSurface) {
+ cairo_surface_destroy(surf);
+ }
+}
+
+void DrawTargetCairo::ClearRect(const Rect& aRect) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+
+ if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 ||
+ !std::isfinite(aRect.X()) || !std::isfinite(aRect.Width()) ||
+ !std::isfinite(aRect.Y()) || !std::isfinite(aRect.Height())) {
+ gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext)
+ << " with " << aRect.Width() << "x" << aRect.Height()
+ << " [" << aRect.X() << ", " << aRect.Y() << "]";
+ }
+
+ cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
+ cairo_new_path(mContext);
+ cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
+ cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(),
+ aRect.Height());
+ cairo_fill(mContext);
+}
+
+void DrawTargetCairo::StrokeRect(
+ const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions /* = StrokeOptions() */,
+ const DrawOptions& aOptions /* = DrawOptions() */) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(),
+ aRect.Height());
+
+ DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
+}
+
+void DrawTargetCairo::StrokeLine(
+ const Point& aStart, const Point& aEnd, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions /* = StrokeOptions() */,
+ const DrawOptions& aOptions /* = DrawOptions() */) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+
+ cairo_new_path(mContext);
+ cairo_move_to(mContext, aStart.x, aStart.y);
+ cairo_line_to(mContext, aEnd.x, aEnd.y);
+
+ DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
+}
+
+void DrawTargetCairo::Stroke(
+ const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions /* = StrokeOptions() */,
+ const DrawOptions& aOptions /* = DrawOptions() */) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext, aPath);
+
+ if (aPath->GetBackendType() != BackendType::CAIRO) return;
+
+ PathCairo* path =
+ const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
+ path->SetPathOnContext(mContext);
+
+ DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
+}
+
+void DrawTargetCairo::Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions /* = DrawOptions() */) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext, aPath);
+
+ if (aPath->GetBackendType() != BackendType::CAIRO) return;
+
+ PathCairo* path =
+ const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
+ path->SetPathOnContext(mContext);
+
+ DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
+}
+
+bool DrawTargetCairo::IsCurrentGroupOpaque() {
+ cairo_surface_t* surf = cairo_get_group_target(mContext);
+
+ if (!surf) {
+ return false;
+ }
+
+ return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR;
+}
+
+void DrawTargetCairo::SetFontOptions(cairo_antialias_t aAAMode) {
+ // This will attempt to detect if the currently set scaled font on the
+ // context has enabled subpixel AA. If it is not permitted, then it will
+ // downgrade to grayscale AA.
+ // This only currently works effectively for the cairo-ft backend relative
+ // to system defaults, as only cairo-ft reflect system defaults in the scaled
+ // font state. However, this will work for cairo-ft on both tree Cairo and
+ // system Cairo.
+ // Other backends leave the CAIRO_ANTIALIAS_DEFAULT setting untouched while
+ // potentially interpreting it as subpixel or even other types of AA that
+ // can't be safely equivocated with grayscale AA. For this reason we don't
+ // try to also detect and modify the default AA setting, only explicit
+ // subpixel AA. These other backends must instead rely on tree Cairo's
+ // cairo_surface_set_subpixel_antialiasing extension.
+
+ // If allowing subpixel AA, then leave Cairo's default AA state.
+ if (mPermitSubpixelAA && aAAMode == CAIRO_ANTIALIAS_DEFAULT) {
+ return;
+ }
+
+ if (!mFontOptions) {
+ mFontOptions = cairo_font_options_create();
+ if (!mFontOptions) {
+ gfxWarning() << "Failed allocating Cairo font options";
+ return;
+ }
+ }
+
+ cairo_get_font_options(mContext, mFontOptions);
+ cairo_antialias_t oldAA = cairo_font_options_get_antialias(mFontOptions);
+ cairo_antialias_t newAA =
+ aAAMode == CAIRO_ANTIALIAS_DEFAULT ? oldAA : aAAMode;
+ // Nothing to change if switching to default AA.
+ if (newAA == CAIRO_ANTIALIAS_DEFAULT) {
+ return;
+ }
+ // If the current font requests subpixel AA, force it to gray since we don't
+ // allow subpixel AA.
+ if (!mPermitSubpixelAA && newAA == CAIRO_ANTIALIAS_SUBPIXEL) {
+ newAA = CAIRO_ANTIALIAS_GRAY;
+ }
+ // Only override old AA with lower levels of AA.
+ if (oldAA == CAIRO_ANTIALIAS_DEFAULT || (int)newAA < (int)oldAA) {
+ cairo_font_options_set_antialias(mFontOptions, newAA);
+ cairo_set_font_options(mContext, mFontOptions);
+ }
+}
+
+void DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
+ DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
+ cairo_surface_set_subpixel_antialiasing(
+ cairo_get_group_target(mContext),
+ aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED
+ : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
+}
+
+static bool SupportsVariationSettings(cairo_surface_t* surface) {
+ switch (cairo_surface_get_type(surface)) {
+ case CAIRO_SURFACE_TYPE_PDF:
+ case CAIRO_SURFACE_TYPE_PS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void DrawTargetCairo::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ if (!IsValid()) {
+ gfxDebug() << "FillGlyphs bad surface "
+ << cairo_surface_status(cairo_get_group_target(mContext));
+ return;
+ }
+
+ cairo_scaled_font_t* cairoScaledFont =
+ aFont ? aFont->GetCairoScaledFont() : nullptr;
+ if (!cairoScaledFont) {
+ gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font";
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+ AutoClearDeviceOffset clear(aPattern);
+
+ cairo_set_scaled_font(mContext, cairoScaledFont);
+
+ cairo_pattern_t* pat =
+ GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
+ if (!pat) return;
+
+ cairo_set_source(mContext, pat);
+ cairo_pattern_destroy(pat);
+
+ cairo_antialias_t aa = GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode);
+ cairo_set_antialias(mContext, aa);
+
+ // Override any font-specific options as necessary.
+ SetFontOptions(aa);
+
+ // Convert our GlyphBuffer into a vector of Cairo glyphs. This code can
+ // execute millions of times in short periods, so we want to avoid heap
+ // allocation whenever possible. So we use an inline vector capacity of 1024
+ // bytes (the maximum allowed by mozilla::Vector), which gives an inline
+ // length of 1024 / 24 = 42 elements, which is enough to typically avoid heap
+ // allocation in ~99% of cases.
+ Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs;
+ if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) {
+ gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed";
+ return;
+ }
+ for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
+ glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
+ glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
+ glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
+ }
+
+ if (!SupportsVariationSettings(mSurface) && aFont->HasVariationSettings() &&
+ StaticPrefs::print_font_variations_as_paths()) {
+ cairo_set_fill_rule(mContext, CAIRO_FILL_RULE_WINDING);
+ cairo_new_path(mContext);
+ cairo_glyph_path(mContext, &glyphs[0], aBuffer.mNumGlyphs);
+ cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+ cairo_fill(mContext);
+ } else {
+ cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs);
+ }
+
+ if (cairo_surface_status(cairo_get_group_target(mContext))) {
+ gfxDebug() << "Ending FillGlyphs with a bad surface "
+ << cairo_surface_status(cairo_get_group_target(mContext));
+ }
+}
+
+void DrawTargetCairo::Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions /* = DrawOptions() */) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+ AutoClearDeviceOffset clearSource(aSource);
+ AutoClearDeviceOffset clearMask(aMask);
+
+ cairo_set_antialias(mContext,
+ GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
+
+ cairo_pattern_t* source =
+ GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
+ if (!source) {
+ return;
+ }
+
+ cairo_pattern_t* mask =
+ GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform());
+ if (!mask) {
+ cairo_pattern_destroy(source);
+ return;
+ }
+
+ if (cairo_pattern_status(source) || cairo_pattern_status(mask)) {
+ cairo_pattern_destroy(source);
+ cairo_pattern_destroy(mask);
+ gfxWarning() << "Invalid pattern";
+ return;
+ }
+
+ cairo_set_source(mContext, source);
+ cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+ cairo_mask(mContext, mask);
+
+ cairo_pattern_destroy(mask);
+ cairo_pattern_destroy(source);
+}
+
+void DrawTargetCairo::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
+ Point aOffset, const DrawOptions& aOptions) {
+ if (mTransformSingular) {
+ return;
+ }
+
+ AutoPrepareForDrawing prep(this, mContext);
+ AutoClearDeviceOffset clearSource(aSource);
+ AutoClearDeviceOffset clearMask(aMask);
+
+ if (!PatternIsCompatible(aSource)) {
+ return;
+ }
+
+ cairo_set_antialias(mContext,
+ GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
+
+ cairo_pattern_t* pat =
+ GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
+ if (!pat) {
+ return;
+ }
+
+ if (cairo_pattern_status(pat)) {
+ cairo_pattern_destroy(pat);
+ gfxWarning() << "Invalid pattern";
+ return;
+ }
+
+ cairo_set_source(mContext, pat);
+
+ if (NeedIntermediateSurface(aSource, aOptions)) {
+ cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
+
+ // Don't want operators to be applied twice
+ cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+
+ // Now draw the content using the desired operator
+ cairo_paint_with_alpha(mContext, aOptions.mAlpha);
+
+ cairo_pop_group_to_source(mContext);
+ }
+
+ cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask);
+ if (!surf) {
+ cairo_pattern_destroy(pat);
+ return;
+ }
+ cairo_pattern_t* mask = cairo_pattern_create_for_surface(surf);
+ cairo_matrix_t matrix;
+
+ cairo_matrix_init_translate(&matrix, -aOffset.x - aMask->GetRect().x,
+ -aOffset.y - aMask->GetRect().y);
+ cairo_pattern_set_matrix(mask, &matrix);
+
+ cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+
+ cairo_mask(mContext, mask);
+
+ cairo_surface_destroy(surf);
+ cairo_pattern_destroy(mask);
+ cairo_pattern_destroy(pat);
+}
+
+void DrawTargetCairo::PushClip(const Path* aPath) {
+ if (aPath->GetBackendType() != BackendType::CAIRO) {
+ return;
+ }
+
+ WillChange(aPath);
+ cairo_save(mContext);
+
+ PathCairo* path =
+ const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
+
+ if (mTransformSingular) {
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, 0, 0, 0, 0);
+ } else {
+ path->SetPathOnContext(mContext);
+ }
+ cairo_clip_preserve(mContext);
+}
+
+void DrawTargetCairo::PushClipRect(const Rect& aRect) {
+ WillChange();
+ cairo_save(mContext);
+
+ cairo_new_path(mContext);
+ if (mTransformSingular) {
+ cairo_rectangle(mContext, 0, 0, 0, 0);
+ } else {
+ cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(),
+ aRect.Height());
+ }
+ cairo_clip_preserve(mContext);
+}
+
+void DrawTargetCairo::PopClip() {
+ // save/restore does not affect the path, so no need to call WillChange()
+
+ // cairo_restore will restore the transform too and we don't want to do that
+ // so we'll save it now and restore it after the cairo_restore
+ cairo_matrix_t mat;
+ cairo_get_matrix(mContext, &mat);
+
+ cairo_restore(mContext);
+
+ cairo_set_matrix(mContext, &mat);
+}
+
+void DrawTargetCairo::PushLayer(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground) {
+ PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
+ aCopyBackground, CompositionOp::OP_OVER);
+}
+
+void DrawTargetCairo::PushLayerWithBlend(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds,
+ bool aCopyBackground,
+ CompositionOp aCompositionOp) {
+ cairo_content_t content = CAIRO_CONTENT_COLOR_ALPHA;
+
+ if (mFormat == SurfaceFormat::A8) {
+ content = CAIRO_CONTENT_ALPHA;
+ } else if (aOpaque) {
+ content = CAIRO_CONTENT_COLOR;
+ }
+
+ if (aCopyBackground) {
+ cairo_surface_t* source = cairo_get_group_target(mContext);
+ cairo_push_group_with_content(mContext, content);
+ cairo_surface_t* dest = cairo_get_group_target(mContext);
+ cairo_t* ctx = cairo_create(dest);
+ cairo_set_source_surface(ctx, source, 0, 0);
+ cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ctx);
+ cairo_destroy(ctx);
+ } else {
+ cairo_push_group_with_content(mContext, content);
+ }
+
+ PushedLayer layer(aOpacity, aCompositionOp, mPermitSubpixelAA);
+
+ if (aMask) {
+ cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask);
+ if (surf) {
+ layer.mMaskPattern = cairo_pattern_create_for_surface(surf);
+ Matrix maskTransform = aMaskTransform;
+ maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y());
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(maskTransform, mat);
+ cairo_matrix_invert(&mat);
+ cairo_pattern_set_matrix(layer.mMaskPattern, &mat);
+ cairo_surface_destroy(surf);
+ } else {
+ gfxCriticalError() << "Failed to get cairo surface for mask surface!";
+ }
+ }
+
+ mPushedLayers.push_back(layer);
+
+ SetPermitSubpixelAA(aOpaque);
+}
+
+void DrawTargetCairo::PopLayer() {
+ MOZ_RELEASE_ASSERT(!mPushedLayers.empty());
+
+ cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+
+ cairo_pop_group_to_source(mContext);
+
+ PushedLayer layer = mPushedLayers.back();
+ mPushedLayers.pop_back();
+
+ if (!layer.mMaskPattern) {
+ cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp));
+ cairo_paint_with_alpha(mContext, layer.mOpacity);
+ } else {
+ if (layer.mOpacity != Float(1.0)) {
+ cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
+
+ // Now draw the content using the desired operator
+ cairo_paint_with_alpha(mContext, layer.mOpacity);
+
+ cairo_pop_group_to_source(mContext);
+ }
+ cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp));
+ cairo_mask(mContext, layer.mMaskPattern);
+ }
+
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(mTransform, mat);
+ cairo_set_matrix(mContext, &mat);
+
+ cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+
+ cairo_pattern_destroy(layer.mMaskPattern);
+ SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA);
+}
+
+void DrawTargetCairo::ClearSurfaceForUnboundedSource(
+ const CompositionOp& aOperator) {
+ if (aOperator != CompositionOp::OP_SOURCE) return;
+ cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
+ // It doesn't really matter what the source is here, since Paint
+ // isn't bounded by the source and the mask covers the entire clip
+ // region.
+ cairo_paint(mContext);
+}
+
+already_AddRefed<GradientStops> DrawTargetCairo::CreateGradientStops(
+ GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
+ return MakeAndAddRef<GradientStopsCairo>(aStops, aNumStops, aExtendMode);
+}
+
+already_AddRefed<FilterNode> DrawTargetCairo::CreateFilter(FilterType aType) {
+ return FilterNodeSoftware::Create(aType);
+}
+
+already_AddRefed<SourceSurface> DrawTargetCairo::CreateSourceSurfaceFromData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat) const {
+ if (!aData) {
+ gfxWarning() << "DrawTargetCairo::CreateSourceSurfaceFromData null aData";
+ return nullptr;
+ }
+
+ cairo_surface_t* surf =
+ CopyToImageSurface(aData, IntRect(IntPoint(), aSize), aStride, aFormat);
+ if (!surf) {
+ return nullptr;
+ }
+
+ RefPtr<SourceSurfaceCairo> source_surf =
+ new SourceSurfaceCairo(surf, aSize, aFormat);
+ cairo_surface_destroy(surf);
+
+ return source_surf.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetCairo::OptimizeSourceSurface(
+ SourceSurface* aSurface) const {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetCairo::CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const {
+ return nullptr;
+}
+
+already_AddRefed<DrawTarget> DrawTargetCairo::CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ if (cairo_surface_status(cairo_get_group_target(mContext))) {
+ RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
+ if (target->Init(aSize, aFormat)) {
+ return target.forget();
+ }
+ }
+
+ cairo_surface_t* similar;
+ switch (cairo_surface_get_type(mSurface)) {
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ case CAIRO_SURFACE_TYPE_WIN32:
+ similar = cairo_win32_surface_create_with_dib(
+ GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height);
+ break;
+#endif
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+ case CAIRO_SURFACE_TYPE_QUARTZ:
+ if (StaticPrefs::gfx_cairo_quartz_cg_layer_enabled()) {
+ similar = cairo_quartz_surface_create_cg_layer(
+ mSurface, GfxFormatToCairoContent(aFormat), aSize.width,
+ aSize.height);
+ break;
+ }
+ [[fallthrough]];
+#endif
+ default:
+ similar = cairo_surface_create_similar(mSurface,
+ GfxFormatToCairoContent(aFormat),
+ aSize.width, aSize.height);
+ break;
+ }
+
+ if (!cairo_surface_status(similar)) {
+ RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
+ if (target->InitAlreadyReferenced(similar, aSize)) {
+ return target.forget();
+ }
+ }
+
+ gfxCriticalError(
+ CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize)))
+ << "Failed to create similar cairo surface! Size: " << aSize
+ << " Status: " << cairo_surface_status(similar)
+ << cairo_surface_status(cairo_get_group_target(mContext)) << " format "
+ << (int)aFormat;
+ cairo_surface_destroy(similar);
+
+ return nullptr;
+}
+
+RefPtr<DrawTarget> DrawTargetCairo::CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) {
+ RefPtr<DrawTarget> result;
+ // Doing this save()/restore() dance is wasteful
+ cairo_save(mContext);
+
+ if (!aBounds.IsEmpty()) {
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, aBounds.X(), aBounds.Y(), aBounds.Width(),
+ aBounds.Height());
+ cairo_clip(mContext);
+ }
+ cairo_identity_matrix(mContext);
+ IntRect clipBounds = IntRect::RoundOut(GetUserSpaceClip());
+ if (!clipBounds.IsEmpty()) {
+ RefPtr<DrawTarget> dt = CreateSimilarDrawTarget(
+ IntSize(clipBounds.width, clipBounds.height), aFormat);
+ result = gfx::Factory::CreateOffsetDrawTarget(
+ dt, IntPoint(clipBounds.x, clipBounds.y));
+ result->SetTransform(mTransform);
+ } else {
+ // Everything is clipped but we still want some kind of surface
+ result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat);
+ }
+
+ cairo_restore(mContext);
+ return result;
+}
+bool DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface,
+ const IntSize& aSize,
+ SurfaceFormat* aFormat) {
+ if (cairo_surface_status(aSurface)) {
+ gfxCriticalNote << "Attempt to create DrawTarget for invalid surface. "
+ << aSize
+ << " Cairo Status: " << cairo_surface_status(aSurface);
+ cairo_surface_destroy(aSurface);
+ return false;
+ }
+
+ mContext = cairo_create(aSurface);
+ mSurface = aSurface;
+ mSize = aSize;
+ mFormat = aFormat ? *aFormat : GfxFormatForCairoSurface(aSurface);
+
+ // Cairo image surface have a bug where they will allocate a mask surface (for
+ // clipping) the size of the clip extents, and don't take the surface extents
+ // into account. Add a manual clip to the surface extents to prevent this.
+ cairo_new_path(mContext);
+ cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height);
+ cairo_clip(mContext);
+
+ if (mFormat == SurfaceFormat::A8R8G8B8_UINT32 ||
+ mFormat == SurfaceFormat::R8G8B8A8) {
+ SetPermitSubpixelAA(false);
+ } else {
+ SetPermitSubpixelAA(true);
+ }
+
+ return true;
+}
+
+already_AddRefed<DrawTarget> DrawTargetCairo::CreateShadowDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const {
+ cairo_surface_t* similar = cairo_surface_create_similar(
+ cairo_get_target(mContext), GfxFormatToCairoContent(aFormat), aSize.width,
+ aSize.height);
+
+ if (cairo_surface_status(similar)) {
+ return nullptr;
+ }
+
+ // If we don't have a blur then we can use the RGBA mask and keep all the
+ // operations in graphics memory.
+ if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) {
+ RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
+ if (target->InitAlreadyReferenced(similar, aSize)) {
+ return target.forget();
+ } else {
+ return nullptr;
+ }
+ }
+
+ cairo_surface_t* blursurf =
+ cairo_image_surface_create(CAIRO_FORMAT_A8, aSize.width, aSize.height);
+
+ if (cairo_surface_status(blursurf)) {
+ return nullptr;
+ }
+
+ cairo_surface_t* tee = cairo_tee_surface_create(blursurf);
+ cairo_surface_destroy(blursurf);
+ if (cairo_surface_status(tee)) {
+ cairo_surface_destroy(similar);
+ return nullptr;
+ }
+
+ cairo_tee_surface_add(tee, similar);
+ cairo_surface_destroy(similar);
+
+ RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
+ if (target->InitAlreadyReferenced(tee, aSize)) {
+ return target.forget();
+ }
+ return nullptr;
+}
+
+bool DrawTargetCairo::Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) {
+ return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix);
+}
+
+bool DrawTargetCairo::Init(cairo_surface_t* aSurface, const IntSize& aSize,
+ SurfaceFormat* aFormat) {
+ cairo_surface_reference(aSurface);
+ return InitAlreadyReferenced(aSurface, aSize, aFormat);
+}
+
+bool DrawTargetCairo::Init(const IntSize& aSize, SurfaceFormat aFormat) {
+ cairo_surface_t* surf = cairo_image_surface_create(
+ GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height);
+ return InitAlreadyReferenced(surf, aSize);
+}
+
+bool DrawTargetCairo::Init(unsigned char* aData, const IntSize& aSize,
+ int32_t aStride, SurfaceFormat aFormat) {
+ cairo_surface_t* surf = cairo_image_surface_create_for_data(
+ aData, GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height,
+ aStride);
+ return InitAlreadyReferenced(surf, aSize);
+}
+
+void* DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType) {
+ if (aType == NativeSurfaceType::CAIRO_CONTEXT) {
+ return mContext;
+ }
+
+ return nullptr;
+}
+
+void DrawTargetCairo::MarkSnapshotIndependent() {
+ if (mSnapshot) {
+ if (mSnapshot->refCount() > 1) {
+ // We only need to worry about snapshots that someone else knows about
+ mSnapshot->DrawTargetWillChange();
+ }
+ mSnapshot = nullptr;
+ }
+}
+
+void DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */) {
+ MarkSnapshotIndependent();
+ MOZ_ASSERT(!mLockedBits);
+}
+
+void DrawTargetCairo::SetTransform(const Matrix& aTransform) {
+ DrawTarget::SetTransform(aTransform);
+
+ mTransformSingular = aTransform.IsSingular();
+ if (!mTransformSingular) {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(mTransform, mat);
+ cairo_set_matrix(mContext, &mat);
+ }
+}
+
+Rect DrawTargetCairo::GetUserSpaceClip() const {
+ double clipX1, clipY1, clipX2, clipY2;
+ cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2);
+ return Rect(clipX1, clipY1, clipX2 - clipX1,
+ clipY2 - clipY1); // Narrowing of doubles to floats
+}
+
+#ifdef MOZ_X11
+bool BorrowedXlibDrawable::Init(DrawTarget* aDT) {
+ MOZ_ASSERT(aDT, "Caller should check for nullptr");
+ MOZ_ASSERT(!mDT, "Can't initialize twice!");
+ mDT = aDT;
+ mDrawable = X11None;
+
+# ifdef CAIRO_HAS_XLIB_SURFACE
+ if (aDT->GetBackendType() != BackendType::CAIRO || aDT->IsTiledDrawTarget()) {
+ return false;
+ }
+
+ DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT);
+ cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext);
+ if (cairo_surface_get_type(surf) != CAIRO_SURFACE_TYPE_XLIB) {
+ return false;
+ }
+ cairo_surface_flush(surf);
+
+ cairoDT->WillChange();
+
+ mDisplay = cairo_xlib_surface_get_display(surf);
+ mDrawable = cairo_xlib_surface_get_drawable(surf);
+ mScreen = cairo_xlib_surface_get_screen(surf);
+ mVisual = cairo_xlib_surface_get_visual(surf);
+ mSize.width = cairo_xlib_surface_get_width(surf);
+ mSize.height = cairo_xlib_surface_get_height(surf);
+
+ double x = 0, y = 0;
+ cairo_surface_get_device_offset(surf, &x, &y);
+ mOffset = Point(x, y);
+
+ return true;
+# else
+ return false;
+# endif
+}
+
+void BorrowedXlibDrawable::Finish() {
+ DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(mDT);
+ cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext);
+ cairo_surface_mark_dirty(surf);
+ if (mDrawable) {
+ mDrawable = X11None;
+ }
+}
+#endif
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DrawTargetCairo.h b/gfx/2d/DrawTargetCairo.h
new file mode 100644
index 0000000000..8de89f9397
--- /dev/null
+++ b/gfx/2d/DrawTargetCairo.h
@@ -0,0 +1,252 @@
+/* -*- 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_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, override)
+
+ 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() = default;
+
+ const std::vector<GradientStop>& GetStops() const { return mStops; }
+
+ ExtendMode GetExtendMode() const { return mExtendMode; }
+
+ virtual BackendType GetBackendType() const override {
+ 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 BorrowedXlibDrawable;
+
+ DrawTargetCairo();
+ virtual ~DrawTargetCairo();
+
+ virtual bool IsValid() const override;
+ virtual DrawTargetType GetType() const override;
+ virtual BackendType GetBackendType() const override {
+ return BackendType::CAIRO;
+ }
+
+ virtual void Link(const char* aDestination, const Rect& aRect) override;
+ virtual void Destination(const char* aDestination,
+ const Point& aPoint) override;
+
+ virtual already_AddRefed<SourceSurface> Snapshot() override;
+ virtual IntSize GetSize() const 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 ShadowOptions& aShadow,
+ 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) 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 PushLayerWithBlend(
+ bool aOpaque, Float aOpacity, SourceSurface* aMask,
+ const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(),
+ bool aCopyBackground = false,
+ CompositionOp = CompositionOp::OP_OVER) override;
+ virtual void PopLayer() override;
+
+ virtual already_AddRefed<PathBuilder> CreatePathBuilder(
+ FillRule aFillRule = FillRule::FILL_WINDING) const override {
+ return PathBuilderCairo::Create(aFillRule);
+ }
+
+ 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 RefPtr<DrawTarget> CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) 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* 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 32766; }
+
+ 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() const;
+
+ // 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(cairo_antialias_t aAAMode = CAIRO_ANTIALIAS_DEFAULT);
+
+ 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, CompositionOp aCompositionOp,
+ bool aWasPermittingSubpixelAA)
+ : mOpacity(aOpacity),
+ mCompositionOp(aCompositionOp),
+ mMaskPattern(nullptr),
+ mWasPermittingSubpixelAA(aWasPermittingSubpixelAA) {}
+ Float mOpacity;
+ CompositionOp mCompositionOp;
+ 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_
diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp
new file mode 100644
index 0000000000..96a898163d
--- /dev/null
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -0,0 +1,2383 @@
+/* -*- 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 <optional>
+
+#include <initguid.h>
+#include "DrawTargetD2D1.h"
+#include "FilterNodeSoftware.h"
+#include "GradientStopsD2D.h"
+#include "SourceSurfaceD2D1.h"
+#include "ConicGradientEffectD2D1.h"
+#include "RadialGradientEffectD2D1.h"
+
+#include "HelpersD2D.h"
+#include "FilterNodeD2D1.h"
+#include "ExtendInputEffectD2D1.h"
+#include "nsAppRunner.h"
+#include "MainThreadUtils.h"
+
+#include "mozilla/Mutex.h"
+
+// decltype is not usable for overloaded functions.
+typedef HRESULT(WINAPI* D2D1CreateFactoryFunc)(
+ D2D1_FACTORY_TYPE factoryType, REFIID iid,
+ CONST D2D1_FACTORY_OPTIONS* pFactoryOptions, void** factory);
+
+namespace mozilla {
+namespace gfx {
+
+uint64_t DrawTargetD2D1::mVRAMUsageDT;
+uint64_t DrawTargetD2D1::mVRAMUsageSS;
+StaticRefPtr<ID2D1Factory1> DrawTargetD2D1::mFactory;
+
+const D2D1_MATRIX_5X4_F kLuminanceMatrix =
+ D2D1::Matrix5x4F(0, 0, 0, 0.2125f, 0, 0, 0, 0.7154f, 0, 0, 0, 0.0721f, 0, 0,
+ 0, 0, 0, 0, 0, 0);
+
+RefPtr<ID2D1Factory1> D2DFactory() { return DrawTargetD2D1::factory(); }
+
+DrawTargetD2D1::DrawTargetD2D1()
+ : mPushedLayers(1),
+ mSnapshotLock(std::make_shared<Mutex>("DrawTargetD2D1::mSnapshotLock")),
+ mUsedCommandListsSincePurge(0),
+ mTransformedGlyphsSinceLastPurge(0),
+ mComplexBlendsWithListInList(0),
+ mDeviceSeq(0),
+ mInitState(InitState::Uninitialized) {}
+
+DrawTargetD2D1::~DrawTargetD2D1() {
+ PopAllClips();
+
+ if (mSnapshot) {
+ MutexAutoLock lock(*mSnapshotLock);
+ // We may hold the only reference. MarkIndependent will clear mSnapshot;
+ // keep the snapshot object alive so it doesn't get destroyed while
+ // MarkIndependent is running.
+ RefPtr<SourceSurfaceD2D1> deathGrip = mSnapshot;
+ // mSnapshot can be treated as independent of this DrawTarget since we know
+ // this DrawTarget won't change again.
+ deathGrip->MarkIndependent();
+ // mSnapshot will be cleared now.
+ }
+
+ if (mDC && IsDeviceContextValid()) {
+ // The only way mDC can be null is if Init failed, but it can happen and the
+ // destructor is the only place where we need to check for it since the
+ // DrawTarget will destroyed right after Init fails.
+ mDC->EndDraw();
+ }
+
+ {
+ // Until this point in the destructor it -must- still be valid for
+ // FlushInternal to be called on this.
+ StaticMutexAutoLock lock(Factory::mDTDependencyLock);
+ // Targets depending on us can break that dependency, since we're obviously
+ // not going to be modified in the future.
+ for (auto iter = mDependentTargets.begin(); iter != mDependentTargets.end();
+ iter++) {
+ (*iter)->mDependingOnTargets.erase(this);
+ }
+ // Our dependencies on other targets no longer matter.
+ for (TargetSet::iterator iter = mDependingOnTargets.begin();
+ iter != mDependingOnTargets.end(); iter++) {
+ (*iter)->mDependentTargets.erase(this);
+ }
+ }
+}
+
+bool DrawTargetD2D1::IsValid() const {
+ if (mInitState != InitState::Uninitialized && !IsDeviceContextValid()) {
+ return false;
+ }
+ if (NS_IsMainThread()) {
+ // Uninitialized DTs are considered valid.
+ return mInitState != InitState::Failure;
+ } else {
+ return const_cast<DrawTargetD2D1*>(this)->EnsureInitialized();
+ }
+}
+
+already_AddRefed<SourceSurface> DrawTargetD2D1::Snapshot() {
+ if (!EnsureInitialized()) {
+ return nullptr;
+ }
+
+ MutexAutoLock lock(*mSnapshotLock);
+ if (mSnapshot) {
+ RefPtr<SourceSurface> snapshot(mSnapshot);
+ return snapshot.forget();
+ }
+ PopAllClips();
+
+ Flush();
+
+ mSnapshot = new SourceSurfaceD2D1(mBitmap, mDC, mFormat, mSize, this);
+
+ RefPtr<SourceSurface> snapshot(mSnapshot);
+ return snapshot.forget();
+}
+
+bool DrawTargetD2D1::EnsureLuminanceEffect() {
+ if (mLuminanceEffect.get()) {
+ return true;
+ }
+
+ HRESULT hr = mDC->CreateEffect(CLSID_D2D1ColorMatrix,
+ getter_AddRefs(mLuminanceEffect));
+ if (FAILED(hr)) {
+ gfxCriticalError() << "Failed to create luminance effect. Code: "
+ << hexa(hr);
+ return false;
+ }
+
+ mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX,
+ kLuminanceMatrix);
+ mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_ALPHA_MODE,
+ D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT);
+ return true;
+}
+
+already_AddRefed<SourceSurface> DrawTargetD2D1::IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) {
+ if (!EnsureInitialized()) {
+ return nullptr;
+ }
+ if ((aLuminanceType != LuminanceType::LUMINANCE) ||
+ // See bug 1372577, some race condition where we get invalid
+ // results with D2D in the parent process. Fallback in that case.
+ XRE_IsParentProcess()) {
+ return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
+ }
+
+ // Create the luminance effect
+ if (!EnsureLuminanceEffect()) {
+ return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
+ }
+
+ Flush();
+
+ {
+ D2D1_MATRIX_5X4_F matrix = kLuminanceMatrix;
+ matrix._14 *= aOpacity;
+ matrix._24 *= aOpacity;
+ matrix._34 *= aOpacity;
+
+ mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix);
+ }
+
+ mLuminanceEffect->SetInput(0, mBitmap);
+
+ RefPtr<ID2D1Image> luminanceOutput;
+ mLuminanceEffect->GetOutput(getter_AddRefs(luminanceOutput));
+
+ return MakeAndAddRef<SourceSurfaceD2D1>(luminanceOutput, mDC,
+ SurfaceFormat::B8G8R8A8, mSize);
+}
+
+// Command lists are kept around by device contexts until EndDraw is called,
+// this can cause issues with memory usage (see bug 1238328). EndDraw/BeginDraw
+// are expensive though, especially relatively when little work is done, so
+// we try to reduce the amount of times we execute these purges.
+static const uint32_t kPushedLayersBeforePurge = 25;
+// Rendering glyphs with different transforms causes the glyph cache to grow
+// very large (see bug 1474883) so we must call EndDraw every so often.
+static const uint32_t kTransformedGlyphsBeforePurge = 1000;
+
+void DrawTargetD2D1::Flush() { FlushInternal(); }
+
+bool DrawTargetD2D1::MaybeClearRect(CompositionOp aOp, const Rect& aBounds) {
+ if (aOp == CompositionOp::OP_CLEAR) {
+ FillRect(aBounds, ColorPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f)),
+ DrawOptions(1.0f, aOp));
+ return true;
+ }
+ return false;
+}
+
+void DrawTargetD2D1::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource,
+ const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) {
+ if (MaybeClearRect(aOptions.mCompositionOp, aDest)) {
+ return;
+ }
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp,
+ ColorPattern(DeviceColor()))) {
+ return;
+ }
+
+ Rect source = aSource - aSurface->GetRect().TopLeft();
+
+ D2D1_RECT_F samplingBounds;
+
+ if (aSurfOptions.mSamplingBounds == SamplingBounds::BOUNDED) {
+ samplingBounds = D2DRect(source);
+ } else {
+ samplingBounds = D2D1::RectF(0, 0, Float(aSurface->GetSize().width),
+ Float(aSurface->GetSize().height));
+ }
+
+ Float xScale = aDest.Width() / source.Width();
+ Float yScale = aDest.Height() / source.Height();
+
+ RefPtr<ID2D1ImageBrush> brush;
+
+ // Here we scale the source pattern up to the size and position where we want
+ // it to be.
+ Matrix transform;
+ transform.PreTranslate(aDest.X() - source.X() * xScale,
+ aDest.Y() - source.Y() * yScale);
+ transform.PreScale(xScale, yScale);
+
+ RefPtr<ID2D1Image> image =
+ GetImageForSurface(aSurface, transform, ExtendMode::CLAMP);
+
+ if (!image) {
+ gfxWarning() << *this << ": Unable to get D2D image for surface.";
+ return;
+ }
+
+ RefPtr<ID2D1Bitmap> bitmap;
+ HRESULT hr = E_FAIL;
+ if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
+ // If this is called with a DataSourceSurface it might do a partial upload
+ // that our DrawBitmap call doesn't support.
+ hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
+ }
+
+ if (SUCCEEDED(hr) && bitmap &&
+ aSurfOptions.mSamplingBounds == SamplingBounds::UNBOUNDED) {
+ mDC->DrawBitmap(bitmap, D2DRect(aDest), aOptions.mAlpha,
+ D2DFilter(aSurfOptions.mSamplingFilter), D2DRect(source));
+ } else {
+ // This has issues ignoring the alpha channel on windows 7 with images
+ // marked opaque.
+ MOZ_ASSERT(aSurface->GetFormat() != SurfaceFormat::B8G8R8X8);
+
+ // Bug 1275478 - D2D1 cannot draw A8 surface correctly.
+ MOZ_ASSERT(aSurface->GetFormat() != SurfaceFormat::A8);
+
+ mDC->CreateImageBrush(
+ image,
+ D2D1::ImageBrushProperties(
+ samplingBounds, D2D1_EXTEND_MODE_CLAMP, D2D1_EXTEND_MODE_CLAMP,
+ D2DInterpolationMode(aSurfOptions.mSamplingFilter)),
+ D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(transform)),
+ getter_AddRefs(brush));
+ mDC->FillRectangle(D2DRect(aDest), brush);
+ }
+
+ FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(DeviceColor()));
+}
+
+void DrawTargetD2D1::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ if (aNode->GetBackendType() != FILTER_BACKEND_DIRECT2D1_1) {
+ gfxWarning() << *this << ": Incompatible filter passed to DrawFilter.";
+ return;
+ }
+
+ if (MaybeClearRect(aOptions.mCompositionOp,
+ Rect(aDestPoint, aSourceRect.Size()))) {
+ return;
+ }
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp,
+ ColorPattern(DeviceColor()))) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ FilterNodeD2D1* node = static_cast<FilterNodeD2D1*>(aNode);
+ node->WillDraw(this);
+
+ if (aOptions.mAlpha == 1.0f) {
+ mDC->DrawImage(node->OutputEffect(), D2DPoint(aDestPoint),
+ D2DRect(aSourceRect));
+ } else {
+ RefPtr<ID2D1Image> image;
+ node->OutputEffect()->GetOutput(getter_AddRefs(image));
+
+ Matrix mat = Matrix::Translation(aDestPoint);
+
+ RefPtr<ID2D1ImageBrush> imageBrush;
+ mDC->CreateImageBrush(
+ image, D2D1::ImageBrushProperties(D2DRect(aSourceRect)),
+ D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)),
+ getter_AddRefs(imageBrush));
+ mDC->FillRectangle(D2D1::RectF(aDestPoint.x, aDestPoint.y,
+ aDestPoint.x + aSourceRect.width,
+ aDestPoint.y + aSourceRect.height),
+ imageBrush);
+ }
+
+ FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(DeviceColor()));
+}
+
+void DrawTargetD2D1::DrawSurfaceWithShadow(SourceSurface* aSurface,
+ const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOperator) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+
+ if (MaybeClearRect(aOperator, Rect(aDest, Size(aSurface->GetSize())))) {
+ return;
+ }
+
+ MarkChanged();
+
+ Matrix mat;
+ RefPtr<ID2D1Image> image =
+ GetImageForSurface(aSurface, mat, ExtendMode::CLAMP, nullptr, false);
+
+ if (!image) {
+ gfxWarning() << "Couldn't get image for surface.";
+ return;
+ }
+
+ if (!mat.IsIdentity()) {
+ gfxDebug() << *this
+ << ": At this point complex partial uploads are not supported "
+ "for Shadow surfaces.";
+ return;
+ }
+
+ if (!PrepareForDrawing(aOperator, ColorPattern(aShadow.mColor))) {
+ return;
+ }
+
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ RefPtr<ID2D1Effect> shadowEffect;
+ HRESULT hr = mDC->CreateEffect(
+ mFormat == SurfaceFormat::A8 ? CLSID_D2D1GaussianBlur : CLSID_D2D1Shadow,
+ getter_AddRefs(shadowEffect));
+ if (SUCCEEDED(hr) && shadowEffect) {
+ shadowEffect->SetInput(0, image);
+ if (mFormat == SurfaceFormat::A8) {
+ shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION,
+ aShadow.mSigma);
+ shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE,
+ D2D1_BORDER_MODE_HARD);
+ } else {
+ shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION,
+ aShadow.mSigma);
+ D2D1_VECTOR_4F color = {aShadow.mColor.r, aShadow.mColor.g,
+ aShadow.mColor.b, aShadow.mColor.a};
+ shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color);
+ }
+
+ D2D1_POINT_2F shadowPoint = D2DPoint(aDest + aShadow.mOffset);
+ mDC->DrawImage(shadowEffect, &shadowPoint, nullptr,
+ D2D1_INTERPOLATION_MODE_LINEAR,
+ D2D1_COMPOSITE_MODE_SOURCE_OVER);
+ } else {
+ gfxWarning() << "Failed to create shadow effect. Code: " << hexa(hr);
+ }
+
+ if (aSurface->GetFormat() != SurfaceFormat::A8) {
+ D2D1_POINT_2F imgPoint = D2DPoint(aDest);
+ mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR,
+ D2D1_COMPOSITE_MODE_SOURCE_OVER);
+ }
+
+ FinalizeDrawing(aOperator, ColorPattern(aShadow.mColor));
+}
+
+void DrawTargetD2D1::ClearRect(const Rect& aRect) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+
+ if (aRect.IsEmpty()) {
+ // Nothing to be done.
+ return;
+ }
+
+ MarkChanged();
+
+ PopAllClips();
+
+ PushClipRect(aRect);
+
+ if (mTransformDirty || !mTransform.IsIdentity()) {
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+ }
+
+ D2D1_RECT_F clipRect;
+ bool isPixelAligned;
+ if (mTransform.IsRectilinear() &&
+ GetDeviceSpaceClipRect(clipRect, isPixelAligned)) {
+ mDC->PushAxisAlignedClip(clipRect, isPixelAligned
+ ? D2D1_ANTIALIAS_MODE_ALIASED
+ : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+ mDC->Clear();
+ mDC->PopAxisAlignedClip();
+
+ PopClip();
+ return;
+ }
+
+ RefPtr<ID2D1CommandList> list;
+ mUsedCommandListsSincePurge++;
+ mDC->CreateCommandList(getter_AddRefs(list));
+ mDC->SetTarget(list);
+
+ IntRect addClipRect;
+ RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&addClipRect);
+
+ RefPtr<ID2D1SolidColorBrush> brush;
+ mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White),
+ getter_AddRefs(brush));
+ mDC->PushAxisAlignedClip(
+ D2D1::RectF(addClipRect.X(), addClipRect.Y(), addClipRect.XMost(),
+ addClipRect.YMost()),
+ D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+ mDC->FillGeometry(geom, brush);
+ mDC->PopAxisAlignedClip();
+
+ mDC->SetTarget(CurrentTarget());
+ list->Close();
+
+ mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2D1_COMPOSITE_MODE_DESTINATION_OUT);
+
+ PopClip();
+
+ return;
+}
+
+void DrawTargetD2D1::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
+ Point aOffset, const DrawOptions& aOptions) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+ MarkChanged();
+
+ RefPtr<ID2D1Bitmap> bitmap;
+
+ Matrix mat = Matrix::Translation(aOffset);
+ RefPtr<ID2D1Image> image =
+ GetImageForSurface(aMask, mat, ExtendMode::CLAMP, nullptr);
+
+ MOZ_ASSERT(!mat.HasNonTranslation());
+ aOffset.x = mat._31;
+ aOffset.y = mat._32;
+
+ if (!image) {
+ gfxWarning() << "Failed to get image for surface.";
+ return;
+ }
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aSource)) {
+ return;
+ }
+
+ IntSize size =
+ IntSize::Truncate(aMask->GetSize().width, aMask->GetSize().height);
+ Rect dest =
+ Rect(aOffset.x + aMask->GetRect().x, aOffset.y + aMask->GetRect().y,
+ Float(size.width), Float(size.height));
+
+ HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
+ if (!bitmap || FAILED(hr)) {
+ // D2D says if we have an actual ID2D1Image and not a bitmap underlying the
+ // object, we can't query for a bitmap. Instead, Push/PopLayer
+ gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces. "
+ "Falling back to push/pop layer";
+
+ RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions);
+ RefPtr<ID2D1ImageBrush> maskBrush;
+ hr = mDC->CreateImageBrush(
+ image,
+ D2D1::ImageBrushProperties(D2D1::RectF(0, 0, size.width, size.height)),
+ D2D1::BrushProperties(
+ 1.0f, D2D1::Matrix3x2F::Translation(aMask->GetRect().x,
+ aMask->GetRect().y)),
+ getter_AddRefs(maskBrush));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ mDC->PushLayer(
+ D2D1::LayerParameters1(D2D1::InfiniteRect(), nullptr,
+ D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+ D2D1::Matrix3x2F::Translation(
+ aMask->GetRect().x, aMask->GetRect().y),
+ 1.0f, maskBrush, D2D1_LAYER_OPTIONS1_NONE),
+ nullptr);
+
+ mDC->FillRectangle(D2DRect(dest), source);
+ mDC->PopLayer();
+
+ FinalizeDrawing(aOptions.mCompositionOp, aSource);
+ return;
+ } else {
+ // If this is a data source surface, we might have created a partial bitmap
+ // for this surface and only uploaded part of the mask. In that case,
+ // we have to fixup our sizes here.
+ size.width = bitmap->GetSize().width;
+ size.height = bitmap->GetSize().height;
+ dest.SetWidth(size.width);
+ dest.SetHeight(size.height);
+ }
+
+ // FillOpacityMask only works if the antialias mode is MODE_ALIASED
+ mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+
+ Rect maskRect = Rect(aMask->GetRect().x, aMask->GetRect().y,
+ Float(size.width), Float(size.height));
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aSource, aOptions);
+ mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
+ D2DRect(dest), D2DRect(maskRect));
+
+ mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aSource);
+}
+
+void DrawTargetD2D1::CopySurface(SourceSurface* aSurface,
+ const IntRect& aSourceRect,
+ const IntPoint& aDestination) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+ MarkChanged();
+
+ PopAllClips();
+
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ Matrix mat = Matrix::Translation(aDestination.x - aSourceRect.X(),
+ aDestination.y - aSourceRect.Y());
+ RefPtr<ID2D1Image> image =
+ GetImageForSurface(aSurface, mat, ExtendMode::CLAMP, nullptr, false);
+
+ if (!image) {
+ gfxWarning() << "Couldn't get image for surface.";
+ return;
+ }
+
+ if (mat.HasNonIntegerTranslation()) {
+ gfxDebug() << *this
+ << ": At this point scaled partial uploads are not supported "
+ "for CopySurface.";
+ return;
+ }
+
+ IntRect sourceRect = aSourceRect;
+ sourceRect.SetLeftEdge(sourceRect.X() + (aDestination.x - aSourceRect.X()) -
+ mat._31);
+ sourceRect.SetTopEdge(sourceRect.Y() + (aDestination.y - aSourceRect.Y()) -
+ mat._32);
+
+ RefPtr<ID2D1Bitmap> bitmap;
+ HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
+
+ if (SUCCEEDED(hr) && bitmap && mFormat == SurfaceFormat::A8) {
+ RefPtr<ID2D1SolidColorBrush> brush;
+ mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White),
+ D2D1::BrushProperties(), getter_AddRefs(brush));
+ mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
+ mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS);
+ mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+ return;
+ }
+
+ Rect srcRect(Float(sourceRect.X()), Float(sourceRect.Y()),
+ Float(aSourceRect.Width()), Float(aSourceRect.Height()));
+
+ Rect dstRect(Float(aDestination.x), Float(aDestination.y),
+ Float(aSourceRect.Width()), Float(aSourceRect.Height()));
+
+ if (SUCCEEDED(hr) && bitmap) {
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
+ mDC->DrawBitmap(bitmap, D2DRect(dstRect), 1.0f,
+ D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2DRect(srcRect));
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+ return;
+ }
+
+ mDC->DrawImage(image,
+ D2D1::Point2F(Float(aDestination.x), Float(aDestination.y)),
+ D2DRect(srcRect), D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
+}
+
+void DrawTargetD2D1::FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+ mDC->FillRectangle(D2DRect(aRect), brush);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::FillRoundedRect(const RoundedRect& aRect,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (!aRect.corners.AreRadiiSame()) {
+ return DrawTarget::FillRoundedRect(aRect, aPattern, aOptions);
+ }
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+ mDC->FillRoundedRectangle(D2DRoundedRect(aRect), brush);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::StrokeRect(const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+ RefPtr<ID2D1StrokeStyle> strokeStyle =
+ CreateStrokeStyleForOptions(aStrokeOptions);
+
+ mDC->DrawRectangle(D2DRect(aRect), brush, aStrokeOptions.mLineWidth,
+ strokeStyle);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::StrokeLine(const Point& aStart, const Point& aEnd,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+ RefPtr<ID2D1StrokeStyle> strokeStyle =
+ CreateStrokeStyleForOptions(aStrokeOptions);
+
+ mDC->DrawLine(D2DPoint(aStart), D2DPoint(aEnd), brush,
+ aStrokeOptions.mLineWidth, strokeStyle);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ const Path* path = aPath;
+ if (path->GetBackendType() != BackendType::DIRECT2D1_1) {
+ gfxDebug() << *this << ": Ignoring drawing call for incompatible path.";
+ return;
+ }
+ const PathD2D* d2dPath = static_cast<const PathD2D*>(path);
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+ RefPtr<ID2D1StrokeStyle> strokeStyle =
+ CreateStrokeStyleForOptions(aStrokeOptions);
+
+ mDC->DrawGeometry(d2dPath->mGeometry, brush, aStrokeOptions.mLineWidth,
+ strokeStyle);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ const Path* path = aPath;
+ if (!path || path->GetBackendType() != BackendType::DIRECT2D1_1) {
+ gfxDebug() << *this << ": Ignoring drawing call for incompatible path.";
+ return;
+ }
+ const PathD2D* d2dPath = static_cast<const PathD2D*>(path);
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+
+ mDC->FillGeometry(d2dPath->mGeometry, brush);
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (aFont->GetType() != FontType::DWRITE) {
+ gfxDebug() << *this << ": Ignoring drawing call for incompatible font.";
+ return;
+ }
+
+ ScaledFontDWrite* font = static_cast<ScaledFontDWrite*>(aFont);
+
+ // May be null, if we failed to initialize the default rendering params.
+ RefPtr<IDWriteRenderingParams> params =
+ font->DWriteSettings().RenderingParams();
+
+ AntialiasMode aaMode = font->GetDefaultAAMode();
+
+ if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
+ aaMode = aOptions.mAntialiasMode;
+ }
+
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) {
+ return;
+ }
+
+ bool forceClearType = false;
+ if (!CurrentLayer().mIsOpaque && mPermitSubpixelAA &&
+ aOptions.mCompositionOp == CompositionOp::OP_OVER &&
+ aaMode == AntialiasMode::SUBPIXEL) {
+ forceClearType = true;
+ }
+
+ D2D1_TEXT_ANTIALIAS_MODE d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
+
+ switch (aaMode) {
+ case AntialiasMode::NONE:
+ d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
+ break;
+ case AntialiasMode::GRAY:
+ d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
+ break;
+ case AntialiasMode::SUBPIXEL:
+ d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
+ break;
+ default:
+ d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
+ }
+
+ if (d2dAAMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE &&
+ !CurrentLayer().mIsOpaque && !forceClearType) {
+ d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
+ }
+
+ mDC->SetTextAntialiasMode(d2dAAMode);
+
+ if (params != mTextRenderingParams) {
+ // According to
+ // https://docs.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-id2d1rendertarget-settextrenderingparams
+ // it's OK to pass null for params here; it will just "clear current text
+ // rendering options".
+ mDC->SetTextRenderingParams(params);
+ mTextRenderingParams = params;
+ }
+
+ RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions);
+
+ AutoDWriteGlyphRun autoRun;
+ DWriteGlyphRunFromGlyphs(aBuffer, font, &autoRun);
+
+ bool needsRepushedLayers = false;
+ if (forceClearType) {
+ D2D1_RECT_F rect;
+ bool isAligned;
+ needsRepushedLayers = CurrentLayer().mPushedClips.size() &&
+ !GetDeviceSpaceClipRect(rect, isAligned);
+
+ // If we have a complex clip in our stack and we have a transparent
+ // background, and subpixel AA is permitted, we need to repush our layer
+ // stack limited by the glyph run bounds initializing our layers for
+ // subpixel AA.
+ if (needsRepushedLayers) {
+ mDC->GetGlyphRunWorldBounds(D2D1::Point2F(), &autoRun,
+ DWRITE_MEASURING_MODE_NATURAL, &rect);
+ rect.left = std::floor(rect.left);
+ rect.right = std::ceil(rect.right);
+ rect.top = std::floor(rect.top);
+ rect.bottom = std::ceil(rect.bottom);
+
+ PopAllClips();
+
+ if (!mTransform.IsRectilinear()) {
+ // We must limit the pixels we touch to the -user space- bounds of
+ // the glyphs being drawn. In order not to get transparent pixels
+ // copied up in our pushed layer stack.
+ D2D1_RECT_F userRect;
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mDC->GetGlyphRunWorldBounds(D2D1::Point2F(), &autoRun,
+ DWRITE_MEASURING_MODE_NATURAL, &userRect);
+
+ RefPtr<ID2D1PathGeometry> path;
+ factory()->CreatePathGeometry(getter_AddRefs(path));
+ RefPtr<ID2D1GeometrySink> sink;
+ path->Open(getter_AddRefs(sink));
+ AddRectToSink(sink, userRect);
+ sink->Close();
+
+ mDC->PushLayer(
+ D2D1::LayerParameters1(
+ D2D1::InfiniteRect(), path, D2D1_ANTIALIAS_MODE_ALIASED,
+ D2DMatrix(mTransform), 1.0f, nullptr,
+ D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND |
+ D2D1_LAYER_OPTIONS1_IGNORE_ALPHA),
+ nullptr);
+ }
+
+ PushClipsToDC(mDC, true, rect);
+ mDC->SetTransform(D2DMatrix(mTransform));
+ }
+ }
+
+ if (brush) {
+ mDC->DrawGlyphRun(D2D1::Point2F(), &autoRun, brush);
+ }
+
+ if (mTransform.HasNonTranslation()) {
+ mTransformedGlyphsSinceLastPurge += aBuffer.mNumGlyphs;
+ }
+
+ if (needsRepushedLayers) {
+ PopClipsFromDC(mDC);
+
+ if (!mTransform.IsRectilinear()) {
+ mDC->PopLayer();
+ }
+ }
+
+ FinalizeDrawing(aOptions.mCompositionOp, aPattern);
+}
+
+void DrawTargetD2D1::Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions) {
+ if (!PrepareForDrawing(aOptions.mCompositionOp, aSource)) {
+ return;
+ }
+
+ RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions);
+ RefPtr<ID2D1Brush> mask = CreateBrushForPattern(aMask, DrawOptions());
+ mDC->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr,
+ D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+ D2D1::IdentityMatrix(), 1.0f, mask),
+ nullptr);
+
+ Rect rect(0, 0, (Float)mSize.width, (Float)mSize.height);
+ Matrix mat = mTransform;
+ mat.Invert();
+
+ mDC->FillRectangle(D2DRect(mat.TransformBounds(rect)), source);
+
+ mDC->PopLayer();
+
+ FinalizeDrawing(aOptions.mCompositionOp, aSource);
+}
+
+void DrawTargetD2D1::PushClipGeometry(ID2D1Geometry* aGeometry,
+ const D2D1_MATRIX_3X2_F& aTransform,
+ bool aPixelAligned) {
+ mCurrentClippedGeometry = nullptr;
+
+ PushedClip clip;
+ clip.mGeometry = aGeometry;
+ clip.mTransform = aTransform;
+ clip.mIsPixelAligned = aPixelAligned;
+
+ aGeometry->GetBounds(aTransform, &clip.mBounds);
+
+ CurrentLayer().mPushedClips.push_back(clip);
+
+ // The transform of clips is relative to the world matrix, since we use the
+ // total transform for the clips, make the world matrix identity.
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ if (CurrentLayer().mClipsArePushed) {
+ PushD2DLayer(mDC, clip.mGeometry, clip.mTransform, clip.mIsPixelAligned);
+ }
+}
+
+void DrawTargetD2D1::PushClip(const Path* aPath) {
+ const Path* path = aPath;
+ if (path->GetBackendType() != BackendType::DIRECT2D1_1) {
+ gfxDebug() << *this << ": Ignoring clipping call for incompatible path.";
+ return;
+ }
+ if (!EnsureInitialized()) {
+ return;
+ }
+
+ RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(path));
+
+ PushClipGeometry(pathD2D->GetGeometry(), D2DMatrix(mTransform));
+}
+
+void DrawTargetD2D1::PushClipRect(const Rect& aRect) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+ if (!mTransform.IsRectilinear()) {
+ // Whoops, this isn't a rectangle in device space, Direct2D will not deal
+ // with this transform the way we want it to.
+ // See remarks:
+ // http://msdn.microsoft.com/en-us/library/dd316860%28VS.85%29.aspx
+ RefPtr<ID2D1Geometry> geom = ConvertRectToGeometry(D2DRect(aRect));
+ return PushClipGeometry(geom, D2DMatrix(mTransform));
+ }
+
+ mCurrentClippedGeometry = nullptr;
+
+ PushedClip clip;
+ Rect rect = mTransform.TransformBounds(aRect);
+ IntRect intRect;
+ clip.mIsPixelAligned = rect.ToIntRect(&intRect);
+
+ // Do not store the transform, just store the device space rectangle directly.
+ clip.mBounds = D2DRect(rect);
+
+ CurrentLayer().mPushedClips.push_back(clip);
+
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ if (CurrentLayer().mClipsArePushed) {
+ mDC->PushAxisAlignedClip(
+ clip.mBounds, clip.mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED
+ : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+ }
+}
+
+void DrawTargetD2D1::PushDeviceSpaceClipRects(const IntRect* aRects,
+ uint32_t aCount) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+ // Build a path for the union of the rects.
+ RefPtr<ID2D1PathGeometry> path;
+ factory()->CreatePathGeometry(getter_AddRefs(path));
+ RefPtr<ID2D1GeometrySink> sink;
+ path->Open(getter_AddRefs(sink));
+ sink->SetFillMode(D2D1_FILL_MODE_WINDING);
+ for (uint32_t i = 0; i < aCount; i++) {
+ const IntRect& rect = aRects[i];
+ sink->BeginFigure(D2DPoint(rect.TopLeft()), D2D1_FIGURE_BEGIN_FILLED);
+ D2D1_POINT_2F lines[3] = {D2DPoint(rect.TopRight()),
+ D2DPoint(rect.BottomRight()),
+ D2DPoint(rect.BottomLeft())};
+ sink->AddLines(lines, 3);
+ sink->EndFigure(D2D1_FIGURE_END_CLOSED);
+ }
+ sink->Close();
+
+ // The path is in device-space, so there is no transform needed,
+ // and all rects are pixel aligned.
+ PushClipGeometry(path, D2D1::IdentityMatrix(), true);
+}
+
+void DrawTargetD2D1::PopClip() {
+ if (!EnsureInitialized()) {
+ return;
+ }
+ mCurrentClippedGeometry = nullptr;
+ if (CurrentLayer().mPushedClips.empty()) {
+ gfxDevCrash(LogReason::UnbalancedClipStack)
+ << "DrawTargetD2D1::PopClip: No clip to pop.";
+ return;
+ }
+
+ if (CurrentLayer().mClipsArePushed) {
+ if (CurrentLayer().mPushedClips.back().mGeometry) {
+ mDC->PopLayer();
+ } else {
+ mDC->PopAxisAlignedClip();
+ }
+ }
+ CurrentLayer().mPushedClips.pop_back();
+}
+
+bool DrawTargetD2D1::RemoveAllClips() {
+ if (!EnsureInitialized()) {
+ return false;
+ }
+ mCurrentClippedGeometry = nullptr;
+ while (!CurrentLayer().mPushedClips.empty()) {
+ PopClip();
+ }
+ return true;
+}
+
+void DrawTargetD2D1::PushLayer(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground) {
+ if (!EnsureInitialized()) {
+ return;
+ }
+ D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE;
+
+ if (aOpaque) {
+ options |= D2D1_LAYER_OPTIONS1_IGNORE_ALPHA;
+ }
+ if (aCopyBackground) {
+ options |= D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND;
+ }
+
+ RefPtr<ID2D1ImageBrush> mask;
+ Matrix maskTransform = aMaskTransform;
+ RefPtr<ID2D1PathGeometry> clip;
+
+ if (aMask) {
+ RefPtr<ID2D1Image> image =
+ GetImageForSurface(aMask, maskTransform, ExtendMode::CLAMP);
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ maskTransform =
+ maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y());
+ // The mask is given in user space. Our layer will apply it in device space.
+ maskTransform = maskTransform * mTransform;
+
+ if (image) {
+ IntSize maskSize = aMask->GetSize();
+ HRESULT hr = mDC->CreateImageBrush(
+ image,
+ D2D1::ImageBrushProperties(
+ D2D1::RectF(0, 0, maskSize.width, maskSize.height)),
+ D2D1::BrushProperties(1.0f, D2DMatrix(maskTransform)),
+ getter_AddRefs(mask));
+ if (FAILED(hr)) {
+ gfxWarning() << "[D2D1.1] Failed to create a ImageBrush, code: "
+ << hexa(hr);
+ }
+
+ factory()->CreatePathGeometry(getter_AddRefs(clip));
+ RefPtr<ID2D1GeometrySink> sink;
+ clip->Open(getter_AddRefs(sink));
+ AddRectToSink(sink, D2D1::RectF(0, 0, aMask->GetSize().width,
+ aMask->GetSize().height));
+ sink->Close();
+ } else {
+ gfxCriticalError() << "Failed to get image for mask surface!";
+ }
+ }
+
+ PushAllClips();
+
+ mDC->PushLayer(D2D1::LayerParameters1(
+ D2D1::InfiniteRect(), clip, D2D1_ANTIALIAS_MODE_ALIASED,
+ D2DMatrix(maskTransform), aOpacity, mask, options),
+ nullptr);
+ PushedLayer pushedLayer;
+ pushedLayer.mClipsArePushed = false;
+ pushedLayer.mIsOpaque = aOpaque;
+ pushedLayer.mOldPermitSubpixelAA = mPermitSubpixelAA;
+ mPermitSubpixelAA = aOpaque;
+
+ mDC->CreateCommandList(getter_AddRefs(pushedLayer.mCurrentList));
+ mPushedLayers.push_back(pushedLayer);
+
+ mDC->SetTarget(CurrentTarget());
+
+ mUsedCommandListsSincePurge++;
+}
+
+void DrawTargetD2D1::PopLayer() {
+ // We must have at least one layer at all times.
+ MOZ_ASSERT(mPushedLayers.size() > 1);
+ MOZ_ASSERT(CurrentLayer().mPushedClips.size() == 0);
+ if (!EnsureInitialized() || mPushedLayers.size() <= 1) {
+ return;
+ }
+ RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList;
+ mPermitSubpixelAA = CurrentLayer().mOldPermitSubpixelAA;
+
+ mPushedLayers.pop_back();
+ mDC->SetTarget(CurrentTarget());
+
+ list->Close();
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ DCCommandSink sink(mDC);
+ list->Stream(&sink);
+
+ mComplexBlendsWithListInList = 0;
+
+ mDC->PopLayer();
+}
+
+already_AddRefed<SourceSurface> DrawTargetD2D1::CreateSourceSurfaceFromData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat) const {
+ RefPtr<ID2D1Bitmap1> bitmap;
+
+ RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext();
+ if (!dc) {
+ return nullptr;
+ }
+
+ HRESULT hr =
+ dc->CreateBitmap(D2DIntSize(aSize), aData, aStride,
+ D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE,
+ D2DPixelFormat(aFormat)),
+ getter_AddRefs(bitmap));
+
+ if (FAILED(hr) || !bitmap) {
+ gfxCriticalError(
+ CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize)))
+ << "[D2D1.1] 1CreateBitmap failure " << aSize << " Code: " << hexa(hr)
+ << " format " << (int)aFormat;
+ return nullptr;
+ }
+
+ return MakeAndAddRef<SourceSurfaceD2D1>(bitmap.get(), dc.get(), aFormat,
+ aSize);
+}
+
+already_AddRefed<DrawTarget> DrawTargetD2D1::CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ RefPtr<DrawTargetD2D1> dt = new DrawTargetD2D1();
+
+ if (!dt->Init(aSize, aFormat)) {
+ return nullptr;
+ }
+
+ return dt.forget();
+}
+
+bool DrawTargetD2D1::CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const {
+ RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext();
+ if (!dc) {
+ return false;
+ }
+ return (dc->GetMaximumBitmapSize() >= UINT32(aSize.width) &&
+ dc->GetMaximumBitmapSize() >= UINT32(aSize.height));
+}
+
+RefPtr<DrawTarget> DrawTargetD2D1::CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) {
+ RefPtr<DrawTarget> result;
+
+ if (!aBounds.IsEmpty()) {
+ PushClipRect(aBounds);
+ }
+
+ D2D1_RECT_F clipRect;
+ bool isAligned;
+ GetDeviceSpaceClipRect(clipRect, isAligned);
+ IntRect rect = RoundedOut(ToRect(clipRect));
+
+ RefPtr<DrawTarget> dt = CreateSimilarDrawTarget(rect.Size(), aFormat);
+ result = gfx::Factory::CreateOffsetDrawTarget(dt, rect.TopLeft());
+ result->SetTransform(mTransform);
+ if (!aBounds.IsEmpty()) {
+ PopClip();
+ }
+
+ return result;
+}
+
+already_AddRefed<GradientStops> DrawTargetD2D1::CreateGradientStops(
+ GradientStop* rawStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
+ if (aNumStops == 0) {
+ gfxWarning() << *this
+ << ": Failed to create GradientStopCollection with no stops.";
+ return nullptr;
+ }
+
+ D2D1_GRADIENT_STOP* stops = new D2D1_GRADIENT_STOP[aNumStops];
+
+ for (uint32_t i = 0; i < aNumStops; i++) {
+ stops[i].position = rawStops[i].offset;
+ stops[i].color = D2DColor(rawStops[i].color);
+ }
+
+ RefPtr<ID2D1GradientStopCollection1> stopCollection;
+
+ RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext();
+
+ if (!dc) {
+ return nullptr;
+ }
+
+ HRESULT hr = dc->CreateGradientStopCollection(
+ stops, aNumStops, D2D1_COLOR_SPACE_SRGB, D2D1_COLOR_SPACE_SRGB,
+ D2D1_BUFFER_PRECISION_8BPC_UNORM, D2DExtend(aExtendMode, Axis::BOTH),
+ D2D1_COLOR_INTERPOLATION_MODE_PREMULTIPLIED,
+ getter_AddRefs(stopCollection));
+ delete[] stops;
+
+ if (FAILED(hr)) {
+ gfxWarning() << *this << ": Failed to create GradientStopCollection. Code: "
+ << hexa(hr);
+ return nullptr;
+ }
+
+ RefPtr<ID3D11Device> device = Factory::GetDirect3D11Device();
+ return MakeAndAddRef<GradientStopsD2D>(stopCollection, device);
+}
+
+already_AddRefed<FilterNode> DrawTargetD2D1::CreateFilter(FilterType aType) {
+ if (!EnsureInitialized()) {
+ return nullptr;
+ }
+ return FilterNodeD2D1::Create(mDC, aType);
+}
+
+bool DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat) {
+ RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq);
+ if (!device) {
+ gfxCriticalNote << "[D2D1.1] Failed to obtain a device for "
+ "DrawTargetD2D1::Init(ID3D11Texture2D*, SurfaceFormat).";
+ return false;
+ }
+
+ aTexture->QueryInterface(__uuidof(IDXGISurface),
+ (void**)((IDXGISurface**)getter_AddRefs(mSurface)));
+ if (!mSurface) {
+ gfxCriticalError() << "[D2D1.1] Failed to obtain a DXGI surface.";
+ return false;
+ }
+
+ mFormat = aFormat;
+
+ D3D11_TEXTURE2D_DESC desc;
+ aTexture->GetDesc(&desc);
+ mSize.width = desc.Width;
+ mSize.height = desc.Height;
+
+ return true;
+}
+
+bool DrawTargetD2D1::Init(const IntSize& aSize, SurfaceFormat aFormat) {
+ RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq);
+ if (!device) {
+ gfxCriticalNote << "[D2D1.1] Failed to obtain a device for "
+ "DrawTargetD2D1::Init(IntSize, SurfaceFormat).";
+ return false;
+ }
+
+ if (!CanCreateSimilarDrawTarget(aSize, aFormat)) {
+ // Size unsupported.
+ return false;
+ }
+
+ mFormat = aFormat;
+ mSize = aSize;
+
+ return true;
+}
+
+/**
+ * Private helpers.
+ */
+uint32_t DrawTargetD2D1::GetByteSize() const {
+ return mSize.width * mSize.height * BytesPerPixel(mFormat);
+}
+
+RefPtr<ID2D1Factory1> DrawTargetD2D1::factory() {
+ StaticMutexAutoLock lock(Factory::mDeviceLock);
+
+ if (mFactory || !NS_IsMainThread()) {
+ return mFactory;
+ }
+
+ // We don't allow initializing the factory off the main thread.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ RefPtr<ID2D1Factory> factory;
+ D2D1CreateFactoryFunc createD2DFactory;
+ HMODULE d2dModule = LoadLibraryW(L"d2d1.dll");
+ createD2DFactory =
+ (D2D1CreateFactoryFunc)GetProcAddress(d2dModule, "D2D1CreateFactory");
+
+ if (!createD2DFactory) {
+ gfxWarning() << "Failed to locate D2D1CreateFactory function.";
+ return nullptr;
+ }
+
+ D2D1_FACTORY_OPTIONS options;
+#ifdef _DEBUG
+ options.debugLevel = D2D1_DEBUG_LEVEL_WARNING;
+#else
+ options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
+#endif
+ // options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
+
+ HRESULT hr =
+ createD2DFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, __uuidof(ID2D1Factory),
+ &options, getter_AddRefs(factory));
+
+ if (FAILED(hr) || !factory) {
+ gfxCriticalNote << "Failed to create a D2D1 content device: " << hexa(hr);
+ return nullptr;
+ }
+
+ RefPtr<ID2D1Factory1> factory1;
+ hr = factory->QueryInterface(__uuidof(ID2D1Factory1),
+ getter_AddRefs(factory1));
+ if (FAILED(hr) || !factory1) {
+ return nullptr;
+ }
+
+ mFactory = factory1;
+
+ ExtendInputEffectD2D1::Register(mFactory);
+ ConicGradientEffectD2D1::Register(mFactory);
+ RadialGradientEffectD2D1::Register(mFactory);
+
+ return mFactory;
+}
+
+void DrawTargetD2D1::CleanupD2D() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ Factory::mDeviceLock.AssertCurrentThreadOwns();
+
+ if (mFactory) {
+ RadialGradientEffectD2D1::Unregister(mFactory);
+ ConicGradientEffectD2D1::Unregister(mFactory);
+ ExtendInputEffectD2D1::Unregister(mFactory);
+ mFactory = nullptr;
+ }
+}
+
+void DrawTargetD2D1::FlushInternal(bool aHasDependencyMutex /* = false */) {
+ if (IsDeviceContextValid()) {
+ if ((mUsedCommandListsSincePurge >= kPushedLayersBeforePurge ||
+ mTransformedGlyphsSinceLastPurge >= kTransformedGlyphsBeforePurge) &&
+ mPushedLayers.size() == 1) {
+ // It's important to pop all clips as otherwise layers can forget about
+ // their clip when doing an EndDraw. When we have layers pushed we cannot
+ // easily pop all underlying clips to delay the purge until we have no
+ // layers pushed.
+ PopAllClips();
+ mUsedCommandListsSincePurge = 0;
+ mTransformedGlyphsSinceLastPurge = 0;
+ mDC->EndDraw();
+ mDC->BeginDraw();
+ } else {
+ mDC->Flush();
+ }
+ }
+
+ Maybe<StaticMutexAutoLock> lock;
+
+ if (!aHasDependencyMutex) {
+ lock.emplace(Factory::mDTDependencyLock);
+ }
+
+ Factory::mDTDependencyLock.AssertCurrentThreadOwns();
+ // We no longer depend on any target.
+ for (TargetSet::iterator iter = mDependingOnTargets.begin();
+ iter != mDependingOnTargets.end(); iter++) {
+ (*iter)->mDependentTargets.erase(this);
+ }
+ mDependingOnTargets.clear();
+}
+
+bool DrawTargetD2D1::EnsureInitialized() {
+ if (mInitState != InitState::Uninitialized) {
+ return mInitState == InitState::Success;
+ }
+
+ // Don't retry.
+ mInitState = InitState::Failure;
+
+ HRESULT hr;
+
+ RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq);
+ if (!device) {
+ gfxCriticalNote << "[D2D1.1] Failed to obtain a device for "
+ "DrawTargetD2D1::EnsureInitialized().";
+ return false;
+ }
+
+ hr = device->CreateDeviceContext(
+ D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
+ getter_AddRefs(mDC));
+
+ if (FAILED(hr)) {
+ gfxCriticalError() << "[D2D1.1] 2Failed to create a DeviceContext, code: "
+ << hexa(hr) << " format " << (int)mFormat;
+ return false;
+ }
+
+ if (!mSurface) {
+ if (mDC->GetMaximumBitmapSize() < UINT32(mSize.width) ||
+ mDC->GetMaximumBitmapSize() < UINT32(mSize.height)) {
+ // This is 'ok', so don't assert
+ gfxCriticalNote << "[D2D1.1] Attempt to use unsupported surface size "
+ << mSize;
+ return false;
+ }
+
+ D2D1_BITMAP_PROPERTIES1 props;
+ props.dpiX = 96;
+ props.dpiY = 96;
+ props.pixelFormat = D2DPixelFormat(mFormat);
+ props.colorContext = nullptr;
+ props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
+ hr = mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props,
+ (ID2D1Bitmap1**)getter_AddRefs(mBitmap));
+
+ if (FAILED(hr)) {
+ gfxCriticalError() << "[D2D1.1] 3CreateBitmap failure " << mSize
+ << " Code: " << hexa(hr) << " format " << (int)mFormat;
+ return false;
+ }
+ } else {
+ D2D1_BITMAP_PROPERTIES1 props;
+ props.dpiX = 96;
+ props.dpiY = 96;
+ props.pixelFormat = D2DPixelFormat(mFormat);
+ props.colorContext = nullptr;
+ props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
+ hr = mDC->CreateBitmapFromDxgiSurface(
+ mSurface, props, (ID2D1Bitmap1**)getter_AddRefs(mBitmap));
+
+ if (FAILED(hr)) {
+ gfxCriticalError()
+ << "[D2D1.1] CreateBitmapFromDxgiSurface failure Code: " << hexa(hr)
+ << " format " << (int)mFormat;
+ return false;
+ }
+ }
+
+ mDC->SetTarget(CurrentTarget());
+
+ hr = mDC->CreateSolidColorBrush(D2D1::ColorF(0, 0),
+ getter_AddRefs(mSolidColorBrush));
+
+ if (FAILED(hr)) {
+ gfxCriticalError() << "[D2D1.1] Failure creating solid color brush (I2).";
+ return false;
+ }
+
+ mDC->BeginDraw();
+
+ CurrentLayer().mIsOpaque = mFormat == SurfaceFormat::B8G8R8X8;
+
+ if (!mSurface) {
+ mDC->Clear();
+ }
+
+ mInitState = InitState::Success;
+
+ return true;
+}
+
+void DrawTargetD2D1::MarkChanged() {
+ if (mSnapshot) {
+ MutexAutoLock lock(*mSnapshotLock);
+ if (mSnapshot->hasOneRef()) {
+ // Just destroy it, since no-one else knows about it.
+ mSnapshot = nullptr;
+ } else {
+ mSnapshot->DrawTargetWillChange();
+ // The snapshot will no longer depend on this target.
+ MOZ_ASSERT(!mSnapshot);
+ }
+ }
+
+ {
+ StaticMutexAutoLock lock(Factory::mDTDependencyLock);
+ if (mDependentTargets.size()) {
+ // Copy mDependentTargets since the Flush()es below will modify it.
+ TargetSet tmpTargets = mDependentTargets;
+ for (TargetSet::iterator iter = tmpTargets.begin();
+ iter != tmpTargets.end(); iter++) {
+ (*iter)->FlushInternal(true);
+ }
+ // The Flush() should have broken all dependencies on this target.
+ MOZ_ASSERT(!mDependentTargets.size());
+ }
+ }
+}
+
+bool DrawTargetD2D1::ShouldClipTemporarySurfaceDrawing(CompositionOp aOp,
+ const Pattern& aPattern,
+ bool aClipIsComplex) {
+ bool patternSupported = IsPatternSupportedByD2D(aPattern, aOp);
+ return patternSupported && !CurrentLayer().mIsOpaque &&
+ D2DSupportsCompositeMode(aOp) && IsOperatorBoundByMask(aOp) &&
+ aClipIsComplex;
+}
+
+bool DrawTargetD2D1::PrepareForDrawing(CompositionOp aOp,
+ const Pattern& aPattern) {
+ if (!EnsureInitialized()) {
+ return false;
+ }
+
+ MarkChanged();
+
+ PushAllClips();
+
+ bool patternSupported = IsPatternSupportedByD2D(aPattern, aOp);
+ if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) {
+ // It's important to do this before FlushTransformToDC! As this will cause
+ // the transform to become dirty.
+
+ FlushTransformToDC();
+
+ if (aOp != CompositionOp::OP_OVER) {
+ mDC->SetPrimitiveBlend(D2DPrimitiveBlendMode(aOp));
+ }
+
+ return true;
+ }
+
+ HRESULT result = mDC->CreateCommandList(getter_AddRefs(mCommandList));
+ mDC->SetTarget(mCommandList);
+ mUsedCommandListsSincePurge++;
+
+ // This is where we should have a valid command list. If we don't, something
+ // is wrong, and it's likely an OOM.
+ if (!mCommandList) {
+ gfxDevCrash(LogReason::InvalidCommandList)
+ << "Invalid D2D1.1 command list on creation "
+ << mUsedCommandListsSincePurge << ", " << gfx::hexa(result);
+ }
+
+ D2D1_RECT_F rect;
+ bool isAligned;
+ bool clipIsComplex = CurrentLayer().mPushedClips.size() &&
+ !GetDeviceSpaceClipRect(rect, isAligned);
+
+ if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) {
+ PushClipsToDC(mDC);
+ }
+
+ FlushTransformToDC();
+
+ return true;
+}
+
+void DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp,
+ const Pattern& aPattern) {
+ bool patternSupported = IsPatternSupportedByD2D(aPattern, aOp);
+
+ if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) {
+ if (aOp != CompositionOp::OP_OVER)
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+ return;
+ }
+
+ D2D1_RECT_F rect;
+ bool isAligned;
+ bool clipIsComplex = CurrentLayer().mPushedClips.size() &&
+ !GetDeviceSpaceClipRect(rect, isAligned);
+
+ if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) {
+ PopClipsFromDC(mDC);
+ }
+
+ mDC->SetTarget(CurrentTarget());
+ if (!mCommandList) {
+ gfxDevCrash(LogReason::InvalidCommandList)
+ << "Invalid D21.1 command list on finalize";
+ return;
+ }
+ mCommandList->Close();
+
+ RefPtr<ID2D1CommandList> source = mCommandList;
+ mCommandList = nullptr;
+
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ if (patternSupported) {
+ if (D2DSupportsCompositeMode(aOp)) {
+ RefPtr<ID2D1Image> tmpImage;
+ if (clipIsComplex) {
+ PopAllClips();
+ if (!IsOperatorBoundByMask(aOp)) {
+ tmpImage = GetImageForLayerContent();
+ }
+ }
+ mDC->DrawImage(source, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2DCompositionMode(aOp));
+
+ if (tmpImage) {
+ RefPtr<ID2D1ImageBrush> brush;
+ RefPtr<ID2D1Geometry> inverseGeom = GetInverseClippedGeometry();
+ mDC->CreateImageBrush(tmpImage,
+ D2D1::ImageBrushProperties(
+ D2D1::RectF(0, 0, mSize.width, mSize.height)),
+ getter_AddRefs(brush));
+
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
+ mDC->FillGeometry(inverseGeom, brush);
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+ }
+ return;
+ }
+
+ RefPtr<ID2D1Effect> blendEffect;
+ HRESULT hr =
+ mDC->CreateEffect(CLSID_D2D1Blend, getter_AddRefs(blendEffect));
+
+ if (FAILED(hr) || !blendEffect) {
+ gfxWarning() << "Failed to create blend effect!";
+ return;
+ }
+
+ IntRect bounds(IntPoint(), mSize);
+ RefPtr<ID2D1Geometry> geom;
+ if (CurrentLayer().mPushedClips.size() > 0) {
+ geom = GetClippedGeometry(&bounds);
+ }
+ RefPtr<ID2D1Image> tmpImage = GetImageForLayerContent(&bounds, bool(geom));
+ if (!tmpImage) {
+ return;
+ }
+
+ blendEffect->SetInput(0, tmpImage);
+ blendEffect->SetInput(1, source);
+ blendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp));
+
+ if (geom) {
+ RefPtr<ID2D1Image> blendOutput;
+ blendEffect->GetOutput(getter_AddRefs(blendOutput));
+
+ RefPtr<ID2D1ImageBrush> brush;
+ mDC->CreateImageBrush(
+ blendOutput, D2D1::ImageBrushProperties(D2DRect(bounds)),
+ D2D1::BrushProperties(
+ 1.0f, D2D1::Matrix3x2F::Translation(bounds.x, bounds.y)),
+ getter_AddRefs(brush));
+
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
+ mDC->FillGeometry(geom, brush);
+ mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+ } else {
+ mDC->DrawImage(blendEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
+ }
+
+ mComplexBlendsWithListInList++;
+ return;
+ }
+
+ if (aPattern.GetType() == PatternType::CONIC_GRADIENT) {
+ const ConicGradientPattern* pat =
+ static_cast<const ConicGradientPattern*>(&aPattern);
+
+ if (!pat->mStops ||
+ pat->mStops->GetBackendType() != BackendType::DIRECT2D) {
+ // Draw nothing because of no color stops
+ return;
+ }
+ RefPtr<ID2D1Effect> conicGradientEffect;
+
+ HRESULT hr = mDC->CreateEffect(CLSID_ConicGradientEffect,
+ getter_AddRefs(conicGradientEffect));
+ if (FAILED(hr) || !conicGradientEffect) {
+ gfxWarning() << "Failed to create conic gradient effect. Code: "
+ << hexa(hr);
+ return;
+ }
+
+ PushAllClips();
+
+ conicGradientEffect->SetValue(
+ CONIC_PROP_STOP_COLLECTION,
+ static_cast<const GradientStopsD2D*>(pat->mStops.get())
+ ->mStopCollection);
+ conicGradientEffect->SetValue(
+ CONIC_PROP_CENTER, D2D1::Vector2F(pat->mCenter.x, pat->mCenter.y));
+ conicGradientEffect->SetValue(CONIC_PROP_ANGLE, pat->mAngle);
+ conicGradientEffect->SetValue(CONIC_PROP_START_OFFSET, pat->mStartOffset);
+ conicGradientEffect->SetValue(CONIC_PROP_END_OFFSET, pat->mEndOffset);
+ conicGradientEffect->SetValue(CONIC_PROP_TRANSFORM,
+ D2DMatrix(pat->mMatrix * mTransform));
+ conicGradientEffect->SetInput(0, source);
+
+ mDC->DrawImage(conicGradientEffect,
+ D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2DCompositionMode(aOp));
+ return;
+ }
+
+ MOZ_ASSERT(aPattern.GetType() == PatternType::RADIAL_GRADIENT);
+
+ const RadialGradientPattern* pat =
+ static_cast<const RadialGradientPattern*>(&aPattern);
+ if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) {
+ // Draw nothing!
+ return;
+ }
+
+ if (!pat->mStops || pat->mStops->GetBackendType() != BackendType::DIRECT2D) {
+ // Draw nothing because of no color stops
+ return;
+ }
+
+ RefPtr<ID2D1Effect> radialGradientEffect;
+
+ HRESULT hr = mDC->CreateEffect(CLSID_RadialGradientEffect,
+ getter_AddRefs(radialGradientEffect));
+ if (FAILED(hr) || !radialGradientEffect) {
+ gfxWarning() << "Failed to create radial gradient effect. Code: "
+ << hexa(hr);
+ return;
+ }
+
+ PushAllClips();
+
+ radialGradientEffect->SetValue(
+ RADIAL_PROP_STOP_COLLECTION,
+ static_cast<const GradientStopsD2D*>(pat->mStops.get())->mStopCollection);
+ radialGradientEffect->SetValue(
+ RADIAL_PROP_CENTER_1, D2D1::Vector2F(pat->mCenter1.x, pat->mCenter1.y));
+ radialGradientEffect->SetValue(
+ RADIAL_PROP_CENTER_2, D2D1::Vector2F(pat->mCenter2.x, pat->mCenter2.y));
+ radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_1, pat->mRadius1);
+ radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2);
+ radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2);
+ radialGradientEffect->SetValue(RADIAL_PROP_TRANSFORM,
+ D2DMatrix(pat->mMatrix * mTransform));
+ radialGradientEffect->SetInput(0, source);
+
+ mDC->DrawImage(radialGradientEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2DCompositionMode(aOp));
+}
+
+void DrawTargetD2D1::AddDependencyOnSource(SourceSurfaceD2D1* aSource) {
+ Maybe<MutexAutoLock> snapshotLock;
+ // We grab the SnapshotLock as well, this guaranteeds aSource->mDrawTarget
+ // cannot be cleared in between the if statement and the dereference.
+ if (aSource->mSnapshotLock) {
+ snapshotLock.emplace(*aSource->mSnapshotLock);
+ }
+ {
+ StaticMutexAutoLock lock(Factory::mDTDependencyLock);
+ if (aSource->mDrawTarget &&
+ !mDependingOnTargets.count(aSource->mDrawTarget)) {
+ aSource->mDrawTarget->mDependentTargets.insert(this);
+ mDependingOnTargets.insert(aSource->mDrawTarget);
+ }
+ }
+}
+
+static D2D1_RECT_F IntersectRect(const D2D1_RECT_F& aRect1,
+ const D2D1_RECT_F& aRect2) {
+ D2D1_RECT_F result;
+ result.left = std::max(aRect1.left, aRect2.left);
+ result.top = std::max(aRect1.top, aRect2.top);
+ result.right = std::min(aRect1.right, aRect2.right);
+ result.bottom = std::min(aRect1.bottom, aRect2.bottom);
+
+ result.right = std::max(result.right, result.left);
+ result.bottom = std::max(result.bottom, result.top);
+
+ return result;
+}
+
+bool DrawTargetD2D1::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect,
+ bool& aIsPixelAligned) {
+ aIsPixelAligned = true;
+ aClipRect = D2D1::RectF(0, 0, mSize.width, mSize.height);
+
+ if (!CurrentLayer().mPushedClips.size()) {
+ return false;
+ }
+
+ for (auto iter = CurrentLayer().mPushedClips.begin();
+ iter != CurrentLayer().mPushedClips.end(); iter++) {
+ if (iter->mGeometry) {
+ return false;
+ }
+ aClipRect = IntersectRect(aClipRect, iter->mBounds);
+ if (!iter->mIsPixelAligned) {
+ aIsPixelAligned = false;
+ }
+ }
+ return true;
+}
+
+static const uint32_t sComplexBlendsWithListAllowedInList = 4;
+
+already_AddRefed<ID2D1Image> DrawTargetD2D1::GetImageForLayerContent(
+ const IntRect* aBounds, bool aShouldPreserveContent) {
+ PopAllClips();
+
+ IntRect bounds = aBounds ? *aBounds : IntRect(IntPoint(), mSize);
+ IntSize size(bounds.XMost(), bounds.YMost());
+ if (!CurrentLayer().mCurrentList) {
+ RefPtr<ID2D1Bitmap> tmpBitmap;
+ HRESULT hr = mDC->CreateBitmap(
+ D2DIntSize(size), D2D1::BitmapProperties(D2DPixelFormat(mFormat)),
+ getter_AddRefs(tmpBitmap));
+ if (FAILED(hr)) {
+ gfxCriticalError(
+ CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(size)))
+ << "[D2D1.1] 6CreateBitmap failure " << size << " Code: " << hexa(hr)
+ << " format " << (int)mFormat;
+ // If it's a recreate target error, return and handle it elsewhere.
+ if (hr == D2DERR_RECREATE_TARGET) {
+ mDC->Flush();
+ return nullptr;
+ }
+ // For now, crash in other scenarios; this should happen because tmpBitmap
+ // is null and CopyFromBitmap call below dereferences it.
+ }
+ mDC->Flush();
+
+ D2D1_POINT_2U destOffset = D2D1::Point2U(bounds.x, bounds.y);
+ D2D1_RECT_U srcRect =
+ D2D1::RectU(bounds.x, bounds.y, bounds.width, bounds.height);
+ tmpBitmap->CopyFromBitmap(&destOffset, mBitmap, &srcRect);
+ return tmpBitmap.forget();
+ } else {
+ RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList;
+ mDC->CreateCommandList(getter_AddRefs(CurrentLayer().mCurrentList));
+ mDC->SetTarget(CurrentTarget());
+ list->Close();
+
+ RefPtr<ID2D1Bitmap1> tmpBitmap;
+ if (mComplexBlendsWithListInList >= sComplexBlendsWithListAllowedInList) {
+ D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
+ D2D1_BITMAP_OPTIONS_TARGET,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED));
+ mDC->CreateBitmap(D2DIntSize(size), nullptr, 0, &props,
+ getter_AddRefs(tmpBitmap));
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+ mDC->SetTarget(tmpBitmap);
+ mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
+ D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
+ mDC->SetTarget(CurrentTarget());
+ mComplexBlendsWithListInList = 0;
+ }
+
+ DCCommandSink sink(mDC);
+
+ if (aShouldPreserveContent) {
+ list->Stream(&sink);
+ }
+
+ if (tmpBitmap) {
+ return tmpBitmap.forget();
+ }
+
+ return list.forget();
+ }
+}
+
+already_AddRefed<ID2D1Geometry> DrawTargetD2D1::GetClippedGeometry(
+ IntRect* aClipBounds) {
+ if (mCurrentClippedGeometry) {
+ *aClipBounds = mCurrentClipBounds;
+ RefPtr<ID2D1Geometry> clippedGeometry(mCurrentClippedGeometry);
+ return clippedGeometry.forget();
+ }
+
+ MOZ_ASSERT(CurrentLayer().mPushedClips.size());
+
+ mCurrentClipBounds = IntRect(IntPoint(0, 0), mSize);
+
+ // if pathGeom is null then pathRect represents the path.
+ RefPtr<ID2D1Geometry> pathGeom;
+ D2D1_RECT_F pathRect;
+ bool pathRectIsAxisAligned = false;
+ auto iter = CurrentLayer().mPushedClips.begin();
+
+ if (iter->mGeometry) {
+ pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform);
+ } else {
+ pathRect = iter->mBounds;
+ pathRectIsAxisAligned = iter->mIsPixelAligned;
+ }
+
+ iter++;
+ for (; iter != CurrentLayer().mPushedClips.end(); iter++) {
+ // Do nothing but add it to the current clip bounds.
+ if (!iter->mGeometry && iter->mIsPixelAligned) {
+ mCurrentClipBounds.IntersectRect(
+ mCurrentClipBounds,
+ IntRect(int32_t(iter->mBounds.left), int32_t(iter->mBounds.top),
+ int32_t(iter->mBounds.right - iter->mBounds.left),
+ int32_t(iter->mBounds.bottom - iter->mBounds.top)));
+ continue;
+ }
+
+ if (!pathGeom) {
+ if (pathRectIsAxisAligned) {
+ mCurrentClipBounds.IntersectRect(
+ mCurrentClipBounds,
+ IntRect(int32_t(pathRect.left), int32_t(pathRect.top),
+ int32_t(pathRect.right - pathRect.left),
+ int32_t(pathRect.bottom - pathRect.top)));
+ }
+ if (iter->mGeometry) {
+ // See if pathRect needs to go into the path geometry.
+ if (!pathRectIsAxisAligned) {
+ pathGeom = ConvertRectToGeometry(pathRect);
+ } else {
+ pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform);
+ }
+ } else {
+ pathRect = IntersectRect(pathRect, iter->mBounds);
+ pathRectIsAxisAligned = false;
+ continue;
+ }
+ }
+
+ RefPtr<ID2D1PathGeometry> newGeom;
+ factory()->CreatePathGeometry(getter_AddRefs(newGeom));
+
+ RefPtr<ID2D1GeometrySink> currentSink;
+ newGeom->Open(getter_AddRefs(currentSink));
+
+ if (iter->mGeometry) {
+ pathGeom->CombineWithGeometry(iter->mGeometry,
+ D2D1_COMBINE_MODE_INTERSECT,
+ iter->mTransform, currentSink);
+ } else {
+ RefPtr<ID2D1Geometry> rectGeom = ConvertRectToGeometry(iter->mBounds);
+ pathGeom->CombineWithGeometry(rectGeom, D2D1_COMBINE_MODE_INTERSECT,
+ D2D1::IdentityMatrix(), currentSink);
+ }
+
+ currentSink->Close();
+
+ pathGeom = newGeom.forget();
+ }
+
+ // For now we need mCurrentClippedGeometry to always be non-nullptr. This
+ // method might seem a little strange but it is just fine, if pathGeom is
+ // nullptr pathRect will always still contain 1 clip unaccounted for
+ // regardless of mCurrentClipBounds.
+ if (!pathGeom) {
+ pathGeom = ConvertRectToGeometry(pathRect);
+ }
+ mCurrentClippedGeometry = pathGeom.forget();
+ *aClipBounds = mCurrentClipBounds;
+ RefPtr<ID2D1Geometry> clippedGeometry(mCurrentClippedGeometry);
+ return clippedGeometry.forget();
+}
+
+already_AddRefed<ID2D1Geometry> DrawTargetD2D1::GetInverseClippedGeometry() {
+ IntRect bounds;
+ RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&bounds);
+ RefPtr<ID2D1RectangleGeometry> rectGeom;
+ RefPtr<ID2D1PathGeometry> inverseGeom;
+
+ factory()->CreateRectangleGeometry(
+ D2D1::RectF(0, 0, mSize.width, mSize.height), getter_AddRefs(rectGeom));
+ factory()->CreatePathGeometry(getter_AddRefs(inverseGeom));
+ RefPtr<ID2D1GeometrySink> sink;
+ inverseGeom->Open(getter_AddRefs(sink));
+ rectGeom->CombineWithGeometry(geom, D2D1_COMBINE_MODE_EXCLUDE,
+ D2D1::IdentityMatrix(), sink);
+ sink->Close();
+
+ return inverseGeom.forget();
+}
+
+void DrawTargetD2D1::PopAllClips() {
+ if (CurrentLayer().mClipsArePushed) {
+ PopClipsFromDC(mDC);
+
+ CurrentLayer().mClipsArePushed = false;
+ }
+}
+
+void DrawTargetD2D1::PushAllClips() {
+ if (!CurrentLayer().mClipsArePushed) {
+ PushClipsToDC(mDC);
+
+ CurrentLayer().mClipsArePushed = true;
+ }
+}
+
+void DrawTargetD2D1::PushClipsToDC(ID2D1DeviceContext* aDC,
+ bool aForceIgnoreAlpha,
+ const D2D1_RECT_F& aMaxRect) {
+ mDC->SetTransform(D2D1::IdentityMatrix());
+ mTransformDirty = true;
+
+ for (auto iter = CurrentLayer().mPushedClips.begin();
+ iter != CurrentLayer().mPushedClips.end(); iter++) {
+ if (iter->mGeometry) {
+ PushD2DLayer(aDC, iter->mGeometry, iter->mTransform,
+ iter->mIsPixelAligned, aForceIgnoreAlpha, aMaxRect);
+ } else {
+ mDC->PushAxisAlignedClip(iter->mBounds,
+ iter->mIsPixelAligned
+ ? D2D1_ANTIALIAS_MODE_ALIASED
+ : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+ }
+ }
+}
+
+void DrawTargetD2D1::PopClipsFromDC(ID2D1DeviceContext* aDC) {
+ for (int i = CurrentLayer().mPushedClips.size() - 1; i >= 0; i--) {
+ if (CurrentLayer().mPushedClips[i].mGeometry) {
+ aDC->PopLayer();
+ } else {
+ aDC->PopAxisAlignedClip();
+ }
+ }
+}
+
+already_AddRefed<ID2D1Brush> DrawTargetD2D1::CreateTransparentBlackBrush() {
+ return GetSolidColorBrush(D2D1::ColorF(0, 0));
+}
+
+already_AddRefed<ID2D1SolidColorBrush> DrawTargetD2D1::GetSolidColorBrush(
+ const D2D_COLOR_F& aColor) {
+ RefPtr<ID2D1SolidColorBrush> brush = mSolidColorBrush;
+ brush->SetColor(aColor);
+ return brush.forget();
+}
+
+already_AddRefed<ID2D1Brush> DrawTargetD2D1::CreateBrushForPattern(
+ const Pattern& aPattern, const DrawOptions& aOptions) {
+ if (!IsPatternSupportedByD2D(aPattern) ||
+ aOptions.mCompositionOp == CompositionOp::OP_CLEAR) {
+ return GetSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f));
+ }
+
+ if (aPattern.GetType() == PatternType::COLOR) {
+ DeviceColor color = static_cast<const ColorPattern*>(&aPattern)->mColor;
+ return GetSolidColorBrush(
+ D2D1::ColorF(color.r, color.g, color.b, color.a * aOptions.mAlpha));
+ }
+ if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
+ RefPtr<ID2D1LinearGradientBrush> gradBrush;
+ const LinearGradientPattern* pat =
+ static_cast<const LinearGradientPattern*>(&aPattern);
+
+ if (!pat->mStops ||
+ pat->mStops->GetBackendType() != BackendType::DIRECT2D) {
+ gfxDebug() << "No stops specified for gradient pattern.";
+ return CreateTransparentBlackBrush();
+ }
+
+ if (pat->mBegin == pat->mEnd) {
+ return CreateTransparentBlackBrush();
+ }
+
+ GradientStopsD2D* stops = static_cast<GradientStopsD2D*>(pat->mStops.get());
+
+ mDC->CreateLinearGradientBrush(
+ D2D1::LinearGradientBrushProperties(D2DPoint(pat->mBegin),
+ D2DPoint(pat->mEnd)),
+ D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(pat->mMatrix)),
+ stops->mStopCollection, getter_AddRefs(gradBrush));
+
+ if (!gradBrush) {
+ gfxWarning() << "Couldn't create gradient brush.";
+ return CreateTransparentBlackBrush();
+ }
+
+ return gradBrush.forget();
+ }
+ if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
+ RefPtr<ID2D1RadialGradientBrush> gradBrush;
+ const RadialGradientPattern* pat =
+ static_cast<const RadialGradientPattern*>(&aPattern);
+
+ if (!pat->mStops ||
+ pat->mStops->GetBackendType() != BackendType::DIRECT2D) {
+ gfxDebug() << "No stops specified for gradient pattern.";
+ return CreateTransparentBlackBrush();
+ }
+
+ if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) {
+ return CreateTransparentBlackBrush();
+ }
+
+ GradientStopsD2D* stops = static_cast<GradientStopsD2D*>(pat->mStops.get());
+
+ // This will not be a complex radial gradient brush.
+ mDC->CreateRadialGradientBrush(
+ D2D1::RadialGradientBrushProperties(
+ D2DPoint(pat->mCenter2), D2DPoint(pat->mCenter1 - pat->mCenter2),
+ pat->mRadius2, pat->mRadius2),
+ D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(pat->mMatrix)),
+ stops->mStopCollection, getter_AddRefs(gradBrush));
+
+ if (!gradBrush) {
+ gfxWarning() << "Couldn't create gradient brush.";
+ return CreateTransparentBlackBrush();
+ }
+
+ return gradBrush.forget();
+ }
+ if (aPattern.GetType() == PatternType::SURFACE) {
+ const SurfacePattern* pat = static_cast<const SurfacePattern*>(&aPattern);
+
+ if (!pat->mSurface) {
+ gfxDebug() << "No source surface specified for surface pattern";
+ return CreateTransparentBlackBrush();
+ }
+
+ D2D1_RECT_F samplingBounds;
+ Matrix mat = pat->mMatrix;
+
+ MOZ_ASSERT(pat->mSurface->IsValid());
+
+ RefPtr<SourceSurface> surf = pat->mSurface;
+
+ RefPtr<ID2D1Image> image = GetImageForSurface(
+ surf, mat, pat->mExtendMode,
+ !pat->mSamplingRect.IsEmpty() ? &pat->mSamplingRect : nullptr);
+
+ if (!image) {
+ return CreateTransparentBlackBrush();
+ }
+
+ if (surf->GetFormat() == SurfaceFormat::A8) {
+ // See bug 1251431, at least FillOpacityMask does not appear to allow a
+ // source bitmapbrush with source format A8. This creates a BGRA surface
+ // with the same alpha values that the A8 surface has.
+ RefPtr<ID2D1Bitmap> bitmap;
+ HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
+ if (SUCCEEDED(hr) && bitmap) {
+ RefPtr<ID2D1Image> oldTarget;
+ RefPtr<ID2D1Bitmap1> tmpBitmap;
+ mDC->CreateBitmap(D2D1::SizeU(pat->mSurface->GetSize().width,
+ pat->mSurface->GetSize().height),
+ nullptr, 0,
+ D2D1::BitmapProperties1(
+ D2D1_BITMAP_OPTIONS_TARGET,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED)),
+ getter_AddRefs(tmpBitmap));
+
+ if (!tmpBitmap) {
+ return CreateTransparentBlackBrush();
+ }
+
+ mDC->GetTarget(getter_AddRefs(oldTarget));
+ mDC->SetTarget(tmpBitmap);
+
+ RefPtr<ID2D1SolidColorBrush> brush;
+ mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White),
+ getter_AddRefs(brush));
+ mDC->FillOpacityMask(bitmap, brush);
+ mDC->SetTarget(oldTarget);
+ image = tmpBitmap;
+ }
+ }
+
+ if (pat->mSamplingRect.IsEmpty()) {
+ RefPtr<ID2D1Bitmap> bitmap;
+ HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
+ if (SUCCEEDED(hr) && bitmap) {
+ /**
+ * Create the brush with the proper repeat modes.
+ */
+ RefPtr<ID2D1BitmapBrush> bitmapBrush;
+ D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
+ D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
+
+ mDC->CreateBitmapBrush(
+ bitmap,
+ D2D1::BitmapBrushProperties(xRepeat, yRepeat,
+ D2DFilter(pat->mSamplingFilter)),
+ D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)),
+ getter_AddRefs(bitmapBrush));
+ if (!bitmapBrush) {
+ gfxWarning() << "Couldn't create bitmap brush!";
+ return CreateTransparentBlackBrush();
+ }
+ return bitmapBrush.forget();
+ }
+ }
+
+ RefPtr<ID2D1ImageBrush> imageBrush;
+ if (pat->mSamplingRect.IsEmpty()) {
+ samplingBounds = D2D1::RectF(0, 0, Float(pat->mSurface->GetSize().width),
+ Float(pat->mSurface->GetSize().height));
+ } else if (surf->GetType() == SurfaceType::D2D1_1_IMAGE) {
+ samplingBounds = D2DRect(pat->mSamplingRect);
+ mat.PreTranslate(pat->mSamplingRect.X(), pat->mSamplingRect.Y());
+ } else {
+ // We will do a partial upload of the sampling restricted area from
+ // GetImageForSurface.
+ samplingBounds = D2D1::RectF(0, 0, pat->mSamplingRect.Width(),
+ pat->mSamplingRect.Height());
+ }
+
+ D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
+ D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
+
+ mDC->CreateImageBrush(
+ image,
+ D2D1::ImageBrushProperties(samplingBounds, xRepeat, yRepeat,
+ D2DInterpolationMode(pat->mSamplingFilter)),
+ D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)),
+ getter_AddRefs(imageBrush));
+
+ if (!imageBrush) {
+ gfxWarning() << "Couldn't create image brush!";
+ return CreateTransparentBlackBrush();
+ }
+
+ return imageBrush.forget();
+ }
+
+ gfxWarning() << "Invalid pattern type detected.";
+ return CreateTransparentBlackBrush();
+}
+
+already_AddRefed<ID2D1Image> DrawTargetD2D1::GetImageForSurface(
+ SourceSurface* aSurface, Matrix& aSourceTransform, ExtendMode aExtendMode,
+ const IntRect* aSourceRect, bool aUserSpace) {
+ RefPtr<ID2D1Image> image;
+ RefPtr<SourceSurface> surface = aSurface->GetUnderlyingSurface();
+
+ if (!surface) {
+ return nullptr;
+ }
+
+ switch (surface->GetType()) {
+ case SurfaceType::D2D1_1_IMAGE: {
+ SourceSurfaceD2D1* surf = static_cast<SourceSurfaceD2D1*>(surface.get());
+ image = surf->GetImage();
+ AddDependencyOnSource(surf);
+ } break;
+ default: {
+ RefPtr<DataSourceSurface> dataSurf = surface->GetDataSurface();
+ if (!dataSurf) {
+ gfxWarning() << "Invalid surface type.";
+ return nullptr;
+ }
+ Matrix transform = aUserSpace ? mTransform : Matrix();
+ return CreatePartialBitmapForSurface(dataSurf, transform, mSize,
+ aExtendMode, aSourceTransform, mDC,
+ aSourceRect);
+ } break;
+ }
+
+ return image.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetD2D1::OptimizeSourceSurface(
+ SourceSurface* aSurface) const {
+ if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext();
+ if (!dc) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+
+ std::optional<SurfaceFormat> convertTo;
+ switch (data->GetFormat()) {
+ case gfx::SurfaceFormat::R8G8B8X8:
+ convertTo = SurfaceFormat::B8G8R8X8;
+ break;
+ case gfx::SurfaceFormat::R8G8B8A8:
+ convertTo = SurfaceFormat::B8G8R8X8;
+ break;
+ default:
+ break;
+ }
+
+ if (convertTo) {
+ const auto size = data->GetSize();
+ const RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTarget(BackendType::SKIA, size, *convertTo);
+ if (!dt) {
+ return nullptr;
+ }
+ dt->CopySurface(data, {{}, size}, {});
+
+ const RefPtr<SourceSurface> snapshot = dt->Snapshot();
+ data = snapshot->GetDataSurface();
+ }
+
+ RefPtr<ID2D1Bitmap1> bitmap;
+ {
+ DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ);
+ if (MOZ2D_WARN_IF(!map.IsMapped())) {
+ return nullptr;
+ }
+
+ HRESULT hr = dc->CreateBitmap(
+ D2DIntSize(data->GetSize()), map.GetData(), map.GetStride(),
+ D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE,
+ D2DPixelFormat(data->GetFormat())),
+ getter_AddRefs(bitmap));
+
+ if (FAILED(hr)) {
+ gfxCriticalError(CriticalLog::DefaultOptions(
+ Factory::ReasonableSurfaceSize(data->GetSize())))
+ << "[D2D1.1] 4CreateBitmap failure " << data->GetSize()
+ << " Code: " << hexa(hr) << " format " << (int)data->GetFormat();
+ }
+ }
+
+ if (!bitmap) {
+ return data.forget();
+ }
+
+ return MakeAndAddRef<SourceSurfaceD2D1>(bitmap.get(), dc.get(),
+ data->GetFormat(), data->GetSize());
+}
+
+void DrawTargetD2D1::PushD2DLayer(ID2D1DeviceContext* aDC,
+ ID2D1Geometry* aGeometry,
+ const D2D1_MATRIX_3X2_F& aTransform,
+ bool aPixelAligned, bool aForceIgnoreAlpha,
+ const D2D1_RECT_F& aMaxRect) {
+ D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE;
+
+ if (CurrentLayer().mIsOpaque || aForceIgnoreAlpha) {
+ options = D2D1_LAYER_OPTIONS1_IGNORE_ALPHA |
+ D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND;
+ }
+
+ D2D1_ANTIALIAS_MODE antialias = aPixelAligned
+ ? D2D1_ANTIALIAS_MODE_ALIASED
+ : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
+
+ mDC->PushLayer(D2D1::LayerParameters1(aMaxRect, aGeometry, antialias,
+ aTransform, 1.0, nullptr, options),
+ nullptr);
+}
+
+bool DrawTargetD2D1::IsDeviceContextValid() const {
+ uint32_t seqNo;
+ return mDC && Factory::GetD2D1Device(&seqNo) && seqNo == mDeviceSeq;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DrawTargetD2D1.h b/gfx/2d/DrawTargetD2D1.h
new file mode 100644
index 0000000000..770b3d2866
--- /dev/null
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -0,0 +1,332 @@
+/* -*- 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_DRAWTARGETD2D1_H_
+#define MOZILLA_GFX_DRAWTARGETD2D1_H_
+
+#include "2D.h"
+#include <d3d11.h>
+#include <d2d1_1.h>
+#include "PathD2D.h"
+#include "HelpersD2D.h"
+#include "mozilla/StaticPtr.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 bool IsValid() const override;
+ 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 already_AddRefed<SourceSurface> IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) override;
+ virtual IntSize GetSize() const 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 ShadowOptions& aShadow,
+ 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 FillRoundedRect(
+ const RoundedRect& 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()) 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 bool RemoveAllClips() 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 bool CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const override;
+ virtual RefPtr<DrawTarget> CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) override;
+
+ virtual already_AddRefed<PathBuilder> CreatePathBuilder(
+ FillRule aFillRule = FillRule::FILL_WINDING) const override {
+ return PathBuilderD2D::Create(aFillRule);
+ }
+
+ 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(); }
+
+ 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. By default, the surface and its transform are
+ // interpreted in user-space, but may be specified in device-space instead.
+ already_AddRefed<ID2D1Image> GetImageForSurface(
+ SourceSurface* aSurface, Matrix& aSourceTransform, ExtendMode aExtendMode,
+ const IntRect* aSourceRect = nullptr, bool aUserSpace = true);
+
+ already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface* aSurface,
+ ExtendMode aExtendMode) {
+ Matrix mat;
+ return GetImageForSurface(aSurface, mat, aExtendMode, nullptr);
+ }
+
+ static RefPtr<ID2D1Factory1> factory();
+ static void CleanupD2D();
+
+ 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;
+
+ void FlushInternal(bool aHasDependencyMutex = false);
+ bool EnsureInitialized();
+
+ 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);
+ bool PrepareForDrawing(CompositionOp aOp, const Pattern& aPattern);
+ void FinalizeDrawing(CompositionOp aOp, const Pattern& aPattern);
+ bool MaybeClearRect(CompositionOp aOp, const Rect& aBounds);
+ 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(
+ const IntRect* aBounds = nullptr, 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, const DrawOptions& aOptions);
+
+ 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());
+
+ // This function is used to determine if the mDC is still valid; if it is
+ // stale, we should avoid using it to execute any draw commands.
+ bool IsDeviceContextValid() const;
+
+ IntSize mSize;
+
+ 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;
+ std::shared_ptr<Mutex> mSnapshotLock;
+ // 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;
+ uint32_t mTransformedGlyphsSinceLastPurge;
+ // 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)
+ uint32_t mComplexBlendsWithListInList;
+
+ static StaticRefPtr<ID2D1Factory1> mFactory;
+ // This value is uesed to verify if the DrawTarget is created by a stale
+ // device.
+ uint32_t mDeviceSeq;
+
+ // List of effects we use
+ bool EnsureLuminanceEffect();
+ RefPtr<ID2D1Effect> mLuminanceEffect;
+
+ enum class InitState { Uninitialized, Success, Failure };
+ InitState mInitState;
+ RefPtr<IDXGISurface> mSurface;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_DRAWTARGETD2D_H_ */
diff --git a/gfx/2d/DrawTargetOffset.cpp b/gfx/2d/DrawTargetOffset.cpp
new file mode 100644
index 0000000000..cc49cf0493
--- /dev/null
+++ b/gfx/2d/DrawTargetOffset.cpp
@@ -0,0 +1,226 @@
+/* -*- 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 "DrawTargetOffset.h"
+#include "Logging.h"
+#include "PathHelpers.h"
+
+namespace mozilla {
+namespace gfx {
+
+DrawTargetOffset::DrawTargetOffset() = default;
+
+bool DrawTargetOffset::Init(DrawTarget* aDrawTarget, IntPoint aOrigin) {
+ mDrawTarget = aDrawTarget;
+ mOrigin = aOrigin;
+ mDrawTarget->SetTransform(Matrix::Translation(-mOrigin.x, -mOrigin.y));
+ mFormat = mDrawTarget->GetFormat();
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+ return true;
+}
+
+already_AddRefed<SourceSurface> DrawTargetOffset::Snapshot() {
+ RefPtr<SourceSurface> snapshot = mDrawTarget->Snapshot();
+
+ if (!snapshot) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<SourceSurfaceOffset>(snapshot, mOrigin);
+}
+
+void DrawTargetOffset::DetachAllSnapshots() {}
+
+// Skip the mClippedOut check since this is only used for Flush() which
+// should happen even if we're clipped.
+#define OFFSET_COMMAND(command) \
+ void DrawTargetOffset::command() { mDrawTarget->command(); }
+#define OFFSET_COMMAND1(command, type1) \
+ void DrawTargetOffset::command(type1 arg1) { mDrawTarget->command(arg1); }
+#define OFFSET_COMMAND3(command, type1, type2, type3) \
+ void DrawTargetOffset::command(type1 arg1, type2 arg2, type3 arg3) { \
+ mDrawTarget->command(arg1, arg2, arg3); \
+ }
+#define OFFSET_COMMAND4(command, type1, type2, type3, type4) \
+ void DrawTargetOffset::command(type1 arg1, type2 arg2, type3 arg3, \
+ type4 arg4) { \
+ mDrawTarget->command(arg1, arg2, arg3, arg4); \
+ }
+#define OFFSET_COMMAND5(command, type1, type2, type3, type4, type5) \
+ void DrawTargetOffset::command(type1 arg1, type2 arg2, type3 arg3, \
+ type4 arg4, type5 arg5) { \
+ mDrawTarget->command(arg1, arg2, arg3, arg4, arg5); \
+ }
+
+OFFSET_COMMAND(Flush)
+OFFSET_COMMAND1(ClearRect, const Rect&)
+OFFSET_COMMAND4(MaskSurface, const Pattern&, SourceSurface*, Point,
+ const DrawOptions&)
+OFFSET_COMMAND4(FillGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&,
+ const DrawOptions&)
+OFFSET_COMMAND5(StrokeGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&,
+ const StrokeOptions&, const DrawOptions&)
+OFFSET_COMMAND3(FillRoundedRect, const RoundedRect&, const Pattern&,
+ const DrawOptions&)
+
+bool DrawTargetOffset::Draw3DTransformedSurface(SourceSurface* aSrc,
+ const Matrix4x4& aMatrix) {
+ return mDrawTarget->Draw3DTransformedSurface(aSrc, aMatrix);
+}
+
+OFFSET_COMMAND3(Mask, const Pattern&, const Pattern&, const DrawOptions&)
+
+void DrawTargetOffset::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ auto clone = mTransform;
+ bool invertible = clone.Invert();
+ // aSourceRect is in filter space. The filter outputs from aSourceRect need
+ // to be drawn at aDestPoint in user space.
+ Rect userSpaceSource = Rect(aDestPoint, aSourceRect.Size());
+ if (invertible) {
+ // Try to reduce the source rect so that it's not much bigger
+ // than the draw target. The result is not minimal. Examples
+ // are left as an exercise for the reader.
+ auto destRect = Rect(mDrawTarget->GetRect() + mOrigin);
+ Rect userSpaceBounds = clone.TransformBounds(destRect);
+ userSpaceSource = userSpaceSource.Intersect(userSpaceBounds);
+ }
+
+ // Compute how much we moved the top-left of the source rect by, and use that
+ // to compute the new dest point, and move our intersected source rect back
+ // into the (new) filter space.
+ Point shift = userSpaceSource.TopLeft() - aDestPoint;
+ Rect filterSpaceSource =
+ Rect(aSourceRect.TopLeft() + shift, userSpaceSource.Size());
+ mDrawTarget->DrawFilter(aNode, filterSpaceSource, aDestPoint + shift,
+ aOptions);
+}
+
+void DrawTargetOffset::PushClip(const Path* aPath) {
+ mDrawTarget->PushClip(aPath);
+}
+
+void DrawTargetOffset::PushClipRect(const Rect& aRect) {
+ mDrawTarget->PushClipRect(aRect);
+}
+
+void DrawTargetOffset::PopClip() { mDrawTarget->PopClip(); }
+
+void DrawTargetOffset::CopySurface(SourceSurface* aSurface,
+ const IntRect& aSourceRect,
+ const IntPoint& aDestination) {
+ IntPoint tileOrigin = mOrigin;
+ // CopySurface ignores the transform, account for that here.
+ mDrawTarget->CopySurface(aSurface, aSourceRect, aDestination - tileOrigin);
+}
+
+void DrawTargetOffset::SetTransform(const Matrix& aTransform) {
+ Matrix mat = aTransform;
+ mat.PostTranslate(Float(-mOrigin.x), Float(-mOrigin.y));
+ mDrawTarget->SetTransform(mat);
+
+ DrawTarget::SetTransform(aTransform);
+}
+
+void DrawTargetOffset::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
+ mDrawTarget->SetPermitSubpixelAA(aPermitSubpixelAA);
+}
+
+void DrawTargetOffset::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource,
+ const DrawSurfaceOptions& aSurfaceOptions,
+ const DrawOptions& aDrawOptions) {
+ mDrawTarget->DrawSurface(aSurface, aDest, aSource, aSurfaceOptions,
+ aDrawOptions);
+}
+
+void DrawTargetOffset::FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aDrawOptions) {
+ mDrawTarget->FillRect(aRect, aPattern, aDrawOptions);
+}
+
+void DrawTargetOffset::Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aDrawOptions) {
+ mDrawTarget->Stroke(aPath, aPattern, aStrokeOptions, aDrawOptions);
+}
+
+void DrawTargetOffset::StrokeRect(const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aDrawOptions) {
+ mDrawTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aDrawOptions);
+}
+
+void DrawTargetOffset::StrokeLine(const Point& aStart, const Point& aEnd,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aDrawOptions) {
+ mDrawTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aDrawOptions);
+}
+
+void DrawTargetOffset::Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aDrawOptions) {
+ mDrawTarget->Fill(aPath, aPattern, aDrawOptions);
+}
+
+void DrawTargetOffset::PushLayer(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground) {
+ IntRect bounds = aBounds - mOrigin;
+
+ mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, bounds,
+ aCopyBackground);
+ SetPermitSubpixelAA(mDrawTarget->GetPermitSubpixelAA());
+}
+
+already_AddRefed<SourceSurface> DrawTargetOffset::IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) {
+ RefPtr<SourceSurface> surface =
+ mDrawTarget->IntoLuminanceSource(aLuminanceType, aOpacity);
+
+ if (!surface) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<SourceSurfaceOffset>(surface, mOrigin);
+}
+
+void DrawTargetOffset::PushLayerWithBlend(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds,
+ bool aCopyBackground,
+ CompositionOp aOp) {
+ IntRect bounds = aBounds - mOrigin;
+
+ mDrawTarget->PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform,
+ bounds, aCopyBackground, aOp);
+ SetPermitSubpixelAA(mDrawTarget->GetPermitSubpixelAA());
+}
+
+void DrawTargetOffset::PopLayer() {
+ mDrawTarget->PopLayer();
+ SetPermitSubpixelAA(mDrawTarget->GetPermitSubpixelAA());
+}
+
+RefPtr<DrawTarget> DrawTargetOffset::CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) {
+ RefPtr<DrawTarget> result;
+ RefPtr<DrawTarget> dt =
+ mDrawTarget->CreateClippedDrawTarget(aBounds, aFormat);
+ if (dt) {
+ result = gfx::Factory::CreateOffsetDrawTarget(dt, mOrigin);
+ if (result) {
+ result->SetTransform(mTransform);
+ }
+ }
+ return result;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DrawTargetOffset.h b/gfx/2d/DrawTargetOffset.h
new file mode 100644
index 0000000000..3356cc15bf
--- /dev/null
+++ b/gfx/2d/DrawTargetOffset.h
@@ -0,0 +1,191 @@
+/* -*- 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_DRAWTARGETOFFSET_H_
+#define MOZILLA_GFX_DRAWTARGETOFFSET_H_
+
+#include "2D.h"
+
+#include "mozilla/Vector.h"
+
+#include "Filters.h"
+#include "Logging.h"
+
+#include <vector>
+
+namespace mozilla {
+namespace gfx {
+
+class SourceSurfaceOffset : public SourceSurface {
+ public:
+ SourceSurfaceOffset(RefPtr<SourceSurface> aSurface, IntPoint aOffset)
+ : mSurface(aSurface), mOffset(aOffset) {
+ MOZ_RELEASE_ASSERT(mSurface);
+ }
+
+ virtual SurfaceType GetType() const override { return SurfaceType::OFFSET; }
+ virtual IntSize GetSize() const override { return mSurface->GetSize(); }
+ virtual IntRect GetRect() const override {
+ return mSurface->GetRect() + mOffset;
+ }
+ virtual SurfaceFormat GetFormat() const override {
+ return mSurface->GetFormat();
+ }
+ virtual already_AddRefed<DataSourceSurface> GetDataSurface() override {
+ return mSurface->GetDataSurface();
+ }
+ virtual already_AddRefed<SourceSurface> GetUnderlyingSurface() override {
+ return mSurface->GetUnderlyingSurface();
+ }
+
+ private:
+ RefPtr<SourceSurface> mSurface;
+ IntPoint mOffset;
+};
+
+class DrawTargetOffset : public DrawTarget {
+ public:
+ DrawTargetOffset();
+
+ bool Init(DrawTarget* aDrawTarget, IntPoint aOrigin);
+
+ // We'll pestimistically return true here
+ virtual bool IsTiledDrawTarget() const override { return true; }
+
+ virtual DrawTargetType GetType() const override {
+ return mDrawTarget->GetType();
+ }
+ virtual BackendType GetBackendType() const override {
+ return mDrawTarget->GetBackendType();
+ }
+ virtual already_AddRefed<SourceSurface> Snapshot() override;
+ virtual already_AddRefed<SourceSurface> IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) override;
+ virtual void DetachAllSnapshots() override;
+ virtual IntSize GetSize() const override { return mDrawTarget->GetSize(); }
+ virtual IntRect GetRect() const override {
+ return mDrawTarget->GetRect() + mOrigin;
+ }
+
+ 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 ShadowOptions& aShadow,
+ 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 FillRoundedRect(
+ const RoundedRect& 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()) override;
+ virtual void StrokeGlyphs(
+ ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions = StrokeOptions(),
+ const DrawOptions& aOptions = DrawOptions()) 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 PushLayerWithBlend(
+ bool aOpaque, Float aOpacity, SourceSurface* aMask,
+ const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(),
+ bool aCopyBackground = false,
+ CompositionOp = CompositionOp::OP_OVER) override;
+ virtual bool Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) override;
+ virtual void PopLayer() override;
+
+ virtual void SetTransform(const Matrix& aTransform) override;
+
+ virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override;
+
+ virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat) const override {
+ return mDrawTarget->CreateSourceSurfaceFromData(aData, aSize, aStride,
+ aFormat);
+ }
+ virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(
+ SourceSurface* aSurface) const override {
+ return mDrawTarget->OptimizeSourceSurface(aSurface);
+ }
+
+ virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const override {
+ return mDrawTarget->CreateSourceSurfaceFromNativeSurface(aSurface);
+ }
+
+ virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const override {
+ return mDrawTarget->CreateSimilarDrawTarget(aSize, aFormat);
+ }
+
+ virtual bool CanCreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const override {
+ return mDrawTarget->CanCreateSimilarDrawTarget(aSize, aFormat);
+ }
+ virtual RefPtr<DrawTarget> CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) override;
+
+ virtual already_AddRefed<PathBuilder> CreatePathBuilder(
+ FillRule aFillRule = FillRule::FILL_WINDING) const override {
+ return mDrawTarget->CreatePathBuilder(aFillRule);
+ }
+
+ virtual already_AddRefed<GradientStops> CreateGradientStops(
+ GradientStop* aStops, uint32_t aNumStops,
+ ExtendMode aExtendMode = ExtendMode::CLAMP) const override {
+ return mDrawTarget->CreateGradientStops(aStops, aNumStops, aExtendMode);
+ }
+ virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override {
+ return mDrawTarget->CreateFilter(aType);
+ }
+
+ private:
+ RefPtr<DrawTarget> mDrawTarget;
+ IntPoint mOrigin;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp
new file mode 100644
index 0000000000..d5ffede845
--- /dev/null
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -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 "DrawTargetRecording.h"
+#include "DrawTargetSkia.h"
+#include "PathRecording.h"
+#include <stdio.h>
+
+#include "Logging.h"
+#include "Tools.h"
+#include "Filters.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/UniquePtr.h"
+#include "nsXULAppAPI.h" // for XRE_IsContentProcess()
+#include "RecordingTypes.h"
+#include "RecordedEventImpl.h"
+
+namespace mozilla {
+namespace gfx {
+
+struct RecordingSourceSurfaceUserData {
+ void* refPtr;
+ RefPtr<DrawEventRecorderPrivate> recorder;
+
+ // The optimized surface holds a reference to our surface, for GetDataSurface
+ // calls, so we must hold a weak reference to avoid circular dependency.
+ ThreadSafeWeakPtr<SourceSurface> optimizedSurface;
+};
+
+static void RecordingSourceSurfaceUserDataFunc(void* aUserData) {
+ RecordingSourceSurfaceUserData* userData =
+ static_cast<RecordingSourceSurfaceUserData*>(aUserData);
+
+ if (NS_IsMainThread()) {
+ userData->recorder->RecordSourceSurfaceDestruction(userData->refPtr);
+ delete userData;
+ return;
+ }
+
+ userData->recorder->AddPendingDeletion([userData]() -> void {
+ userData->recorder->RecordSourceSurfaceDestruction(userData->refPtr);
+ delete userData;
+ });
+}
+
+static bool EnsureSurfaceStoredRecording(DrawEventRecorderPrivate* aRecorder,
+ SourceSurface* aSurface,
+ const char* reason) {
+ // It's important that TryAddStoredObject is called first because that will
+ // run any pending processing required by recorded objects that have been
+ // deleted off the main thread.
+ if (!aRecorder->TryAddStoredObject(aSurface)) {
+ // Surface is already stored.
+ return false;
+ }
+ aRecorder->StoreSourceSurfaceRecording(aSurface, reason);
+ aRecorder->AddSourceSurface(aSurface);
+
+ RecordingSourceSurfaceUserData* userData = new RecordingSourceSurfaceUserData;
+ userData->refPtr = aSurface;
+ userData->recorder = aRecorder;
+ aSurface->AddUserData(reinterpret_cast<UserDataKey*>(aRecorder), userData,
+ &RecordingSourceSurfaceUserDataFunc);
+ return true;
+}
+
+class SourceSurfaceRecording : public SourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceRecording, override)
+
+ SourceSurfaceRecording(IntSize aSize, SurfaceFormat aFormat,
+ DrawEventRecorderPrivate* aRecorder,
+ SourceSurface* aOriginalSurface = nullptr)
+ : mSize(aSize),
+ mFormat(aFormat),
+ mRecorder(aRecorder),
+ mOriginalSurface(aOriginalSurface) {
+ mRecorder->AddStoredObject(this);
+ }
+
+ ~SourceSurfaceRecording() {
+ mRecorder->RemoveStoredObject(this);
+ mRecorder->RecordEvent(
+ RecordedSourceSurfaceDestruction(ReferencePtr(this)));
+ }
+
+ SurfaceType GetType() const override { return SurfaceType::RECORDING; }
+ IntSize GetSize() const override { return mSize; }
+ SurfaceFormat GetFormat() const override { return mFormat; }
+ already_AddRefed<DataSourceSurface> GetDataSurface() override {
+ if (mOriginalSurface) {
+ return mOriginalSurface->GetDataSurface();
+ }
+
+ return nullptr;
+ }
+
+ IntSize mSize;
+ SurfaceFormat mFormat;
+ RefPtr<DrawEventRecorderPrivate> mRecorder;
+ // If a SourceSurfaceRecording is returned from an OptimizeSourceSurface call
+ // we need GetDataSurface to work, so we hold the original surface we
+ // optimized to return its GetDataSurface.
+ RefPtr<SourceSurface> mOriginalSurface;
+};
+
+class GradientStopsRecording : public GradientStops {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsRecording, override)
+
+ explicit GradientStopsRecording(DrawEventRecorderPrivate* aRecorder)
+ : mRecorder(aRecorder) {
+ mRecorder->AddStoredObject(this);
+ }
+
+ virtual ~GradientStopsRecording() {
+ mRecorder->RemoveStoredObject(this);
+ mRecorder->RecordEvent(
+ RecordedGradientStopsDestruction(ReferencePtr(this)));
+ }
+
+ BackendType GetBackendType() const override { return BackendType::RECORDING; }
+
+ RefPtr<DrawEventRecorderPrivate> mRecorder;
+};
+
+class FilterNodeRecording : public FilterNode {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeRecording, override)
+ using FilterNode::SetAttribute;
+
+ explicit FilterNodeRecording(DrawEventRecorderPrivate* aRecorder)
+ : mRecorder(aRecorder) {
+ mRecorder->AddStoredObject(this);
+ }
+
+ virtual ~FilterNodeRecording() {
+ mRecorder->RemoveStoredObject(this);
+ mRecorder->RecordEvent(RecordedFilterNodeDestruction(ReferencePtr(this)));
+ }
+
+ void SetInput(uint32_t aIndex, SourceSurface* aSurface) override {
+ EnsureSurfaceStoredRecording(mRecorder, aSurface, "SetInput");
+
+ mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aSurface));
+ }
+ void SetInput(uint32_t aIndex, FilterNode* aFilter) override {
+ MOZ_ASSERT(mRecorder->HasStoredObject(aFilter));
+
+ mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aFilter));
+ }
+
+#define FORWARD_SET_ATTRIBUTE(type, argtype) \
+ void SetAttribute(uint32_t aIndex, type aValue) override { \
+ mRecorder->RecordEvent(RecordedFilterNodeSetAttribute( \
+ this, aIndex, aValue, \
+ RecordedFilterNodeSetAttribute::ARGTYPE_##argtype)); \
+ }
+
+ 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 DeviceColor&, COLOR);
+
+#undef FORWARD_SET_ATTRIBUTE
+
+ void SetAttribute(uint32_t aIndex, const Float* aFloat,
+ uint32_t aSize) override {
+ mRecorder->RecordEvent(
+ RecordedFilterNodeSetAttribute(this, aIndex, aFloat, aSize));
+ }
+
+ FilterBackend GetBackendType() override { return FILTER_BACKEND_RECORDING; }
+
+ RefPtr<DrawEventRecorderPrivate> mRecorder;
+};
+
+DrawTargetRecording::DrawTargetRecording(DrawEventRecorder* aRecorder,
+ DrawTarget* aDT, IntRect aRect,
+ bool aHasData)
+ : mRecorder(static_cast<DrawEventRecorderPrivate*>(aRecorder)),
+ mFinalDT(aDT),
+ mRect(aRect) {
+ RefPtr<SourceSurface> snapshot = aHasData ? mFinalDT->Snapshot() : nullptr;
+ mRecorder->RecordEvent(
+ RecordedDrawTargetCreation(this, mFinalDT->GetBackendType(), mRect,
+ mFinalDT->GetFormat(), aHasData, snapshot));
+ mFormat = mFinalDT->GetFormat();
+}
+
+DrawTargetRecording::DrawTargetRecording(const DrawTargetRecording* aDT,
+ IntRect aRect, SurfaceFormat aFormat)
+ : mRecorder(aDT->mRecorder), mFinalDT(aDT->mFinalDT), mRect(aRect) {
+ mFormat = aFormat;
+}
+
+DrawTargetRecording::~DrawTargetRecording() {
+ mRecorder->RecordEvent(RecordedDrawTargetDestruction(ReferencePtr(this)));
+}
+
+void DrawTargetRecording::Link(const char* aDestination, const Rect& aRect) {
+ mRecorder->RecordEvent(RecordedLink(this, aDestination, aRect));
+}
+
+void DrawTargetRecording::Destination(const char* aDestination,
+ const Point& aPoint) {
+ mRecorder->RecordEvent(RecordedDestination(this, aDestination, aPoint));
+}
+
+void DrawTargetRecording::FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ EnsurePatternDependenciesStored(aPattern);
+
+ mRecorder->RecordEvent(RecordedFillRect(this, aRect, 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));
+}
+
+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));
+}
+
+void DrawTargetRecording::Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (!aPath) {
+ return;
+ }
+
+ RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath);
+ EnsurePatternDependenciesStored(aPattern);
+
+ mRecorder->RecordEvent(RecordedFill(this, pathRecording, aPattern, aOptions));
+}
+
+struct RecordingFontUserData {
+ void* refPtr;
+ void* unscaledFont;
+ RefPtr<DrawEventRecorderPrivate> recorder;
+};
+
+static void RecordingFontUserDataDestroyFunc(void* aUserData) {
+ RecordingFontUserData* userData =
+ static_cast<RecordingFontUserData*>(aUserData);
+
+ userData->recorder->RecordEvent(
+ RecordedScaledFontDestruction(ReferencePtr(userData->refPtr)));
+ userData->recorder->RemoveScaledFont((ScaledFont*)userData->refPtr);
+ userData->recorder->DecrementUnscaledFontRefCount(userData->unscaledFont);
+ delete userData;
+}
+
+void DrawTargetRecording::FillGlyphs(ScaledFont* aFont,
+ const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (!aFont) {
+ return;
+ }
+
+ EnsurePatternDependenciesStored(aPattern);
+
+ UserDataKey* userDataKey = reinterpret_cast<UserDataKey*>(mRecorder.get());
+ if (mRecorder->WantsExternalFonts()) {
+ mRecorder->AddScaledFont(aFont);
+ } else if (!aFont->GetUserData(userDataKey)) {
+ UnscaledFont* unscaledFont = aFont->GetUnscaledFont();
+ if (mRecorder->IncrementUnscaledFontRefCount(unscaledFont) == 0) {
+ // Prefer sending the description, if we can create one. This ensures
+ // we don't record the data of system fonts which saves time and can
+ // prevent duplicate copies from accumulating in the OS cache during
+ // playback.
+ RecordedFontDescriptor fontDesc(unscaledFont);
+ if (fontDesc.IsValid()) {
+ mRecorder->RecordEvent(fontDesc);
+ } else {
+ RecordedFontData fontData(unscaledFont);
+ 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(
+ RecordedUnscaledFontCreation(unscaledFont, fontDetails));
+ } else {
+ gfxWarning() << "DrawTargetRecording::FillGlyphs failed to serialise "
+ "UnscaledFont";
+ }
+ }
+ }
+ mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, unscaledFont));
+ RecordingFontUserData* userData = new RecordingFontUserData;
+ userData->refPtr = aFont;
+ userData->unscaledFont = unscaledFont;
+ userData->recorder = mRecorder;
+ aFont->AddUserData(userDataKey, userData,
+ &RecordingFontUserDataDestroyFunc);
+ userData->recorder->AddScaledFont(aFont);
+ }
+
+ mRecorder->RecordEvent(RecordedFillGlyphs(
+ this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs));
+}
+
+void DrawTargetRecording::Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions) {
+ EnsurePatternDependenciesStored(aSource);
+ EnsurePatternDependenciesStored(aMask);
+
+ mRecorder->RecordEvent(RecordedMask(this, aSource, aMask, aOptions));
+}
+
+void DrawTargetRecording::MaskSurface(const Pattern& aSource,
+ SourceSurface* aMask, Point aOffset,
+ const DrawOptions& aOptions) {
+ if (!aMask) {
+ return;
+ }
+
+ EnsurePatternDependenciesStored(aSource);
+ EnsureSurfaceStoredRecording(mRecorder, aMask, "MaskSurface");
+
+ mRecorder->RecordEvent(
+ RecordedMaskSurface(this, aSource, 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));
+}
+
+already_AddRefed<SourceSurface> DrawTargetRecording::Snapshot() {
+ RefPtr<SourceSurface> retSurf =
+ new SourceSurfaceRecording(mRect.Size(), mFormat, mRecorder);
+
+ mRecorder->RecordEvent(RecordedSnapshot(retSurf, this));
+
+ return retSurf.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetRecording::IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) {
+ RefPtr<SourceSurface> retSurf =
+ new SourceSurfaceRecording(mRect.Size(), SurfaceFormat::A8, mRecorder);
+
+ mRecorder->RecordEvent(
+ RecordedIntoLuminanceSource(retSurf, this, aLuminanceType, aOpacity));
+
+ return retSurf.forget();
+}
+
+void DrawTargetRecording::Flush() {
+ mRecorder->RecordEvent(RecordedFlush(this));
+}
+
+void DrawTargetRecording::DetachAllSnapshots() {
+ mRecorder->RecordEvent(RecordedDetachAllSnapshots(this));
+}
+
+void DrawTargetRecording::DrawSurface(SourceSurface* aSurface,
+ const Rect& aDest, const Rect& aSource,
+ const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) {
+ if (!aSurface) {
+ return;
+ }
+
+ EnsureSurfaceStoredRecording(mRecorder, aSurface, "DrawSurface");
+
+ mRecorder->RecordEvent(RecordedDrawSurface(this, aSurface, aDest, aSource,
+ aSurfOptions, aOptions));
+}
+
+void DrawTargetRecording::DrawDependentSurface(uint64_t aId,
+ const Rect& aDest) {
+ mRecorder->AddDependentSurface(aId);
+ mRecorder->RecordEvent(RecordedDrawDependentSurface(this, aId, aDest));
+}
+
+void DrawTargetRecording::DrawSurfaceWithShadow(SourceSurface* aSurface,
+ const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOp) {
+ if (!aSurface) {
+ return;
+ }
+
+ EnsureSurfaceStoredRecording(mRecorder, aSurface, "DrawSurfaceWithShadow");
+
+ mRecorder->RecordEvent(
+ RecordedDrawSurfaceWithShadow(this, aSurface, aDest, aShadow, aOp));
+}
+
+void DrawTargetRecording::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ if (!aNode) {
+ return;
+ }
+
+ MOZ_ASSERT(mRecorder->HasStoredObject(aNode));
+
+ mRecorder->RecordEvent(
+ RecordedDrawFilter(this, aNode, aSourceRect, aDestPoint, aOptions));
+}
+
+already_AddRefed<FilterNode> DrawTargetRecording::CreateFilter(
+ FilterType aType) {
+ RefPtr<FilterNode> retNode = new FilterNodeRecording(mRecorder);
+
+ mRecorder->RecordEvent(RecordedFilterNodeCreation(this, retNode, aType));
+
+ return retNode.forget();
+}
+
+void DrawTargetRecording::ClearRect(const Rect& aRect) {
+ mRecorder->RecordEvent(RecordedClearRect(this, aRect));
+}
+
+void DrawTargetRecording::CopySurface(SourceSurface* aSurface,
+ const IntRect& aSourceRect,
+ const IntPoint& aDestination) {
+ if (!aSurface) {
+ return;
+ }
+
+ EnsureSurfaceStoredRecording(mRecorder, aSurface, "CopySurface");
+
+ mRecorder->RecordEvent(
+ RecordedCopySurface(this, aSurface, aSourceRect, aDestination));
+}
+
+void DrawTargetRecording::PushClip(const Path* aPath) {
+ if (!aPath) {
+ return;
+ }
+
+ // The canvas doesn't have a clipRect API so we always end up in the generic
+ // path. The D2D backend doesn't have a good way of specializing rectangular
+ // clips so we take advantage of the fact that aPath is usually backed by a
+ // SkiaPath which implements AsRect() and specialize it here.
+ auto rect = aPath->AsRect();
+ if (rect.isSome()) {
+ PushClipRect(rect.value());
+ return;
+ }
+
+ RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath);
+
+ mRecorder->RecordEvent(RecordedPushClip(this, pathRecording));
+}
+
+void DrawTargetRecording::PushClipRect(const Rect& aRect) {
+ mRecorder->RecordEvent(RecordedPushClipRect(this, aRect));
+}
+
+void DrawTargetRecording::PopClip() {
+ mRecorder->RecordEvent(RecordedPopClip(static_cast<DrawTarget*>(this)));
+}
+
+void DrawTargetRecording::PushLayer(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds,
+ bool aCopyBackground) {
+ if (aMask) {
+ EnsureSurfaceStoredRecording(mRecorder, aMask, "PushLayer");
+ }
+
+ mRecorder->RecordEvent(RecordedPushLayer(this, aOpaque, aOpacity, aMask,
+ aMaskTransform, aBounds,
+ aCopyBackground));
+}
+
+void DrawTargetRecording::PushLayerWithBlend(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds,
+ bool aCopyBackground,
+ CompositionOp aCompositionOp) {
+ if (aMask) {
+ EnsureSurfaceStoredRecording(mRecorder, aMask, "PushLayer");
+ }
+
+ mRecorder->RecordEvent(
+ RecordedPushLayerWithBlend(this, aOpaque, aOpacity, aMask, aMaskTransform,
+ aBounds, aCopyBackground, aCompositionOp));
+}
+
+void DrawTargetRecording::PopLayer() {
+ mRecorder->RecordEvent(RecordedPopLayer(static_cast<DrawTarget*>(this)));
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetRecording::CreateSourceSurfaceFromData(unsigned char* aData,
+ const IntSize& aSize,
+ int32_t aStride,
+ SurfaceFormat aFormat) const {
+ RefPtr<SourceSurface> surface = CreateDataSourceSurfaceWithStrideFromData(
+ aSize, aFormat, aStride, aData, aStride);
+ if (!surface) {
+ return nullptr;
+ }
+
+ return OptimizeSourceSurface(surface);
+}
+
+already_AddRefed<SourceSurface> DrawTargetRecording::OptimizeSourceSurface(
+ SourceSurface* aSurface) const {
+ // See if we have a previously optimized surface available. We have to do this
+ // check before the SurfaceType::RECORDING below, because aSurface might be a
+ // SurfaceType::RECORDING from another recorder we have previously optimized.
+ auto* userData = static_cast<RecordingSourceSurfaceUserData*>(
+ aSurface->GetUserData(reinterpret_cast<UserDataKey*>(mRecorder.get())));
+ if (userData) {
+ RefPtr<SourceSurface> strongRef(userData->optimizedSurface);
+ if (strongRef) {
+ return do_AddRef(strongRef);
+ }
+ } else {
+ if (!EnsureSurfaceStoredRecording(mRecorder, aSurface,
+ "OptimizeSourceSurface")) {
+ // Surface was already stored, but doesn't have UserData so must be one
+ // of our recording surfaces.
+ MOZ_ASSERT(aSurface->GetType() == SurfaceType::RECORDING);
+ return do_AddRef(aSurface);
+ }
+
+ userData = static_cast<RecordingSourceSurfaceUserData*>(
+ aSurface->GetUserData(reinterpret_cast<UserDataKey*>(mRecorder.get())));
+ MOZ_ASSERT(userData,
+ "User data should always have been set by "
+ "EnsureSurfaceStoredRecording.");
+ }
+
+ RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(
+ aSurface->GetSize(), aSurface->GetFormat(), mRecorder, aSurface);
+ mRecorder->RecordEvent(
+ RecordedOptimizeSourceSurface(aSurface, this, retSurf));
+ userData->optimizedSurface = retSurf;
+
+ return retSurf.forget();
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetRecording::CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const {
+ MOZ_ASSERT(false);
+ return nullptr;
+}
+
+already_AddRefed<DrawTarget>
+DrawTargetRecording::CreateSimilarDrawTargetWithBacking(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ RefPtr<DrawTarget> similarDT;
+ if (mFinalDT->CanCreateSimilarDrawTarget(aSize, aFormat)) {
+ // If the requested similar draw target is too big, then we should try to
+ // rasterize on the content side to avoid duplicating the effort when a
+ // blob image gets tiled. If we fail somehow to produce it, we can fall
+ // back to recording.
+ constexpr int32_t kRasterThreshold = 256 * 256 * 4;
+ int32_t stride = aSize.width * BytesPerPixel(aFormat);
+ int32_t surfaceBytes = aSize.height * stride;
+ if (surfaceBytes >= kRasterThreshold) {
+ auto surface = MakeRefPtr<SourceSurfaceSharedData>();
+ if (surface->Init(aSize, stride, aFormat)) {
+ auto dt = MakeRefPtr<DrawTargetSkia>();
+ if (dt->Init(std::move(surface))) {
+ return dt.forget();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Skia should initialize given surface!");
+ }
+ }
+ }
+ }
+
+ return CreateSimilarDrawTarget(aSize, aFormat);
+}
+
+already_AddRefed<DrawTarget> DrawTargetRecording::CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ RefPtr<DrawTarget> similarDT;
+ if (mFinalDT->CanCreateSimilarDrawTarget(aSize, aFormat)) {
+ similarDT =
+ new DrawTargetRecording(this, IntRect(IntPoint(0, 0), aSize), aFormat);
+ mRecorder->RecordEvent(
+ RecordedCreateSimilarDrawTarget(this, similarDT.get(), aSize, aFormat));
+ } else if (XRE_IsContentProcess()) {
+ // Crash any content process that calls this function with arguments that
+ // would fail to create a similar draw target. We do this to root out bad
+ // callers. We don't want to crash any important processes though so for
+ // for those we'll just gracefully return nullptr.
+ MOZ_CRASH(
+ "Content-process DrawTargetRecording can't create requested similar "
+ "drawtarget");
+ }
+ return similarDT.forget();
+}
+
+bool DrawTargetRecording::CanCreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ return mFinalDT->CanCreateSimilarDrawTarget(aSize, aFormat);
+}
+
+RefPtr<DrawTarget> DrawTargetRecording::CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) {
+ RefPtr<DrawTarget> similarDT;
+ similarDT = new DrawTargetRecording(this, mRect, aFormat);
+ mRecorder->RecordEvent(
+ RecordedCreateClippedDrawTarget(this, similarDT.get(), aBounds, aFormat));
+ similarDT->SetTransform(mTransform);
+ return similarDT;
+}
+
+already_AddRefed<DrawTarget>
+DrawTargetRecording::CreateSimilarDrawTargetForFilter(
+ const IntSize& aMaxSize, SurfaceFormat aFormat, FilterNode* aFilter,
+ FilterNode* aSource, const Rect& aSourceRect, const Point& aDestPoint) {
+ RefPtr<DrawTarget> similarDT;
+ if (mFinalDT->CanCreateSimilarDrawTarget(aMaxSize, aFormat)) {
+ similarDT = new DrawTargetRecording(this, IntRect(IntPoint(0, 0), aMaxSize),
+ aFormat);
+ mRecorder->RecordEvent(RecordedCreateDrawTargetForFilter(
+ this, similarDT.get(), aMaxSize, aFormat, aFilter, aSource, aSourceRect,
+ aDestPoint));
+ } else if (XRE_IsContentProcess()) {
+ // See CreateSimilarDrawTarget
+ MOZ_CRASH(
+ "Content-process DrawTargetRecording can't create requested clipped "
+ "drawtarget");
+ }
+ return similarDT.forget();
+}
+
+already_AddRefed<PathBuilder> DrawTargetRecording::CreatePathBuilder(
+ FillRule aFillRule) const {
+ return MakeAndAddRef<PathBuilderRecording>(mFinalDT->GetBackendType(),
+ aFillRule);
+}
+
+already_AddRefed<GradientStops> DrawTargetRecording::CreateGradientStops(
+ GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
+ RefPtr<GradientStops> retStops = new GradientStopsRecording(mRecorder);
+
+ mRecorder->RecordEvent(RecordedGradientStopsCreation(this, retStops, aStops,
+ aNumStops, aExtendMode));
+
+ return retStops.forget();
+}
+
+void DrawTargetRecording::SetTransform(const Matrix& aTransform) {
+ mRecorder->RecordEvent(RecordedSetTransform(this, aTransform));
+ DrawTarget::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->TryAddStoredObject(pathRecording)) {
+ // Path is already stored.
+ return pathRecording.forget();
+ }
+ } else {
+ MOZ_ASSERT(!mRecorder->HasStoredObject(aPath));
+ FillRule fillRule = aPath->GetFillRule();
+ RefPtr<PathBuilderRecording> builderRecording =
+ new PathBuilderRecording(mFinalDT->GetBackendType(), fillRule);
+ aPath->StreamToSink(builderRecording);
+ pathRecording = builderRecording->Finish().downcast<PathRecording>();
+ mRecorder->AddStoredObject(pathRecording);
+ }
+
+ // It's important that AddStoredObject or TryAddStoredObject is called before
+ // this because that will run any pending processing required by recorded
+ // objects that have been deleted off the main thread.
+ mRecorder->RecordEvent(RecordedPathCreation(this, pathRecording.get()));
+ pathRecording->mStoredRecorders.push_back(mRecorder);
+
+ return pathRecording.forget();
+}
+
+// This should only be called on the 'root' DrawTargetRecording.
+// Calling it on a child DrawTargetRecordings will cause confusion.
+void DrawTargetRecording::FlushItem(const IntRect& aBounds) {
+ mRecorder->FlushItem(aBounds);
+ // Reinitialize the recorder (FlushItem will write a new recording header)
+ // Tell the new recording about our draw target
+ // This code should match what happens in the DrawTargetRecording constructor.
+ mRecorder->RecordEvent(
+ RecordedDrawTargetCreation(this, mFinalDT->GetBackendType(), mRect,
+ mFinalDT->GetFormat(), false, nullptr));
+ // Add the current transform to the new recording
+ mRecorder->RecordEvent(
+ RecordedSetTransform(this, DrawTarget::GetTransform()));
+}
+
+void DrawTargetRecording::EnsurePatternDependenciesStored(
+ const Pattern& aPattern) {
+ switch (aPattern.GetType()) {
+ case PatternType::COLOR:
+ // No dependencies here.
+ return;
+ case PatternType::LINEAR_GRADIENT: {
+ MOZ_ASSERT_IF(
+ static_cast<const LinearGradientPattern*>(&aPattern)->mStops,
+ mRecorder->HasStoredObject(
+ static_cast<const LinearGradientPattern*>(&aPattern)->mStops));
+ return;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ MOZ_ASSERT_IF(
+ static_cast<const RadialGradientPattern*>(&aPattern)->mStops,
+ mRecorder->HasStoredObject(
+ static_cast<const RadialGradientPattern*>(&aPattern)->mStops));
+ return;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ MOZ_ASSERT_IF(
+ static_cast<const ConicGradientPattern*>(&aPattern)->mStops,
+ mRecorder->HasStoredObject(
+ static_cast<const ConicGradientPattern*>(&aPattern)->mStops));
+ return;
+ }
+ case PatternType::SURFACE: {
+ const SurfacePattern* pat = static_cast<const SurfacePattern*>(&aPattern);
+ EnsureSurfaceStoredRecording(mRecorder, pat->mSurface,
+ "EnsurePatternDependenciesStored");
+ return;
+ }
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/DrawTargetRecording.h b/gfx/2d/DrawTargetRecording.h
new file mode 100644
index 0000000000..e2c2c9412b
--- /dev/null
+++ b/gfx/2d/DrawTargetRecording.h
@@ -0,0 +1,367 @@
+/* -*- 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_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,
+ IntRect aRect, bool aHasData = false);
+
+ ~DrawTargetRecording();
+
+ virtual DrawTargetType GetType() const override {
+ return mFinalDT->GetType();
+ }
+ virtual BackendType GetBackendType() const override {
+ return BackendType::RECORDING;
+ }
+ virtual bool IsRecording() const override { return true; }
+
+ virtual void Link(const char* aDestination, const Rect& aRect) override;
+ virtual void Destination(const char* aDestination,
+ const Point& aPoint) override;
+
+ virtual already_AddRefed<SourceSurface> Snapshot() override;
+ virtual already_AddRefed<SourceSurface> IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) override;
+
+ virtual void DetachAllSnapshots() override;
+
+ virtual IntSize GetSize() const override { return mRect.Size(); }
+ virtual IntRect GetRect() const override { return mRect; }
+
+ virtual void Flush() override;
+
+ virtual void FlushItem(const IntRect& aBounds) override;
+
+ /*
+ * 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 DrawDependentSurface(uint64_t aId, const Rect& aDest) 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 ShadowOptions& aShadow,
+ 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()) 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;
+
+ /**
+ * 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.a
+ * @param aCompositionOp The CompositionOp to use when blending the layer into
+ * the destination
+ */
+ virtual void PushLayerWithBlend(
+ bool aOpaque, Float aOpacity, SourceSurface* aMask,
+ const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(),
+ bool aCopyBackground = false,
+ CompositionOp aCompositionOp = CompositionOp::OP_OVER) 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 DrawTarget whose backing surface is optimized for use with this
+ * DrawTarget.
+ */
+ virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetWithBacking(
+ const IntSize& aSize, SurfaceFormat aFormat) const override;
+
+ bool CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const override;
+ /**
+ * Create a similar DrawTarget whose requested size may be clipped based
+ * on this DrawTarget's rect transformed to the new target's space.
+ */
+ virtual RefPtr<DrawTarget> CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) override;
+
+ virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetForFilter(
+ const IntSize& aSize, SurfaceFormat aFormat, FilterNode* aFilter,
+ FilterNode* aSource, const Rect& aSourceRect,
+ const Point& aDestPoint) 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.
+ *
+ * @param aDT DrawTargetRecording on which CreateSimilarDrawTarget was called
+ * @param aSize size of the the similar DrawTarget
+ * @param aFormat format of the similar DrawTarget
+ */
+ DrawTargetRecording(const DrawTargetRecording* aDT, IntRect aRect,
+ SurfaceFormat aFormat);
+
+ Path* GetPathForPathRecording(const Path* aPath) const;
+ already_AddRefed<PathRecording> EnsurePathStored(const Path* aPath);
+ void EnsurePatternDependenciesStored(const Pattern& aPattern);
+
+ RefPtr<DrawEventRecorderPrivate> mRecorder;
+ RefPtr<DrawTarget> mFinalDT;
+ IntRect mRect;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_DRAWTARGETRECORDING_H_ */
diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp
new file mode 100644
index 0000000000..0ddf9fef52
--- /dev/null
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -0,0 +1,2070 @@
+/* -*- 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 "DrawTargetSkia.h"
+#include "SourceSurfaceSkia.h"
+#include "ScaledFontBase.h"
+#include "FilterNodeSoftware.h"
+#include "HelpersSkia.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Vector.h"
+
+#include "skia/include/core/SkBitmap.h"
+#include "skia/include/core/SkCanvas.h"
+#include "skia/include/core/SkFont.h"
+#include "skia/include/core/SkSurface.h"
+#include "skia/include/core/SkTextBlob.h"
+#include "skia/include/core/SkTypeface.h"
+#include "skia/include/effects/SkGradientShader.h"
+#include "skia/include/core/SkColorFilter.h"
+#include "skia/include/core/SkRegion.h"
+#include "skia/include/effects/SkImageFilters.h"
+#include "skia/include/private/base/SkMalloc.h"
+#include "Blur.h"
+#include "Logging.h"
+#include "Tools.h"
+#include "PathHelpers.h"
+#include "PathSkia.h"
+#include "Swizzle.h"
+#include <algorithm>
+#include <cmath>
+
+#ifdef MOZ_WIDGET_COCOA
+# include "BorrowedContext.h"
+# include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef XP_WIN
+# include "ScaledFontDWrite.h"
+#endif
+
+namespace mozilla {
+
+void RefPtrTraits<SkSurface>::Release(SkSurface* aSurface) {
+ SkSafeUnref(aSurface);
+}
+
+void RefPtrTraits<SkSurface>::AddRef(SkSurface* aSurface) {
+ SkSafeRef(aSurface);
+}
+
+} // namespace mozilla
+
+namespace mozilla::gfx {
+
+class GradientStopsSkia : public GradientStops {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsSkia, override)
+
+ GradientStopsSkia(const std::vector<GradientStop>& aStops, uint32_t aNumStops,
+ ExtendMode aExtendMode)
+ : mCount(aNumStops), mExtendMode(aExtendMode) {
+ if (mCount == 0) {
+ return;
+ }
+
+ // Skia gradients always require a stop at 0.0 and 1.0, insert these if
+ // we don't have them.
+ uint32_t shift = 0;
+ if (aStops[0].offset != 0) {
+ mCount++;
+ shift = 1;
+ }
+ if (aStops[aNumStops - 1].offset != 1) {
+ mCount++;
+ }
+ mColors.resize(mCount);
+ mPositions.resize(mCount);
+ if (aStops[0].offset != 0) {
+ mColors[0] = ColorToSkColor(aStops[0].color, 1.0);
+ mPositions[0] = 0;
+ }
+ for (uint32_t i = 0; i < aNumStops; i++) {
+ mColors[i + shift] = ColorToSkColor(aStops[i].color, 1.0);
+ mPositions[i + shift] = SkFloatToScalar(aStops[i].offset);
+ }
+ if (aStops[aNumStops - 1].offset != 1) {
+ mColors[mCount - 1] = ColorToSkColor(aStops[aNumStops - 1].color, 1.0);
+ mPositions[mCount - 1] = SK_Scalar1;
+ }
+ }
+
+ BackendType GetBackendType() const override { return BackendType::SKIA; }
+
+ std::vector<SkColor> mColors;
+ std::vector<SkScalar> mPositions;
+ int mCount;
+ ExtendMode mExtendMode;
+};
+
+/**
+ * When constructing a temporary SkImage via GetSkImageForSurface, we may also
+ * have to construct a temporary DataSourceSurface, which must live as long as
+ * the SkImage. We attach this temporary surface to the image's pixelref, so
+ * that it can be released once the pixelref is freed.
+ */
+static void ReleaseTemporarySurface(const void* aPixels, void* aContext) {
+ DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext);
+ if (surf) {
+ surf->Release();
+ }
+}
+
+static void ReleaseTemporaryMappedSurface(const void* aPixels, void* aContext) {
+ DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext);
+ if (surf) {
+ surf->Unmap();
+ surf->Release();
+ }
+}
+
+static void WriteRGBXFormat(uint8_t* aData, const IntSize& aSize,
+ const int32_t aStride, SurfaceFormat aFormat) {
+ if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+ return;
+ }
+
+ SwizzleData(aData, aStride, SurfaceFormat::X8R8G8B8_UINT32, aData, aStride,
+ SurfaceFormat::A8R8G8B8_UINT32, aSize);
+}
+
+#ifdef DEBUG
+static IntRect CalculateSurfaceBounds(const IntSize& aSize, const Rect* aBounds,
+ const Matrix* aMatrix) {
+ IntRect surfaceBounds(IntPoint(0, 0), aSize);
+ if (!aBounds) {
+ return surfaceBounds;
+ }
+
+ MOZ_ASSERT(aMatrix);
+ Matrix inverse(*aMatrix);
+ if (!inverse.Invert()) {
+ return surfaceBounds;
+ }
+
+ IntRect bounds;
+ Rect sampledBounds = inverse.TransformBounds(*aBounds);
+ if (!sampledBounds.ToIntRect(&bounds)) {
+ return surfaceBounds;
+ }
+
+ return surfaceBounds.Intersect(bounds);
+}
+
+static const int kARGBAlphaOffset =
+ SurfaceFormat::A8R8G8B8_UINT32 == SurfaceFormat::B8G8R8A8 ? 3 : 0;
+
+static bool VerifyRGBXFormat(uint8_t* aData, const IntSize& aSize,
+ const int32_t aStride, SurfaceFormat aFormat) {
+ if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+ return true;
+ }
+ // We should've initialized the data to be opaque already
+ // On debug builds, verify that this is actually true.
+ int height = aSize.height;
+ int width = aSize.width * 4;
+
+ for (int row = 0; row < height; ++row) {
+ for (int column = 0; column < width; column += 4) {
+ if (aData[column + kARGBAlphaOffset] != 0xFF) {
+ gfxCriticalError() << "RGBX pixel at (" << column << "," << row
+ << ") in " << width << "x" << height
+ << " surface is not opaque: " << int(aData[column])
+ << "," << int(aData[column + 1]) << ","
+ << int(aData[column + 2]) << ","
+ << int(aData[column + 3]);
+ }
+ }
+ aData += aStride;
+ }
+
+ return true;
+}
+
+// Since checking every pixel is expensive, this only checks the four corners
+// and center of a surface that their alpha value is 0xFF.
+static bool VerifyRGBXCorners(uint8_t* aData, const IntSize& aSize,
+ const int32_t aStride, SurfaceFormat aFormat,
+ const Rect* aBounds = nullptr,
+ const Matrix* aMatrix = nullptr) {
+ if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+ return true;
+ }
+
+ IntRect bounds = CalculateSurfaceBounds(aSize, aBounds, aMatrix);
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+
+ const int height = bounds.Height();
+ const int width = bounds.Width();
+ const int pixelSize = 4;
+ MOZ_ASSERT(aSize.width * pixelSize <= aStride);
+
+ const int translation = bounds.Y() * aStride + bounds.X() * pixelSize;
+ const int topLeft = translation;
+ const int topRight = topLeft + (width - 1) * pixelSize;
+ const int bottomLeft = translation + (height - 1) * aStride;
+ const int bottomRight = bottomLeft + (width - 1) * pixelSize;
+
+ // Lastly the center pixel
+ const int middleRowHeight = height / 2;
+ const int middleRowWidth = (width / 2) * pixelSize;
+ const int middle = translation + aStride * middleRowHeight + middleRowWidth;
+
+ const int offsets[] = {topLeft, topRight, bottomRight, bottomLeft, middle};
+ for (int offset : offsets) {
+ if (aData[offset + kARGBAlphaOffset] != 0xFF) {
+ int row = offset / aStride;
+ int column = (offset % aStride) / pixelSize;
+ gfxCriticalError() << "RGBX corner pixel at (" << column << "," << row
+ << ") in " << aSize.width << "x" << aSize.height
+ << " surface, bounded by "
+ << "(" << bounds.X() << "," << bounds.Y() << ","
+ << width << "," << height
+ << ") is not opaque: " << int(aData[offset]) << ","
+ << int(aData[offset + 1]) << ","
+ << int(aData[offset + 2]) << ","
+ << int(aData[offset + 3]);
+ }
+ }
+
+ return true;
+}
+#endif
+
+static sk_sp<SkImage> GetSkImageForSurface(SourceSurface* aSurface,
+ Maybe<MutexAutoLock>* aLock,
+ const Rect* aBounds = nullptr,
+ const Matrix* aMatrix = nullptr) {
+ if (!aSurface) {
+ gfxDebug() << "Creating null Skia image from null SourceSurface";
+ return nullptr;
+ }
+
+ if (aSurface->GetType() == SurfaceType::SKIA) {
+ return static_cast<SourceSurfaceSkia*>(aSurface)->GetImage(aLock);
+ }
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ if (!dataSurface) {
+ gfxWarning() << "Failed getting DataSourceSurface for Skia image";
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ SkImage::RasterReleaseProc releaseProc;
+ if (dataSurface->GetType() == SurfaceType::DATA_SHARED_WRAPPER) {
+ // Technically all surfaces should be mapped and unmapped explicitly but it
+ // appears SourceSurfaceSkia and DataSourceSurfaceWrapper have issues with
+ // this. For now, we just map SourceSurfaceSharedDataWrapper to ensure we
+ // don't unmap the data during the transaction (for blob images).
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ gfxWarning() << "Failed mapping DataSourceSurface for Skia image";
+ return nullptr;
+ }
+ releaseProc = ReleaseTemporaryMappedSurface;
+ } else {
+ map.mData = dataSurface->GetData();
+ map.mStride = dataSurface->Stride();
+ releaseProc = ReleaseTemporarySurface;
+ }
+
+ DataSourceSurface* surf = dataSurface.forget().take();
+
+ // Skia doesn't support RGBX surfaces so ensure that the alpha value is opaque
+ // white.
+ MOZ_ASSERT(VerifyRGBXCorners(map.mData, surf->GetSize(), map.mStride,
+ surf->GetFormat(), aBounds, aMatrix));
+
+ SkPixmap pixmap(MakeSkiaImageInfo(surf->GetSize(), surf->GetFormat()),
+ map.mData, map.mStride);
+ sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, releaseProc, surf);
+ if (!image) {
+ releaseProc(map.mData, surf);
+ gfxDebug() << "Failed making Skia raster image for temporary surface";
+ }
+
+ return image;
+}
+
+DrawTargetSkia::DrawTargetSkia()
+ : mCanvas(nullptr),
+ mSnapshot(nullptr),
+ mSnapshotLock{"DrawTargetSkia::mSnapshotLock"}
+#ifdef MOZ_WIDGET_COCOA
+ ,
+ mCG(nullptr),
+ mColorSpace(nullptr),
+ mCanvasData(nullptr),
+ mCGSize(0, 0),
+ mNeedLayer(false)
+#endif
+{
+}
+
+DrawTargetSkia::~DrawTargetSkia() {
+ if (mSnapshot) {
+ MutexAutoLock lock(mSnapshotLock);
+ // We're going to go away, hand our SkSurface to the SourceSurface.
+ mSnapshot->GiveSurface(mSurface.forget().take());
+ }
+
+#ifdef MOZ_WIDGET_COCOA
+ if (mCG) {
+ CGContextRelease(mCG);
+ mCG = nullptr;
+ }
+
+ if (mColorSpace) {
+ CGColorSpaceRelease(mColorSpace);
+ mColorSpace = nullptr;
+ }
+#endif
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::Snapshot(
+ SurfaceFormat aFormat) {
+ // Without this lock, this could cause us to get out a snapshot and race with
+ // Snapshot::~Snapshot() actually destroying itself.
+ MutexAutoLock lock(mSnapshotLock);
+ if (mSnapshot && aFormat != mSnapshot->GetFormat()) {
+ if (!mSnapshot->hasOneRef()) {
+ mSnapshot->DrawTargetWillChange();
+ }
+ mSnapshot = nullptr;
+ }
+ RefPtr<SourceSurfaceSkia> snapshot = mSnapshot;
+ if (mSurface && !snapshot) {
+ snapshot = new SourceSurfaceSkia();
+ sk_sp<SkImage> image;
+ // If the surface is raster, making a snapshot may trigger a pixel copy.
+ // Instead, try to directly make a raster image referencing the surface
+ // pixels.
+ SkPixmap pixmap;
+ if (mSurface->peekPixels(&pixmap)) {
+ image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
+ } else {
+ image = mSurface->makeImageSnapshot();
+ }
+ if (!snapshot->InitFromImage(image, aFormat, this)) {
+ return nullptr;
+ }
+ mSnapshot = snapshot;
+ }
+
+ return snapshot.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::GetBackingSurface() {
+ if (mBackingSurface) {
+ RefPtr<SourceSurface> snapshot = mBackingSurface;
+ return snapshot.forget();
+ }
+ return Snapshot();
+}
+
+bool DrawTargetSkia::LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride,
+ SurfaceFormat* aFormat, IntPoint* aOrigin) {
+ SkImageInfo info;
+ size_t rowBytes;
+ SkIPoint origin;
+ void* pixels = mCanvas->accessTopLayerPixels(&info, &rowBytes, &origin);
+ if (!pixels ||
+ // Ensure the layer is at the origin if required.
+ (!aOrigin && !origin.isZero())) {
+ return false;
+ }
+
+ MarkChanged();
+
+ *aData = reinterpret_cast<uint8_t*>(pixels);
+ *aSize = IntSize(info.width(), info.height());
+ *aStride = int32_t(rowBytes);
+ *aFormat = SkiaColorTypeToGfxFormat(info.colorType(), info.alphaType());
+ if (aOrigin) {
+ *aOrigin = IntPoint(origin.x(), origin.y());
+ }
+ return true;
+}
+
+void DrawTargetSkia::ReleaseBits(uint8_t* aData) {}
+
+static void ReleaseImage(const void* aPixels, void* aContext) {
+ SkImage* image = static_cast<SkImage*>(aContext);
+ SkSafeUnref(image);
+}
+
+static sk_sp<SkImage> ExtractSubset(sk_sp<SkImage> aImage,
+ const IntRect& aRect) {
+ SkIRect subsetRect = IntRectToSkIRect(aRect);
+ if (aImage->bounds() == subsetRect) {
+ return aImage;
+ }
+ // makeSubset is slow, so prefer to use SkPixmap::extractSubset where
+ // possible.
+ SkPixmap pixmap, subsetPixmap;
+ if (aImage->peekPixels(&pixmap) &&
+ pixmap.extractSubset(&subsetPixmap, subsetRect)) {
+ // Release the original image reference so only the subset image keeps it
+ // alive.
+ return SkImage::MakeFromRaster(subsetPixmap, ReleaseImage,
+ aImage.release());
+ }
+ return aImage->makeSubset(subsetRect);
+}
+
+static void FreeAlphaPixels(void* aBuf, void*) { sk_free(aBuf); }
+
+static bool ExtractAlphaBitmap(const sk_sp<SkImage>& aImage,
+ SkBitmap* aResultBitmap,
+ bool aAllowReuse = false) {
+ SkPixmap pixmap;
+ if (aAllowReuse && aImage->isAlphaOnly() && aImage->peekPixels(&pixmap)) {
+ SkBitmap bitmap;
+ bitmap.installPixels(pixmap.info(), pixmap.writable_addr(),
+ pixmap.rowBytes());
+ *aResultBitmap = bitmap;
+ return true;
+ }
+ SkImageInfo info = SkImageInfo::MakeA8(aImage->width(), aImage->height());
+ // Skia does not fully allocate the last row according to stride.
+ // Since some of our algorithms (i.e. blur) depend on this, we must allocate
+ // the bitmap pixels manually.
+ size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel());
+ if (stride) {
+ CheckedInt<size_t> size = stride;
+ size *= info.height();
+ // We need to leave room for an additional 3 bytes for a potential overrun
+ // in our blurring code.
+ size += 3;
+ if (size.isValid()) {
+ void* buf = sk_malloc_flags(size.value(), 0);
+ if (buf) {
+ SkBitmap bitmap;
+ if (bitmap.installPixels(info, buf, stride, FreeAlphaPixels, nullptr) &&
+ aImage->readPixels(bitmap.info(), bitmap.getPixels(),
+ bitmap.rowBytes(), 0, 0)) {
+ *aResultBitmap = bitmap;
+ return true;
+ }
+ }
+ }
+ }
+
+ gfxWarning() << "Failed reading alpha pixels for Skia bitmap";
+ return false;
+}
+
+static void SetPaintPattern(SkPaint& aPaint, const Pattern& aPattern,
+ Maybe<MutexAutoLock>& aLock, Float aAlpha = 1.0,
+ const SkMatrix* aMatrix = nullptr,
+ const Rect* aBounds = nullptr) {
+ switch (aPattern.GetType()) {
+ case PatternType::COLOR: {
+ DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor;
+ aPaint.setColor(ColorToSkColor(color, aAlpha));
+ break;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ const LinearGradientPattern& pat =
+ static_cast<const LinearGradientPattern&>(aPattern);
+ GradientStopsSkia* stops =
+ pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA
+ ? static_cast<GradientStopsSkia*>(pat.mStops.get())
+ : nullptr;
+ if (!stops || stops->mCount < 2 || !pat.mBegin.IsFinite() ||
+ !pat.mEnd.IsFinite() || pat.mBegin == pat.mEnd) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ } else {
+ SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
+ SkPoint points[2];
+ points[0] = SkPoint::Make(SkFloatToScalar(pat.mBegin.x),
+ SkFloatToScalar(pat.mBegin.y));
+ points[1] = SkPoint::Make(SkFloatToScalar(pat.mEnd.x),
+ SkFloatToScalar(pat.mEnd.y));
+
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+ sk_sp<SkShader> shader = SkGradientShader::MakeLinear(
+ points, &stops->mColors.front(), &stops->mPositions.front(),
+ stops->mCount, mode, 0, &mat);
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ }
+ break;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ const RadialGradientPattern& pat =
+ static_cast<const RadialGradientPattern&>(aPattern);
+ GradientStopsSkia* stops =
+ pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA
+ ? static_cast<GradientStopsSkia*>(pat.mStops.get())
+ : nullptr;
+ if (!stops || stops->mCount < 2 || !pat.mCenter1.IsFinite() ||
+ !std::isfinite(pat.mRadius1) || !pat.mCenter2.IsFinite() ||
+ !std::isfinite(pat.mRadius2) ||
+ (pat.mCenter1 == pat.mCenter2 && pat.mRadius1 == pat.mRadius2)) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ } else {
+ SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
+ SkPoint points[2];
+ points[0] = SkPoint::Make(SkFloatToScalar(pat.mCenter1.x),
+ SkFloatToScalar(pat.mCenter1.y));
+ points[1] = SkPoint::Make(SkFloatToScalar(pat.mCenter2.x),
+ SkFloatToScalar(pat.mCenter2.y));
+
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+ sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical(
+ points[0], SkFloatToScalar(pat.mRadius1), points[1],
+ SkFloatToScalar(pat.mRadius2), &stops->mColors.front(),
+ &stops->mPositions.front(), stops->mCount, mode, 0, &mat);
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ }
+ break;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ const ConicGradientPattern& pat =
+ static_cast<const ConicGradientPattern&>(aPattern);
+ GradientStopsSkia* stops =
+ pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA
+ ? static_cast<GradientStopsSkia*>(pat.mStops.get())
+ : nullptr;
+ if (!stops || stops->mCount < 2 || !pat.mCenter.IsFinite() ||
+ !std::isfinite(pat.mAngle)) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ } else {
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+
+ SkScalar cx = SkFloatToScalar(pat.mCenter.x);
+ SkScalar cy = SkFloatToScalar(pat.mCenter.y);
+
+ // Skia's sweep gradient angles are relative to the x-axis, not the
+ // y-axis.
+ Float angle = (pat.mAngle * 180.0 / M_PI) - 90.0;
+ if (angle != 0.0) {
+ mat.preRotate(angle, cx, cy);
+ }
+
+ SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
+ sk_sp<SkShader> shader = SkGradientShader::MakeSweep(
+ cx, cy, &stops->mColors.front(), &stops->mPositions.front(),
+ stops->mCount, mode, 360 * pat.mStartOffset, 360 * pat.mEndOffset,
+ 0, &mat);
+
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ }
+ break;
+ }
+ case PatternType::SURFACE: {
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ sk_sp<SkImage> image =
+ GetSkImageForSurface(pat.mSurface, &aLock, aBounds, &pat.mMatrix);
+ if (!image) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ break;
+ }
+
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+
+ if (!pat.mSamplingRect.IsEmpty()) {
+ image = ExtractSubset(image, pat.mSamplingRect);
+ if (!image) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ break;
+ }
+ mat.preTranslate(pat.mSamplingRect.X(), pat.mSamplingRect.Y());
+ }
+
+ SkTileMode xTile = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS);
+ SkTileMode yTile = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS);
+
+ SkFilterMode filterMode = pat.mSamplingFilter == SamplingFilter::POINT
+ ? SkFilterMode::kNearest
+ : SkFilterMode::kLinear;
+
+ sk_sp<SkShader> shader =
+ image->makeShader(xTile, yTile, SkSamplingOptions(filterMode), mat);
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ gfxDebug() << "Failed creating Skia surface shader: x-tile="
+ << (int)xTile << " y-tile=" << (int)yTile
+ << " matrix=" << (mat.isFinite() ? "finite" : "non-finite");
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ break;
+ }
+ }
+}
+
+static inline Rect GetClipBounds(SkCanvas* aCanvas) {
+ // Use a manually transformed getClipDeviceBounds instead of
+ // getClipBounds because getClipBounds inflates the the bounds
+ // by a pixel in each direction to compensate for antialiasing.
+ SkIRect deviceBounds;
+ if (!aCanvas->getDeviceClipBounds(&deviceBounds)) {
+ return Rect();
+ }
+ SkMatrix inverseCTM;
+ if (!aCanvas->getTotalMatrix().invert(&inverseCTM)) {
+ return Rect();
+ }
+ SkRect localBounds;
+ inverseCTM.mapRect(&localBounds, SkRect::Make(deviceBounds));
+ return SkRectToRect(localBounds);
+}
+
+struct AutoPaintSetup {
+ AutoPaintSetup(SkCanvas* aCanvas, const DrawOptions& aOptions,
+ const Pattern& aPattern, const Rect* aMaskBounds = nullptr,
+ const SkMatrix* aMatrix = nullptr,
+ const Rect* aSourceBounds = nullptr)
+ : mNeedsRestore(false), mAlpha(1.0) {
+ Init(aCanvas, aOptions, aMaskBounds, false);
+ SetPaintPattern(mPaint, aPattern, mLock, mAlpha, aMatrix, aSourceBounds);
+ }
+
+ AutoPaintSetup(SkCanvas* aCanvas, const DrawOptions& aOptions,
+ const Rect* aMaskBounds = nullptr, bool aForceGroup = false)
+ : mNeedsRestore(false), mAlpha(1.0) {
+ Init(aCanvas, aOptions, aMaskBounds, aForceGroup);
+ }
+
+ ~AutoPaintSetup() {
+ if (mNeedsRestore) {
+ mCanvas->restore();
+ }
+ }
+
+ void Init(SkCanvas* aCanvas, const DrawOptions& aOptions,
+ const Rect* aMaskBounds, bool aForceGroup) {
+ mPaint.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
+ mCanvas = aCanvas;
+
+ // TODO: Can we set greyscale somehow?
+ if (aOptions.mAntialiasMode != AntialiasMode::NONE) {
+ mPaint.setAntiAlias(true);
+ } else {
+ mPaint.setAntiAlias(false);
+ }
+
+ bool needsGroup =
+ aForceGroup ||
+ (!IsOperatorBoundByMask(aOptions.mCompositionOp) &&
+ (!aMaskBounds || !aMaskBounds->Contains(GetClipBounds(aCanvas))));
+
+ // TODO: We could skip the temporary for operator_source and just
+ // clear the clip rect. The other operators would be harder
+ // but could be worth it to skip pushing a group.
+ if (needsGroup) {
+ mPaint.setBlendMode(SkBlendMode::kSrcOver);
+ SkPaint temp;
+ temp.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
+ temp.setAlpha(ColorFloatToByte(aOptions.mAlpha));
+ // TODO: Get a rect here
+ SkCanvas::SaveLayerRec rec(nullptr, &temp,
+ SkCanvas::kPreserveLCDText_SaveLayerFlag);
+ mCanvas->saveLayer(rec);
+ mNeedsRestore = true;
+ } else {
+ mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha));
+ mAlpha = aOptions.mAlpha;
+ }
+ }
+
+ // TODO: Maybe add an operator overload to access this easier?
+ SkPaint mPaint;
+ bool mNeedsRestore;
+ SkCanvas* mCanvas;
+ Maybe<MutexAutoLock> mLock;
+ Float mAlpha;
+};
+
+void DrawTargetSkia::Flush() { mCanvas->flush(); }
+
+void DrawTargetSkia::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource,
+ const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) {
+ if (aSource.IsEmpty()) {
+ return;
+ }
+
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return;
+ }
+
+ SkRect destRect = RectToSkRect(aDest);
+ SkRect sourceRect = RectToSkRect(aSource - aSurface->GetRect().TopLeft());
+ bool forceGroup =
+ image->isAlphaOnly() && aOptions.mCompositionOp != CompositionOp::OP_OVER;
+
+ AutoPaintSetup paint(mCanvas, aOptions, &aDest, forceGroup);
+
+ SkFilterMode filterMode =
+ aSurfOptions.mSamplingFilter == SamplingFilter::POINT
+ ? SkFilterMode::kNearest
+ : SkFilterMode::kLinear;
+
+ mCanvas->drawImageRect(image, sourceRect, destRect,
+ SkSamplingOptions(filterMode), &paint.mPaint,
+ SkCanvas::kStrict_SrcRectConstraint);
+}
+
+DrawTargetType DrawTargetSkia::GetType() const {
+ return DrawTargetType::SOFTWARE_RASTER;
+}
+
+void DrawTargetSkia::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ if (!aNode || aNode->GetBackendType() != FILTER_BACKEND_SOFTWARE) {
+ return;
+ }
+ FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
+ filter->Draw(this, aSourceRect, aDestPoint, aOptions);
+}
+
+void DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface* aSurface,
+ const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOperator) {
+ if (aSurface->GetSize().IsEmpty()) {
+ return;
+ }
+
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return;
+ }
+
+ mCanvas->save();
+ mCanvas->resetMatrix();
+
+ SkPaint paint;
+ paint.setBlendMode(GfxOpToSkiaOp(aOperator));
+
+ // bug 1201272
+ // We can't use the SkDropShadowImageFilter here because it applies the xfer
+ // mode first to render the bitmap to a temporary layer, and then implicitly
+ // uses src-over to composite the resulting shadow.
+ // The canvas spec, however, states that the composite op must be used to
+ // composite the resulting shadow, so we must instead use a SkBlurImageFilter
+ // to blur the image ourselves.
+
+ SkPaint shadowPaint;
+ shadowPaint.setBlendMode(GfxOpToSkiaOp(aOperator));
+
+ auto shadowDest = IntPoint::Round(aDest + aShadow.mOffset);
+
+ SkBitmap blurMask;
+ // Extract the alpha channel of the image into a bitmap. If the image is A8
+ // format already, then we can directly reuse the bitmap rather than create a
+ // new one as the surface only needs to be drawn from once.
+ if (ExtractAlphaBitmap(image, &blurMask, true)) {
+ // Prefer using our own box blur instead of Skia's. It currently performs
+ // much better than SkBlurImageFilter or SkBlurMaskFilter on the CPU.
+ AlphaBoxBlur blur(Rect(0, 0, blurMask.width(), blurMask.height()),
+ int32_t(blurMask.rowBytes()), aShadow.mSigma,
+ aShadow.mSigma);
+ blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels()));
+ blurMask.notifyPixelsChanged();
+
+ shadowPaint.setColor(ColorToSkColor(aShadow.mColor, 1.0f));
+
+ mCanvas->drawImage(blurMask.asImage(), shadowDest.x, shadowDest.y,
+ SkSamplingOptions(SkFilterMode::kLinear), &shadowPaint);
+ } else {
+ sk_sp<SkImageFilter> blurFilter(
+ SkImageFilters::Blur(aShadow.mSigma, aShadow.mSigma, nullptr));
+ sk_sp<SkColorFilter> colorFilter(SkColorFilters::Blend(
+ ColorToSkColor(aShadow.mColor, 1.0f), SkBlendMode::kSrcIn));
+
+ shadowPaint.setImageFilter(blurFilter);
+ shadowPaint.setColorFilter(colorFilter);
+
+ mCanvas->drawImage(image, shadowDest.x, shadowDest.y,
+ SkSamplingOptions(SkFilterMode::kLinear), &shadowPaint);
+ }
+
+ if (aSurface->GetFormat() != SurfaceFormat::A8) {
+ // Composite the original image after the shadow
+ auto dest = IntPoint::Round(aDest);
+ mCanvas->drawImage(image, dest.x, dest.y,
+ SkSamplingOptions(SkFilterMode::kLinear), &paint);
+ }
+
+ mCanvas->restore();
+}
+
+void DrawTargetSkia::FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ // The sprite blitting path in Skia can be faster than the shader blitter for
+ // operators other than source (or source-over with opaque surface). So, when
+ // possible/beneficial, route to DrawSurface which will use the sprite
+ // blitter.
+ if (aPattern.GetType() == PatternType::SURFACE &&
+ aOptions.mCompositionOp != CompositionOp::OP_SOURCE) {
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ // Verify there is a valid surface and a pattern matrix without skew.
+ if (pat.mSurface &&
+ (aOptions.mCompositionOp != CompositionOp::OP_OVER ||
+ GfxFormatToSkiaAlphaType(pat.mSurface->GetFormat()) !=
+ kOpaque_SkAlphaType) &&
+ !pat.mMatrix.HasNonAxisAlignedTransform()) {
+ // Bound the sampling to smaller of the bounds or the sampling rect.
+ IntRect srcRect(IntPoint(0, 0), pat.mSurface->GetSize());
+ if (!pat.mSamplingRect.IsEmpty()) {
+ srcRect = srcRect.Intersect(pat.mSamplingRect);
+ }
+ // Transform the destination rectangle by the inverse of the pattern
+ // matrix so that it is in pattern space like the source rectangle.
+ Rect patRect = aRect - pat.mMatrix.GetTranslation();
+ patRect.Scale(1.0f / pat.mMatrix._11, 1.0f / pat.mMatrix._22);
+ // Verify the pattern rectangle will not tile or clamp.
+ if (!patRect.IsEmpty() && srcRect.Contains(RoundedOut(patRect))) {
+ // The pattern is a surface with an axis-aligned source rectangle
+ // fitting entirely in its bounds, so just treat it as a DrawSurface.
+ DrawSurface(pat.mSurface, aRect, patRect,
+ DrawSurfaceOptions(pat.mSamplingFilter), aOptions);
+ return;
+ }
+ }
+ }
+
+ MarkChanged();
+ SkRect rect = RectToSkRect(aRect);
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern, &aRect, nullptr, &aRect);
+
+ mCanvas->drawRect(rect, paint.mPaint);
+}
+
+void DrawTargetSkia::Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ MarkChanged();
+ MOZ_ASSERT(aPath, "Null path");
+ if (aPath->GetBackendType() != BackendType::SKIA) {
+ return;
+ }
+
+ const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath);
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+ return;
+ }
+
+ if (!skiaPath->GetPath().isFinite()) {
+ return;
+ }
+
+ mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
+}
+
+static Double DashPeriodLength(const StrokeOptions& aStrokeOptions) {
+ Double length = 0;
+ for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) {
+ length += aStrokeOptions.mDashPattern[i];
+ }
+ if (aStrokeOptions.mDashLength & 1) {
+ // "If an odd number of values is provided, then the list of values is
+ // repeated to yield an even number of values."
+ // Double the length.
+ length += length;
+ }
+ return length;
+}
+
+static inline Double RoundDownToMultiple(Double aValue, Double aFactor) {
+ return floor(aValue / aFactor) * aFactor;
+}
+
+static Rect UserSpaceStrokeClip(const IntRect& aDeviceClip,
+ const Matrix& aTransform,
+ const StrokeOptions& aStrokeOptions) {
+ Matrix inverse = aTransform;
+ if (!inverse.Invert()) {
+ return Rect();
+ }
+ Rect deviceClip(aDeviceClip);
+ deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform));
+ return inverse.TransformBounds(deviceClip);
+}
+
+static Rect ShrinkClippedStrokedRect(const Rect& aStrokedRect,
+ const IntRect& aDeviceClip,
+ const Matrix& aTransform,
+ const StrokeOptions& aStrokeOptions) {
+ Rect userSpaceStrokeClip =
+ UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions);
+ RectDouble strokedRectDouble(aStrokedRect.X(), aStrokedRect.Y(),
+ aStrokedRect.Width(), aStrokedRect.Height());
+ RectDouble intersection = strokedRectDouble.Intersect(
+ RectDouble(userSpaceStrokeClip.X(), userSpaceStrokeClip.Y(),
+ userSpaceStrokeClip.Width(), userSpaceStrokeClip.Height()));
+ Double dashPeriodLength = DashPeriodLength(aStrokeOptions);
+ if (intersection.IsEmpty() || dashPeriodLength == 0.0f) {
+ return Rect(intersection.X(), intersection.Y(), intersection.Width(),
+ intersection.Height());
+ }
+
+ // Reduce the rectangle side lengths in multiples of the dash period length
+ // so that the visible dashes stay in the same place.
+ MarginDouble insetBy = strokedRectDouble - intersection;
+ insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength);
+ insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength);
+ insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength);
+ insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength);
+
+ strokedRectDouble.Deflate(insetBy);
+ return Rect(strokedRectDouble.X(), strokedRectDouble.Y(),
+ strokedRectDouble.Width(), strokedRectDouble.Height());
+}
+
+void DrawTargetSkia::StrokeRect(const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ // Stroking large rectangles with dashes is expensive with Skia (fixed
+ // overhead based on the number of dashes, regardless of whether the dashes
+ // are visible), so we try to reduce the size of the stroked rectangle as
+ // much as possible before passing it on to Skia.
+ Rect rect = aRect;
+ if (aStrokeOptions.mDashLength > 0 && !rect.IsEmpty()) {
+ IntRect deviceClip(IntPoint(0, 0), mSize);
+ SkIRect clipBounds;
+ if (mCanvas->getDeviceClipBounds(&clipBounds)) {
+ deviceClip = SkIRectToIntRect(clipBounds);
+ }
+ rect =
+ ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions);
+ if (rect.IsEmpty()) {
+ return;
+ }
+ }
+
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+ return;
+ }
+
+ mCanvas->drawRect(RectToSkRect(rect), paint.mPaint);
+}
+
+void DrawTargetSkia::StrokeLine(const Point& aStart, const Point& aEnd,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+ return;
+ }
+
+ mCanvas->drawLine(SkFloatToScalar(aStart.x), SkFloatToScalar(aStart.y),
+ SkFloatToScalar(aEnd.x), SkFloatToScalar(aEnd.y),
+ paint.mPaint);
+}
+
+void DrawTargetSkia::Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ MarkChanged();
+ if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
+ return;
+ }
+
+ const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath);
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+
+ if (!skiaPath->GetPath().isFinite()) {
+ return;
+ }
+
+ mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
+}
+
+#ifdef MOZ_WIDGET_COCOA
+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;
+}
+
+/***
+ * We have to do a lot of work to draw glyphs with CG because
+ * CG assumes that the origin of rects are in the bottom left
+ * while every other DrawTarget assumes the top left is the origin.
+ * This means we have to transform the CGContext to have rects
+ * actually be applied in top left fashion. We do this by:
+ *
+ * 1) Translating the context up by the height of the canvas
+ * 2) Flipping the context by the Y axis so it's upside down.
+ *
+ * These two transforms put the origin in the top left.
+ * Transforms are better understood thinking about them from right to left order
+ * (mathematically).
+ *
+ * Consider a point we want to draw at (0, 10) in normal cartesian planes with
+ * a box of (100, 100). in CG terms, this would be at (0, 10).
+ * Positive Y values point up.
+ * In our DrawTarget terms, positive Y values point down, so (0, 10) would be
+ * at (0, 90) in cartesian plane terms. That means our point at (0, 10) in
+ * DrawTarget terms should end up at (0, 90). How does this work with the
+ * current transforms?
+ *
+ * Going right to left with the transforms, a CGPoint of (0, 10) has cartesian
+ * coordinates of (0, 10). The first flip of the Y axis puts the point now at
+ * (0, -10); Next, we translate the context up by the size of the canvas
+ * (Positive Y values go up in CG coordinates but down in our draw target
+ * coordinates). Since our canvas size is (100, 100), the resulting coordinate
+ * becomes (0, 90), which is what we expect from our DrawTarget code. These two
+ * transforms put the CG context equal to what every other DrawTarget expects.
+ *
+ * Next, we need two more transforms for actual text. IF we left the transforms
+ * as is, the text would be drawn upside down, so we need another flip of the Y
+ * axis to draw the text right side up. However, with only the flip, the text
+ * would be drawn in the wrong place. Thus we also have to invert the Y position
+ * of the glyphs to get them in the right place.
+ *
+ * Thus we have the following transforms:
+ * 1) Translation of the context up
+ * 2) Flipping the context around the Y axis
+ * 3) Flipping the context around the Y axis
+ * 4) Inverting the Y position of each glyph
+ *
+ * We cannot cancel out (2) and (3) as we have to apply the clips and transforms
+ * of DrawTargetSkia between (2) and (3).
+ *
+ * Consider the example letter P, drawn at (0, 20) in CG coordinates in a
+ * (100, 100) rect.
+ * Again, going right to left of the transforms. We'd get:
+ *
+ * 1) The letter P drawn at (0, -20) due to the inversion of the Y axis
+ * 2) The letter P upside down (b) at (0, 20) due to the second flip
+ * 3) The letter P right side up at (0, -20) due to the first flip
+ * 4) The letter P right side up at (0, 80) due to the translation
+ *
+ * tl;dr - CGRects assume origin is bottom left, DrawTarget rects assume top
+ * left.
+ */
+static bool SetupCGContext(DrawTargetSkia* aDT, CGContextRef aCGContext,
+ SkCanvas* aCanvas, const IntPoint& aOrigin,
+ const IntSize& aSize, bool aClipped) {
+ // DrawTarget expects the origin to be at the top left, but CG
+ // expects it to be at the bottom left. Transform to set the origin to
+ // the top left. Have to set this before we do anything else.
+ // This is transform (1) up top
+ CGContextTranslateCTM(aCGContext, -aOrigin.x, aOrigin.y + aSize.height);
+
+ // Transform (2) from the comments.
+ CGContextScaleCTM(aCGContext, 1, -1);
+
+ // Want to apply clips BEFORE the transform since the transform
+ // will apply to the clips we apply.
+ if (aClipped) {
+ SkRegion clipRegion;
+ aCanvas->temporary_internal_getRgnClip(&clipRegion);
+ Vector<CGRect, 8> rects;
+ for (SkRegion::Iterator it(clipRegion); !it.done(); it.next()) {
+ const SkIRect& rect = it.rect();
+ if (!rects.append(
+ CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()))) {
+ break;
+ }
+ }
+ if (rects.length()) {
+ CGContextClipToRects(aCGContext, rects.begin(), rects.length());
+ }
+ }
+
+ CGContextConcatCTM(aCGContext,
+ GfxMatrixToCGAffineTransform(aDT->GetTransform()));
+ return true;
+}
+// End long comment about transforms.
+
+// The context returned from this method will have the origin
+// in the top left and will have applied all the neccessary clips
+// and transforms to the CGContext. See the comment above
+// SetupCGContext.
+CGContextRef DrawTargetSkia::BorrowCGContext(const DrawOptions& aOptions) {
+ // Since we can't replay Skia clips, we have to use a layer if we have a
+ // complex clip. After saving a layer, the SkCanvas queries for needing a
+ // layer change so save if we pushed a layer.
+ mNeedLayer = !mCanvas->isClipEmpty() && !mCanvas->isClipRect();
+ if (mNeedLayer) {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ SkCanvas::SaveLayerRec rec(nullptr, &paint,
+ SkCanvas::kInitWithPrevious_SaveLayerFlag);
+ mCanvas->saveLayer(rec);
+ }
+
+ uint8_t* data = nullptr;
+ int32_t stride;
+ SurfaceFormat format;
+ IntSize size;
+ IntPoint origin;
+ if (!LockBits(&data, &size, &stride, &format, &origin)) {
+ NS_WARNING("Could not lock skia bits to wrap CG around");
+ return nullptr;
+ }
+
+ if (!mNeedLayer && (data == mCanvasData) && mCG && (mCGSize == size)) {
+ // If our canvas data still points to the same data,
+ // we can reuse the CG Context
+ CGContextSetAlpha(mCG, aOptions.mAlpha);
+ CGContextSetShouldAntialias(mCG,
+ aOptions.mAntialiasMode != AntialiasMode::NONE);
+ CGContextSaveGState(mCG);
+ SetupCGContext(this, mCG, mCanvas, origin, size, true);
+ return mCG;
+ }
+
+ if (!mColorSpace) {
+ mColorSpace = (format == SurfaceFormat::A8) ? CGColorSpaceCreateDeviceGray()
+ : CGColorSpaceCreateDeviceRGB();
+ }
+
+ if (mCG) {
+ // Release the old CG context since it's no longer valid.
+ CGContextRelease(mCG);
+ }
+
+ mCanvasData = data;
+ mCGSize = size;
+
+ uint32_t bitmapInfo =
+ (format == SurfaceFormat::A8)
+ ? kCGImageAlphaOnly
+ : kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
+
+ mCG = CGBitmapContextCreateWithData(
+ mCanvasData, mCGSize.width, mCGSize.height, 8, /* bits per component */
+ stride, mColorSpace, bitmapInfo, NULL, /* Callback when released */
+ NULL);
+ if (!mCG) {
+ if (mNeedLayer) {
+ mCanvas->restore();
+ }
+ ReleaseBits(mCanvasData);
+ NS_WARNING("Could not create bitmap around skia data\n");
+ return nullptr;
+ }
+
+ CGContextSetAlpha(mCG, aOptions.mAlpha);
+ CGContextSetShouldAntialias(mCG,
+ aOptions.mAntialiasMode != AntialiasMode::NONE);
+ CGContextSetShouldSmoothFonts(mCG, true);
+ CGContextSetTextDrawingMode(mCG, kCGTextFill);
+ CGContextSaveGState(mCG);
+ SetupCGContext(this, mCG, mCanvas, origin, size, !mNeedLayer);
+ return mCG;
+}
+
+void DrawTargetSkia::ReturnCGContext(CGContextRef aCGContext) {
+ MOZ_ASSERT(aCGContext == mCG);
+ ReleaseBits(mCanvasData);
+ CGContextRestoreGState(aCGContext);
+
+ if (mNeedLayer) {
+ // A layer was used for clipping and is about to be popped by the restore.
+ // Make sure the CG context referencing it is released first so the popped
+ // layer doesn't accidentally get used.
+ if (mCG) {
+ CGContextRelease(mCG);
+ mCG = nullptr;
+ }
+ mCanvas->restore();
+ }
+}
+
+CGContextRef BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget* aDT) {
+ DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT);
+ return skiaDT->BorrowCGContext(DrawOptions());
+}
+
+void BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget* aDT,
+ CGContextRef cg) {
+ DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT);
+ skiaDT->ReturnCGContext(cg);
+}
+#endif
+
+static bool CanDrawFont(ScaledFont* aFont) {
+ switch (aFont->GetType()) {
+ case FontType::FREETYPE:
+ case FontType::FONTCONFIG:
+ case FontType::MAC:
+ case FontType::GDI:
+ case FontType::DWRITE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void DrawTargetSkia::DrawGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions* aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (!CanDrawFont(aFont)) {
+ return;
+ }
+
+ MarkChanged();
+
+ ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont);
+ SkTypeface* typeface = skiaFont->GetSkTypeface();
+ if (!typeface) {
+ return;
+ }
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (aStrokeOptions && !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) {
+ return;
+ }
+
+ AntialiasMode aaMode = aFont->GetDefaultAAMode();
+ if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
+ aaMode = aOptions.mAntialiasMode;
+ }
+ bool aaEnabled = aaMode != AntialiasMode::NONE;
+ paint.mPaint.setAntiAlias(aaEnabled);
+
+ SkFont font(sk_ref_sp(typeface), SkFloatToScalar(skiaFont->mSize));
+
+ bool useSubpixelAA =
+ GetPermitSubpixelAA() &&
+ (aaMode == AntialiasMode::DEFAULT || aaMode == AntialiasMode::SUBPIXEL);
+ font.setEdging(useSubpixelAA ? SkFont::Edging::kSubpixelAntiAlias
+ : (aaEnabled ? SkFont::Edging::kAntiAlias
+ : SkFont::Edging::kAlias));
+
+ skiaFont->SetupSkFontDrawOptions(font);
+
+ // Limit the amount of internal batch allocations Skia does.
+ const uint32_t kMaxGlyphBatchSize = 8192;
+
+ for (uint32_t offset = 0; offset < aBuffer.mNumGlyphs;) {
+ uint32_t batchSize =
+ std::min(aBuffer.mNumGlyphs - offset, kMaxGlyphBatchSize);
+ SkTextBlobBuilder builder;
+ auto runBuffer = builder.allocRunPos(font, batchSize);
+ for (uint32_t i = 0; i < batchSize; i++, offset++) {
+ runBuffer.glyphs[i] = aBuffer.mGlyphs[offset].mIndex;
+ runBuffer.points()[i] = PointToSkPoint(aBuffer.mGlyphs[offset].mPosition);
+ }
+
+ sk_sp<SkTextBlob> text = builder.make();
+ mCanvas->drawTextBlob(text, 0, 0, paint.mPaint);
+ }
+}
+
+Maybe<Rect> DrawTargetSkia::GetGlyphLocalBounds(
+ ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
+ const StrokeOptions* aStrokeOptions, const DrawOptions& aOptions) {
+ if (!CanDrawFont(aFont)) {
+ return Nothing();
+ }
+
+ ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont);
+ SkTypeface* typeface = skiaFont->GetSkTypeface();
+ if (!typeface) {
+ return Nothing();
+ }
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (aStrokeOptions && !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) {
+ return Nothing();
+ }
+
+ AntialiasMode aaMode = aFont->GetDefaultAAMode();
+ if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
+ aaMode = aOptions.mAntialiasMode;
+ }
+ bool aaEnabled = aaMode != AntialiasMode::NONE;
+ paint.mPaint.setAntiAlias(aaEnabled);
+
+ SkFont font(sk_ref_sp(typeface), SkFloatToScalar(skiaFont->mSize));
+
+ bool useSubpixelAA =
+ GetPermitSubpixelAA() &&
+ (aaMode == AntialiasMode::DEFAULT || aaMode == AntialiasMode::SUBPIXEL);
+ font.setEdging(useSubpixelAA ? SkFont::Edging::kSubpixelAntiAlias
+ : (aaEnabled ? SkFont::Edging::kAntiAlias
+ : SkFont::Edging::kAlias));
+
+ skiaFont->SetupSkFontDrawOptions(font);
+
+ // Limit the amount of internal batch allocations Skia does.
+ const uint32_t kMaxGlyphBatchSize = 8192;
+
+ // Avoid using TextBlobBuilder for bounds computations as the conservative
+ // bounds can be wrong due to buggy font metrics. Instead, explicitly compute
+ // tight bounds directly with the SkFont.
+ Vector<SkGlyphID, 32> glyphs;
+ Vector<SkRect, 32> rects;
+ Rect bounds;
+ for (uint32_t offset = 0; offset < aBuffer.mNumGlyphs;) {
+ uint32_t batchSize =
+ std::min(aBuffer.mNumGlyphs - offset, kMaxGlyphBatchSize);
+ if (glyphs.resizeUninitialized(batchSize) &&
+ rects.resizeUninitialized(batchSize)) {
+ for (uint32_t i = 0; i < batchSize; i++) {
+ glyphs[i] = aBuffer.mGlyphs[offset + i].mIndex;
+ }
+ font.getBounds(glyphs.begin(), batchSize, rects.begin(), nullptr);
+ for (uint32_t i = 0; i < batchSize; i++) {
+ bounds = bounds.Union(SkRectToRect(rects[i]) +
+ aBuffer.mGlyphs[offset + i].mPosition);
+ }
+ }
+ offset += batchSize;
+ }
+
+ SkRect storage;
+ bounds = SkRectToRect(
+ paint.mPaint.computeFastBounds(RectToSkRect(bounds), &storage));
+
+ if (bounds.IsEmpty()) {
+ return Nothing();
+ }
+
+ return Some(bounds);
+}
+
+void DrawTargetSkia::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ DrawGlyphs(aFont, aBuffer, aPattern, nullptr, aOptions);
+}
+
+void DrawTargetSkia::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ DrawGlyphs(aFont, aBuffer, aPattern, &aStrokeOptions, aOptions);
+}
+
+void DrawTargetSkia::Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions) {
+ Maybe<MutexAutoLock> lock;
+ SkPaint maskPaint;
+ SetPaintPattern(maskPaint, aMask, lock);
+
+ sk_sp<SkShader> maskShader(maskPaint.getShader());
+ if (!maskShader && maskPaint.getAlpha() != 0xFF) {
+ if (maskPaint.getAlpha() == 0) {
+ return;
+ }
+ maskShader = SkShaders::Color(maskPaint.getColor());
+ if (!maskShader) {
+ gfxDebug() << "Failed creating Skia clip shader for Mask";
+ return;
+ }
+ }
+
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aSource);
+
+ mCanvas->save();
+ if (maskShader) {
+ mCanvas->clipShader(maskShader);
+ }
+
+ mCanvas->drawPaint(paint.mPaint);
+
+ mCanvas->restore();
+}
+
+void DrawTargetSkia::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
+ Point aOffset, const DrawOptions& aOptions) {
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> maskImage = GetSkImageForSurface(aMask, &lock);
+ SkMatrix maskOffset = SkMatrix::Translate(
+ PointToSkPoint(aOffset + Point(aMask->GetRect().TopLeft())));
+ sk_sp<SkShader> maskShader = maskImage->makeShader(
+ SkTileMode::kClamp, SkTileMode::kClamp,
+ SkSamplingOptions(SkFilterMode::kLinear), maskOffset);
+ if (!maskShader) {
+ gfxDebug() << "Failed creating Skia clip shader for MaskSurface";
+ return;
+ }
+
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aSource);
+
+ mCanvas->save();
+ mCanvas->clipShader(maskShader);
+
+ mCanvas->drawRect(RectToSkRect(Rect(aMask->GetRect()) + aOffset),
+ paint.mPaint);
+
+ mCanvas->restore();
+}
+
+bool DrawTarget::Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) {
+ // Composite the 3D transform with the DT's transform.
+ Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform);
+ if (fullMat.IsSingular()) {
+ return false;
+ }
+ // Transform the surface bounds and clip to this DT.
+ IntRect xformBounds = RoundedOut(fullMat.TransformAndClipBounds(
+ Rect(Point(0, 0), Size(aSurface->GetSize())),
+ Rect(Point(0, 0), Size(GetSize()))));
+ if (xformBounds.IsEmpty()) {
+ return true;
+ }
+ // Offset the matrix by the transformed origin.
+ fullMat.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0);
+
+ // Read in the source data.
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> srcImage = GetSkImageForSurface(aSurface, &lock);
+ if (!srcImage) {
+ return true;
+ }
+
+ // Set up an intermediate destination surface only the size of the transformed
+ // bounds. Try to pass through the source's format unmodified in both the BGRA
+ // and ARGB cases.
+ RefPtr<DataSourceSurface> dstSurf = Factory::CreateDataSourceSurface(
+ xformBounds.Size(),
+ !srcImage->isOpaque() ? aSurface->GetFormat()
+ : SurfaceFormat::A8R8G8B8_UINT32,
+ true);
+ if (!dstSurf) {
+ return false;
+ }
+
+ DataSourceSurface::ScopedMap map(dstSurf, DataSourceSurface::READ_WRITE);
+ std::unique_ptr<SkCanvas> dstCanvas(SkCanvas::MakeRasterDirect(
+ SkImageInfo::Make(xformBounds.Width(), xformBounds.Height(),
+ GfxFormatToSkiaColorType(dstSurf->GetFormat()),
+ kPremul_SkAlphaType),
+ map.GetData(), map.GetStride()));
+ if (!dstCanvas) {
+ return false;
+ }
+
+ // Do the transform.
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setBlendMode(SkBlendMode::kSrc);
+
+ SkMatrix xform;
+ GfxMatrixToSkiaMatrix(fullMat, xform);
+ dstCanvas->setMatrix(xform);
+
+ dstCanvas->drawImage(srcImage, 0, 0, SkSamplingOptions(SkFilterMode::kLinear),
+ &paint);
+ dstCanvas->flush();
+
+ // Temporarily reset the DT's transform, since it has already been composed
+ // above.
+ Matrix origTransform = mTransform;
+ SetTransform(Matrix());
+
+ // Draw the transformed surface within the transformed bounds.
+ DrawSurface(dstSurf, Rect(xformBounds),
+ Rect(Point(0, 0), Size(xformBounds.Size())));
+
+ SetTransform(origTransform);
+
+ return true;
+}
+
+bool DrawTargetSkia::Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) {
+ if (aMatrix.IsSingular()) {
+ return false;
+ }
+
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return true;
+ }
+
+ mCanvas->save();
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ SkMatrix xform;
+ GfxMatrixToSkiaMatrix(aMatrix, xform);
+ mCanvas->concat(xform);
+
+ mCanvas->drawImage(image, 0, 0, SkSamplingOptions(SkFilterMode::kLinear),
+ &paint);
+
+ mCanvas->restore();
+
+ return true;
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::CreateSourceSurfaceFromData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat) const {
+ RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia();
+
+ if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
+ gfxDebug() << *this
+ << ": Failure to create source surface from data. Size: "
+ << aSize;
+ return nullptr;
+ }
+
+ return newSurf.forget();
+}
+
+already_AddRefed<DrawTarget> DrawTargetSkia::CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ RefPtr<DrawTargetSkia> target = new DrawTargetSkia();
+#ifdef DEBUG
+ if (!IsBackedByPixels(mCanvas)) {
+ // If our canvas is backed by vector storage such as PDF then we want to
+ // create a new DrawTarget with similar storage to avoid losing fidelity
+ // (fidelity will be lost if the returned DT is Snapshot()'ed and drawn
+ // back onto us since a raster will be drawn instead of vector commands).
+ NS_WARNING("Not backed by pixels - we need to handle PDF backed SkCanvas");
+ }
+#endif
+
+ if (!target->Init(aSize, aFormat)) {
+ return nullptr;
+ }
+ return target.forget();
+}
+
+bool DrawTargetSkia::CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const {
+ auto minmaxPair = std::minmax(aSize.width, aSize.height);
+ return minmaxPair.first > 0 &&
+ size_t(minmaxPair.second) < GetMaxSurfaceSize();
+}
+
+RefPtr<DrawTarget> DrawTargetSkia::CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) {
+ SkIRect clipBounds;
+
+ RefPtr<DrawTarget> result;
+ // Doing this save()/restore() dance is wasteful
+ mCanvas->save();
+ if (!aBounds.IsEmpty()) {
+ mCanvas->clipRect(RectToSkRect(aBounds), SkClipOp::kIntersect, true);
+ }
+ if (mCanvas->getDeviceClipBounds(&clipBounds)) {
+ RefPtr<DrawTarget> dt = CreateSimilarDrawTarget(
+ IntSize(clipBounds.width(), clipBounds.height()), aFormat);
+ if (dt) {
+ result = gfx::Factory::CreateOffsetDrawTarget(
+ dt, IntPoint(clipBounds.x(), clipBounds.y()));
+ if (result) {
+ result->SetTransform(mTransform);
+ }
+ }
+ } else {
+ // Everything is clipped but we still want some kind of surface
+ result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat);
+ }
+ mCanvas->restore();
+ return result;
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(
+ SourceSurface* aSurface) const {
+ if (aSurface->GetType() == SurfaceType::SKIA) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
+
+ // For plugins, GDI can sometimes just write 0 to the alpha channel
+ // even for RGBX formats. In this case, we have to manually write
+ // the alpha channel to make Skia happy with RGBX and in case GDI
+ // writes some bad data. Luckily, this only happens on plugins.
+ WriteRGBXFormat(map.GetData(), dataSurface->GetSize(), map.GetStride(),
+ dataSurface->GetFormat());
+ return dataSurface.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::OptimizeSourceSurface(
+ SourceSurface* aSurface) const {
+ if (aSurface->GetType() == SurfaceType::SKIA) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ // If we're not using skia-gl then drawing doesn't require any
+ // uploading, so any data surface is fine. Call GetDataSurface
+ // to trigger any required readback so that it only happens
+ // once.
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+#ifdef DEBUG
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ);
+ MOZ_ASSERT(VerifyRGBXFormat(map.GetData(), dataSurface->GetSize(),
+ map.GetStride(), dataSurface->GetFormat()));
+#endif
+ return dataSurface.forget();
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetSkia::CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const {
+ return nullptr;
+}
+
+void DrawTargetSkia::CopySurface(SourceSurface* aSurface,
+ const IntRect& aSourceRect,
+ const IntPoint& aDestination) {
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return;
+ }
+
+ SkPixmap srcPixmap;
+ if (!image->peekPixels(&srcPixmap)) {
+ return;
+ }
+
+ // Ensure the source rect intersects the surface bounds.
+ IntRect srcRect = aSourceRect.Intersect(SkIRectToIntRect(srcPixmap.bounds()));
+ // Move the destination offset to match the altered source rect.
+ IntPoint dstOffset =
+ aDestination + (srcRect.TopLeft() - aSourceRect.TopLeft());
+ // Then ensure the dest rect intersect the canvas bounds.
+ IntRect dstRect = IntRect(dstOffset, srcRect.Size()).Intersect(GetRect());
+ // Move the source rect to match the altered dest rect.
+ srcRect += dstRect.TopLeft() - dstOffset;
+ srcRect.SizeTo(dstRect.Size());
+
+ if (!srcPixmap.extractSubset(&srcPixmap, IntRectToSkIRect(srcRect))) {
+ return;
+ }
+
+ mCanvas->writePixels(srcPixmap.info(), srcPixmap.addr(), srcPixmap.rowBytes(),
+ dstRect.x, dstRect.y);
+}
+
+static inline SkPixelGeometry GetSkPixelGeometry() {
+ return Factory::GetBGRSubpixelOrder() ? kBGR_H_SkPixelGeometry
+ : kRGB_H_SkPixelGeometry;
+}
+
+template <typename T>
+[[nodiscard]] static already_AddRefed<T> AsRefPtr(sk_sp<T>&& aSkPtr) {
+ return already_AddRefed<T>(aSkPtr.release());
+}
+
+bool DrawTargetSkia::Init(const IntSize& aSize, SurfaceFormat aFormat) {
+ if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) {
+ return false;
+ }
+
+ // we need to have surfaces that have a stride aligned to 4 for interop with
+ // cairo
+ SkImageInfo info = MakeSkiaImageInfo(aSize, aFormat);
+ size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel());
+ if (!stride) {
+ return false;
+ }
+ SkSurfaceProps props(0, GetSkPixelGeometry());
+
+ if (aFormat == SurfaceFormat::A8) {
+ // Skia does not fully allocate the last row according to stride.
+ // Since some of our algorithms (i.e. blur) depend on this, we must allocate
+ // the bitmap pixels manually.
+ CheckedInt<size_t> size = stride;
+ size *= info.height();
+ // We need to leave room for an additional 3 bytes for a potential overrun
+ // in our blurring code.
+ size += 3;
+ if (!size.isValid()) {
+ return false;
+ }
+ void* buf = sk_malloc_flags(size.value(), SK_MALLOC_ZERO_INITIALIZE);
+ if (!buf) {
+ return false;
+ }
+ mSurface = AsRefPtr(SkSurface::MakeRasterDirectReleaseProc(
+ info, buf, stride, FreeAlphaPixels, nullptr, &props));
+ } else {
+ mSurface = AsRefPtr(SkSurface::MakeRaster(info, stride, &props));
+ }
+ if (!mSurface) {
+ return false;
+ }
+
+ mSize = aSize;
+ mFormat = aFormat;
+ mCanvas = mSurface->getCanvas();
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+
+ if (info.isOpaque()) {
+ mCanvas->clear(SK_ColorBLACK);
+ }
+ return true;
+}
+
+bool DrawTargetSkia::Init(SkCanvas* aCanvas) {
+ mCanvas = aCanvas;
+
+ SkImageInfo imageInfo = mCanvas->imageInfo();
+
+ // If the canvas is backed by pixels we clear it to be on the safe side. If
+ // it's not (for example, for PDF output) we don't.
+ if (IsBackedByPixels(mCanvas)) {
+ SkColor clearColor =
+ imageInfo.isOpaque() ? SK_ColorBLACK : SK_ColorTRANSPARENT;
+ mCanvas->clear(clearColor);
+ }
+
+ SkISize size = mCanvas->getBaseLayerSize();
+ mSize.width = size.width();
+ mSize.height = size.height();
+ mFormat =
+ SkiaColorTypeToGfxFormat(imageInfo.colorType(), imageInfo.alphaType());
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+ return true;
+}
+
+bool DrawTargetSkia::Init(unsigned char* aData, const IntSize& aSize,
+ int32_t aStride, SurfaceFormat aFormat,
+ bool aUninitialized) {
+ MOZ_ASSERT((aFormat != SurfaceFormat::B8G8R8X8) || aUninitialized ||
+ VerifyRGBXFormat(aData, aSize, aStride, aFormat));
+
+ SkSurfaceProps props(0, GetSkPixelGeometry());
+ mSurface = AsRefPtr(SkSurface::MakeRasterDirect(
+ MakeSkiaImageInfo(aSize, aFormat), aData, aStride, &props));
+ if (!mSurface) {
+ return false;
+ }
+
+ mSize = aSize;
+ mFormat = aFormat;
+ mCanvas = mSurface->getCanvas();
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+ return true;
+}
+
+bool DrawTargetSkia::Init(RefPtr<DataSourceSurface>&& aSurface) {
+ auto map =
+ new DataSourceSurface::ScopedMap(aSurface, DataSourceSurface::READ_WRITE);
+ if (!map->IsMapped()) {
+ delete map;
+ return false;
+ }
+
+ SurfaceFormat format = aSurface->GetFormat();
+ IntSize size = aSurface->GetSize();
+ MOZ_ASSERT((format != SurfaceFormat::B8G8R8X8) ||
+ VerifyRGBXFormat(map->GetData(), size, map->GetStride(), format));
+
+ SkSurfaceProps props(0, GetSkPixelGeometry());
+ mSurface = AsRefPtr(SkSurface::MakeRasterDirectReleaseProc(
+ MakeSkiaImageInfo(size, format), map->GetData(), map->GetStride(),
+ DrawTargetSkia::ReleaseMappedSkSurface, map, &props));
+ if (!mSurface) {
+ delete map;
+ return false;
+ }
+
+ // map is now owned by mSurface
+ mBackingSurface = std::move(aSurface);
+ mSize = size;
+ mFormat = format;
+ mCanvas = mSurface->getCanvas();
+ SetPermitSubpixelAA(IsOpaque(format));
+ return true;
+}
+
+/* static */ void DrawTargetSkia::ReleaseMappedSkSurface(void* aPixels,
+ void* aContext) {
+ auto map = reinterpret_cast<DataSourceSurface::ScopedMap*>(aContext);
+ delete map;
+}
+
+void DrawTargetSkia::SetTransform(const Matrix& aTransform) {
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(aTransform, mat);
+ mCanvas->setMatrix(mat);
+ mTransform = aTransform;
+}
+
+void* DrawTargetSkia::GetNativeSurface(NativeSurfaceType aType) {
+ return nullptr;
+}
+
+already_AddRefed<PathBuilder> DrawTargetSkia::CreatePathBuilder(
+ FillRule aFillRule) const {
+ return PathBuilderSkia::Create(aFillRule);
+}
+
+void DrawTargetSkia::ClearRect(const Rect& aRect) {
+ MarkChanged();
+ mCanvas->save();
+ // Restrict clearing to the clip region if requested
+ mCanvas->clipRect(RectToSkRect(aRect), SkClipOp::kIntersect, true);
+ SkColor clearColor = (mFormat == SurfaceFormat::B8G8R8X8)
+ ? SK_ColorBLACK
+ : SK_ColorTRANSPARENT;
+ mCanvas->clear(clearColor);
+ mCanvas->restore();
+}
+
+void DrawTargetSkia::PushClip(const Path* aPath) {
+ if (aPath->GetBackendType() != BackendType::SKIA) {
+ return;
+ }
+
+ const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath);
+ mCanvas->save();
+ mCanvas->clipPath(skiaPath->GetPath(), SkClipOp::kIntersect, true);
+}
+
+void DrawTargetSkia::PushDeviceSpaceClipRects(const IntRect* aRects,
+ uint32_t aCount) {
+ // Build a region by unioning all the rects together.
+ SkRegion region;
+ for (uint32_t i = 0; i < aCount; i++) {
+ region.op(IntRectToSkIRect(aRects[i]), SkRegion::kUnion_Op);
+ }
+
+ // Clip with the resulting region. clipRegion does not transform
+ // this region by the current transform, unlike the other SkCanvas
+ // clip methods, so it is just passed through in device-space.
+ mCanvas->save();
+ mCanvas->clipRegion(region, SkClipOp::kIntersect);
+}
+
+void DrawTargetSkia::PushClipRect(const Rect& aRect) {
+ SkRect rect = RectToSkRect(aRect);
+
+ mCanvas->save();
+ mCanvas->clipRect(rect, SkClipOp::kIntersect, true);
+}
+
+void DrawTargetSkia::PopClip() {
+ mCanvas->restore();
+ SetTransform(GetTransform());
+}
+
+bool DrawTargetSkia::RemoveAllClips() {
+ mCanvas->restoreToCount(1);
+ SetTransform(GetTransform());
+ return true;
+}
+
+// Get clip bounds in device space for the clipping region. By default, only
+// bounds for simple (empty or rect) regions are reported. If explicitly
+// allowed, the bounds will be reported for complex (all other) regions as well.
+Maybe<IntRect> DrawTargetSkia::GetDeviceClipRect(bool aAllowComplex) const {
+ if (mCanvas->isClipEmpty()) {
+ return Some(IntRect());
+ }
+ if (aAllowComplex || mCanvas->isClipRect()) {
+ SkIRect deviceBounds;
+ if (mCanvas->getDeviceClipBounds(&deviceBounds)) {
+ return Some(SkIRectToIntRect(deviceBounds));
+ }
+ }
+ return Nothing();
+}
+
+void DrawTargetSkia::PushLayer(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground) {
+ PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
+ aCopyBackground, CompositionOp::OP_OVER);
+}
+
+void DrawTargetSkia::PushLayerWithBlend(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds,
+ bool aCopyBackground,
+ CompositionOp aCompositionOp) {
+ SkPaint paint;
+
+ paint.setAlpha(ColorFloatToByte(aOpacity));
+ paint.setBlendMode(GfxOpToSkiaOp(aCompositionOp));
+
+ // aBounds is supplied in device space, but SaveLayerRec wants local space.
+ SkRect bounds = SkRect::MakeEmpty();
+ if (!aBounds.IsEmpty()) {
+ Matrix inverseTransform = mTransform;
+ if (inverseTransform.Invert()) {
+ bounds = RectToSkRect(inverseTransform.TransformBounds(Rect(aBounds)));
+ }
+ }
+
+ // We don't pass a lock object to GetSkImageForSurface here, to force a
+ // copy of the data if this is a copy-on-write snapshot. If we instead held
+ // the lock until the corresponding PopLayer, we'd risk deadlocking if someone
+ // tried to touch the originating DrawTarget while the layer was pushed.
+ sk_sp<SkImage> clipImage = GetSkImageForSurface(aMask, nullptr);
+ bool usedMask = false;
+ if (bool(clipImage)) {
+ Rect maskBounds(aMask->GetRect());
+ sk_sp<SkShader> shader = clipImage->makeShader(
+ SkTileMode::kClamp, SkTileMode::kClamp,
+ SkSamplingOptions(SkFilterMode::kLinear),
+ SkMatrix::Translate(PointToSkPoint(maskBounds.TopLeft())));
+ if (shader) {
+ usedMask = true;
+ mCanvas->save();
+
+ auto oldMatrix = mCanvas->getLocalToDevice();
+ SkMatrix clipMatrix;
+ GfxMatrixToSkiaMatrix(aMaskTransform, clipMatrix);
+ mCanvas->concat(clipMatrix);
+
+ mCanvas->clipRect(RectToSkRect(maskBounds));
+ mCanvas->clipShader(shader);
+
+ mCanvas->setMatrix(oldMatrix);
+ } else {
+ gfxDebug() << "Failed to create Skia clip shader for PushLayerWithBlend";
+ }
+ }
+
+ PushedLayer layer(GetPermitSubpixelAA(), usedMask ? aMask : nullptr);
+ mPushedLayers.push_back(layer);
+
+ SkCanvas::SaveLayerRec saveRec(
+ aBounds.IsEmpty() ? nullptr : &bounds, &paint, nullptr,
+ SkCanvas::kPreserveLCDText_SaveLayerFlag |
+ (aCopyBackground ? SkCanvas::kInitWithPrevious_SaveLayerFlag : 0));
+
+ mCanvas->saveLayer(saveRec);
+
+ SetPermitSubpixelAA(aOpaque);
+
+#ifdef MOZ_WIDGET_COCOA
+ CGContextRelease(mCG);
+ mCG = nullptr;
+#endif
+}
+
+void DrawTargetSkia::PopLayer() {
+ MOZ_RELEASE_ASSERT(!mPushedLayers.empty());
+
+ MarkChanged();
+
+ const PushedLayer& layer = mPushedLayers.back();
+
+ mCanvas->restore();
+
+ if (layer.mMask) {
+ mCanvas->restore();
+ }
+
+ SetTransform(GetTransform());
+ SetPermitSubpixelAA(layer.mOldPermitSubpixelAA);
+
+ mPushedLayers.pop_back();
+
+#ifdef MOZ_WIDGET_COCOA
+ CGContextRelease(mCG);
+ mCG = nullptr;
+#endif
+}
+
+already_AddRefed<GradientStops> DrawTargetSkia::CreateGradientStops(
+ GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
+ std::vector<GradientStop> stops;
+ stops.resize(aNumStops);
+ for (uint32_t i = 0; i < aNumStops; i++) {
+ stops[i] = aStops[i];
+ }
+ std::stable_sort(stops.begin(), stops.end());
+
+ return MakeAndAddRef<GradientStopsSkia>(stops, aNumStops, aExtendMode);
+}
+
+already_AddRefed<FilterNode> DrawTargetSkia::CreateFilter(FilterType aType) {
+ return FilterNodeSoftware::Create(aType);
+}
+
+void DrawTargetSkia::MarkChanged() {
+ // I'm not entirely certain whether this lock is needed, as multiple threads
+ // should never modify the DrawTarget at the same time anyway, but this seems
+ // like the safest.
+ MutexAutoLock lock(mSnapshotLock);
+ if (mSnapshot) {
+ if (mSnapshot->hasOneRef()) {
+ // No owners outside of this DrawTarget's own reference. Just dump it.
+ mSnapshot = nullptr;
+ return;
+ }
+
+ mSnapshot->DrawTargetWillChange();
+ mSnapshot = nullptr;
+
+ // Handle copying of any image snapshots bound to the surface.
+ if (mSurface) {
+ mSurface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode);
+ }
+ }
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/DrawTargetSkia.h b/gfx/2d/DrawTargetSkia.h
new file mode 100644
index 0000000000..e091a9e937
--- /dev/null
+++ b/gfx/2d/DrawTargetSkia.h
@@ -0,0 +1,212 @@
+/* -*- 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_DRAWTARGETSKIA_H
+#define _MOZILLA_GFX_DRAWTARGETSKIA_H
+
+#include "2D.h"
+#include <sstream>
+#include <vector>
+
+#ifdef MOZ_WIDGET_COCOA
+# include <ApplicationServices/ApplicationServices.h>
+#endif
+
+class SkCanvas;
+class SkSurface;
+
+namespace mozilla {
+
+template <>
+class RefPtrTraits<SkSurface> {
+ public:
+ static void Release(SkSurface* aSurface);
+ static void AddRef(SkSurface* aSurface);
+};
+
+namespace gfx {
+
+class DataSourceSurface;
+class SourceSurfaceSkia;
+class BorrowedCGContext;
+
+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;
+ }
+ already_AddRefed<SourceSurface> Snapshot(SurfaceFormat aFormat);
+ virtual already_AddRefed<SourceSurface> Snapshot() override {
+ return Snapshot(mFormat);
+ }
+ already_AddRefed<SourceSurface> GetBackingSurface() override;
+ virtual IntSize GetSize() const 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 ShadowOptions& aShadow,
+ 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;
+
+ virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions()) override;
+ virtual void StrokeGlyphs(
+ ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions = StrokeOptions(),
+ const DrawOptions& aOptions = DrawOptions()) 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 bool RemoveAllClips() override;
+ virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds = IntRect(),
+ bool aCopyBackground = false) override;
+ virtual void PushLayerWithBlend(
+ bool aOpaque, Float aOpacity, SourceSurface* aMask,
+ const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(),
+ bool aCopyBackground = false,
+ CompositionOp aCompositionOp = CompositionOp::OP_OVER) 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 bool CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const override;
+ virtual RefPtr<DrawTarget> CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) 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);
+ bool Init(RefPtr<DataSourceSurface>&& aSurface);
+
+ // 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();
+ }
+
+ Maybe<IntRect> GetDeviceClipRect(bool aAllowComplex = false) const;
+
+ Maybe<Rect> GetGlyphLocalBounds(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions* aStrokeOptions,
+ const DrawOptions& aOptions);
+
+ private:
+ friend class SourceSurfaceSkia;
+
+ static void ReleaseMappedSkSurface(void* aPixels, void* aContext);
+
+ void MarkChanged();
+
+ void DrawGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions* aStrokeOptions = nullptr,
+ const DrawOptions& aOptions = DrawOptions());
+
+ struct PushedLayer {
+ PushedLayer(bool aOldPermitSubpixelAA, SourceSurface* aMask)
+ : mOldPermitSubpixelAA(aOldPermitSubpixelAA), mMask(aMask) {}
+ bool mOldPermitSubpixelAA;
+ RefPtr<SourceSurface> mMask;
+ };
+ std::vector<PushedLayer> mPushedLayers;
+
+ IntSize mSize;
+ RefPtr<SkSurface> mSurface;
+ SkCanvas* mCanvas = nullptr;
+ RefPtr<DataSourceSurface> mBackingSurface;
+ RefPtr<SourceSurfaceSkia> mSnapshot;
+ Mutex mSnapshotLock MOZ_UNANNOTATED;
+
+#ifdef MOZ_WIDGET_COCOA
+ friend class BorrowedCGContext;
+
+ CGContextRef BorrowCGContext(const DrawOptions& aOptions);
+ void ReturnCGContext(CGContextRef);
+ bool FillGlyphsWithCG(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions());
+
+ CGContextRef mCG;
+ CGColorSpaceRef mColorSpace;
+ uint8_t* mCanvasData;
+ IntSize mCGSize;
+ bool mNeedLayer;
+#endif
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_SOURCESURFACESKIA_H
diff --git a/gfx/2d/ExtendInputEffectD2D1.cpp b/gfx/2d/ExtendInputEffectD2D1.cpp
new file mode 100644
index 0000000000..1203df9a4f
--- /dev/null
+++ b/gfx/2d/ExtendInputEffectD2D1.cpp
@@ -0,0 +1,190 @@
+/* -*- 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 "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 <= float(LONG_MIN) ? LONG_MIN : LONG(aRect.x),
+ aRect.y <= float(LONG_MIN) ? LONG_MIN : LONG(aRect.y),
+ aRect.z >= float(LONG_MAX) ? LONG_MAX : LONG(aRect.z),
+ aRect.w >= float(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;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ExtendInputEffectD2D1.h b/gfx/2d/ExtendInputEffectD2D1.h
new file mode 100644
index 0000000000..47a026b940
--- /dev/null
+++ b/gfx/2d/ExtendInputEffectD2D1.h
@@ -0,0 +1,87 @@
+/* -*- 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_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;
+};
+
+} // namespace gfx
+} // namespace mozilla
+#undef SIMPLE_PROP
+
+#endif
diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp
new file mode 100644
index 0000000000..5bd65ac643
--- /dev/null
+++ b/gfx/2d/Factory.cpp
@@ -0,0 +1,1355 @@
+/* -*- 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 "2D.h"
+#include "Swizzle.h"
+
+#ifdef USE_CAIRO
+# include "DrawTargetCairo.h"
+# include "PathCairo.h"
+# include "SourceSurfaceCairo.h"
+#endif
+
+#include "DrawTargetSkia.h"
+#include "PathSkia.h"
+#include "ScaledFontBase.h"
+
+#if defined(WIN32)
+# include "ScaledFontWin.h"
+# include "NativeFontResourceGDI.h"
+# include "UnscaledFontGDI.h"
+#endif
+
+#ifdef XP_DARWIN
+# include "ScaledFontMac.h"
+# include "NativeFontResourceMac.h"
+# include "UnscaledFontMac.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+# include "ScaledFontFontconfig.h"
+# include "NativeFontResourceFreeType.h"
+# include "UnscaledFontFreeType.h"
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "ScaledFontFreeType.h"
+# include "NativeFontResourceFreeType.h"
+# include "UnscaledFontFreeType.h"
+#endif
+
+#ifdef WIN32
+# include "DrawTargetD2D1.h"
+# include "PathD2D.h"
+# include "ScaledFontDWrite.h"
+# include "NativeFontResourceDWrite.h"
+# include "UnscaledFontDWrite.h"
+# include <d3d10_1.h>
+# include <stdlib.h>
+# include "HelpersD2D.h"
+# include "DXVA2Manager.h"
+# include "mozilla/layers/TextureD3D11.h"
+# include "nsWindowsHelpers.h"
+#endif
+
+#include "DrawTargetOffset.h"
+#include "DrawTargetRecording.h"
+
+#include "SourceSurfaceRawData.h"
+
+#include "mozilla/CheckedInt.h"
+
+#ifdef MOZ_ENABLE_FREETYPE
+# include "ft2build.h"
+# include FT_FREETYPE_H
+#endif
+#include "mozilla/StaticPrefs_gfx.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)) || \
+ !defined(__SSE4__)
+// 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
+
+#ifdef MOZ_ENABLE_FREETYPE
+extern "C" {
+
+void mozilla_AddRefSharedFTFace(void* aContext) {
+ if (aContext) {
+ static_cast<mozilla::gfx::SharedFTFace*>(aContext)->AddRef();
+ }
+}
+
+void mozilla_ReleaseSharedFTFace(void* aContext, void* aOwner) {
+ if (aContext) {
+ auto* sharedFace = static_cast<mozilla::gfx::SharedFTFace*>(aContext);
+ sharedFace->ForgetLockOwner(aOwner);
+ sharedFace->Release();
+ }
+}
+
+void mozilla_ForgetSharedFTFaceLockOwner(void* aContext, void* aOwner) {
+ static_cast<mozilla::gfx::SharedFTFace*>(aContext)->ForgetLockOwner(aOwner);
+}
+
+int mozilla_LockSharedFTFace(void* aContext,
+ void* aOwner) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ return int(static_cast<mozilla::gfx::SharedFTFace*>(aContext)->Lock(aOwner));
+}
+
+void mozilla_UnlockSharedFTFace(void* aContext) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ static_cast<mozilla::gfx::SharedFTFace*>(aContext)->Unlock();
+}
+
+FT_Error mozilla_LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex,
+ int32_t aFlags) {
+ return mozilla::gfx::Factory::LoadFTGlyph(aFace, aGlyphIndex, aFlags);
+}
+
+void mozilla_LockFTLibrary(FT_Library aFTLibrary) {
+ mozilla::gfx::Factory::LockFTLibrary(aFTLibrary);
+}
+
+void mozilla_UnlockFTLibrary(FT_Library aFTLibrary) {
+ mozilla::gfx::Factory::UnlockFTLibrary(aFTLibrary);
+}
+}
+#endif
+
+namespace mozilla::gfx {
+
+#ifdef MOZ_ENABLE_FREETYPE
+FT_Library Factory::mFTLibrary = nullptr;
+StaticMutex Factory::mFTLock;
+
+already_AddRefed<SharedFTFace> FTUserFontData::CloneFace(int aFaceIndex) {
+ if (mFontData) {
+ RefPtr<SharedFTFace> face = Factory::NewSharedFTFaceFromData(
+ nullptr, mFontData, mLength, aFaceIndex, this);
+ if (!face ||
+ (FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) != FT_Err_Ok &&
+ FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL) !=
+ FT_Err_Ok)) {
+ return nullptr;
+ }
+ return face.forget();
+ }
+ FT_Face face = Factory::NewFTFace(nullptr, mFilename.c_str(), aFaceIndex);
+ if (face) {
+ return MakeAndAddRef<SharedFTFace>(face, this);
+ }
+ return nullptr;
+}
+#endif
+
+#ifdef WIN32
+// Note: mDeviceLock must be held when mutating these values.
+static uint32_t mDeviceSeq = 0;
+StaticRefPtr<ID3D11Device> Factory::mD3D11Device;
+StaticRefPtr<ID2D1Device> Factory::mD2D1Device;
+StaticRefPtr<IDWriteFactory> Factory::mDWriteFactory;
+StaticRefPtr<ID2D1DeviceContext> Factory::mMTDC;
+StaticRefPtr<ID2D1DeviceContext> Factory::mOffMTDC;
+bool Factory::mDWriteFactoryInitialized = false;
+StaticRefPtr<IDWriteFontCollection> Factory::mDWriteSystemFonts;
+StaticMutex Factory::mDeviceLock;
+StaticMutex Factory::mDTDependencyLock;
+#endif
+
+bool Factory::mBGRSubpixelOrder = false;
+
+mozilla::gfx::Config* Factory::sConfig = nullptr;
+
+void Factory::Init(const Config& aConfig) {
+ MOZ_ASSERT(!sConfig);
+ sConfig = new Config(aConfig);
+
+#ifdef XP_DARWIN
+ NativeFontResourceMac::RegisterMemoryReporter();
+#else
+ NativeFontResource::RegisterMemoryReporter();
+#endif
+}
+
+void Factory::ShutDown() {
+ if (sConfig) {
+ delete sConfig->mLogForwarder;
+ delete sConfig;
+ sConfig = nullptr;
+ }
+
+#ifdef MOZ_ENABLE_FREETYPE
+ mFTLibrary = nullptr;
+#endif
+}
+
+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
+}
+
+bool Factory::HasSSE4() {
+#if defined(__SSE4__)
+ // 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_SSE4,
+ HAS_SSE4
+ } sDetectionState = UNINITIALIZED;
+
+ if (sDetectionState == UNINITIALIZED) {
+ sDetectionState = HasCPUIDBit(1u, ecx, (1u << 19)) ? HAS_SSE4 : NO_SSE4;
+ }
+ return sDetectionState == HAS_SSE4;
+#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, kReasonableSurfaceSize);
+}
+
+bool Factory::AllowedSurfaceSize(const IntSize& aSize) {
+ if (sConfig) {
+ return Factory::CheckSurfaceSize(aSize, sConfig->mMaxTextureSize,
+ sConfig->mMaxAllocSize);
+ }
+
+ return CheckSurfaceSize(aSize);
+}
+
+bool Factory::CheckSurfaceSize(const IntSize& sz, int32_t extentLimit,
+ int32_t allocLimit) {
+ if (sz.width <= 0 || sz.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;
+ }
+
+ // 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
+ case BackendType::SKIA: {
+ RefPtr<DrawTargetSkia> newTarget;
+ newTarget = new DrawTargetSkia();
+ if (newTarget->Init(aSize, aFormat)) {
+ retVal = newTarget;
+ }
+ break;
+ }
+#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 (!retVal) {
+ // Failed
+ gfxCriticalError(LoggerOptionsBasedOnSize(aSize))
+ << "Failed to create DrawTarget, Type: " << int(aBackend)
+ << " Size: " << aSize;
+ }
+
+ return retVal.forget();
+}
+
+already_AddRefed<PathBuilder> Factory::CreatePathBuilder(BackendType aBackend,
+ FillRule aFillRule) {
+ switch (aBackend) {
+#ifdef WIN32
+ case BackendType::DIRECT2D1_1:
+ return PathBuilderD2D::Create(aFillRule);
+#endif
+ case BackendType::SKIA:
+ case BackendType::WEBGL:
+ return PathBuilderSkia::Create(aFillRule);
+#ifdef USE_CAIRO
+ case BackendType::CAIRO:
+ return PathBuilderCairo::Create(aFillRule);
+#endif
+ default:
+ gfxCriticalNote << "Invalid PathBuilder type specified: "
+ << (int)aBackend;
+ return nullptr;
+ }
+}
+
+already_AddRefed<PathBuilder> Factory::CreateSimplePathBuilder() {
+ return CreatePathBuilder(BackendType::SKIA);
+}
+
+already_AddRefed<DrawTarget> Factory::CreateRecordingDrawTarget(
+ DrawEventRecorder* aRecorder, DrawTarget* aDT, IntRect aRect) {
+ return MakeAndAddRef<DrawTargetRecording>(aRecorder, aDT, aRect);
+}
+
+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) {
+ case BackendType::SKIA: {
+ RefPtr<DrawTargetSkia> newTarget;
+ newTarget = new DrawTargetSkia();
+ if (newTarget->Init(aData, aSize, aStride, aFormat, aUninitialized)) {
+ retVal = newTarget;
+ }
+ break;
+ }
+#ifdef USE_CAIRO
+ case BackendType::CAIRO: {
+ RefPtr<DrawTargetCairo> newTarget;
+ newTarget = new DrawTargetCairo();
+ if (newTarget->Init(aData, aSize, aStride, aFormat)) {
+ retVal = std::move(newTarget);
+ }
+ break;
+ }
+#endif
+ default:
+ gfxCriticalNote << "Invalid draw target type specified: "
+ << (int)aBackend;
+ return nullptr;
+ }
+
+ 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::CreateOffsetDrawTarget(
+ DrawTarget* aDrawTarget, IntPoint aTileOrigin) {
+ RefPtr<DrawTargetOffset> dt = new DrawTargetOffset();
+
+ if (!dt->Init(aDrawTarget, aTileOrigin)) {
+ 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:
+ case BackendType::WEBRENDER_TEXT:
+ case BackendType::WEBGL:
+ 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();
+ case BackendType::SKIA:
+ return DrawTargetSkia::GetMaxSurfaceSize();
+#ifdef WIN32
+ case BackendType::DIRECT2D1_1:
+ return DrawTargetD2D1::GetMaxSurfaceSize();
+#endif
+ default:
+ return 0;
+ }
+}
+
+already_AddRefed<NativeFontResource> Factory::CreateNativeFontResource(
+ uint8_t* aData, uint32_t aSize, FontType aFontType, void* aFontContext) {
+ switch (aFontType) {
+#ifdef WIN32
+ case FontType::DWRITE:
+ return NativeFontResourceDWrite::Create(aData, aSize);
+ case FontType::GDI:
+ return NativeFontResourceGDI::Create(aData, aSize);
+#elif defined(XP_DARWIN)
+ case FontType::MAC:
+ return NativeFontResourceMac::Create(aData, aSize);
+#elif defined(MOZ_WIDGET_GTK)
+ case FontType::FONTCONFIG:
+ return NativeFontResourceFontconfig::Create(
+ aData, aSize, static_cast<FT_Library>(aFontContext));
+#elif defined(MOZ_WIDGET_ANDROID)
+ case FontType::FREETYPE:
+ return NativeFontResourceFreeType::Create(
+ aData, aSize, static_cast<FT_Library>(aFontContext));
+#endif
+ default:
+ gfxWarning()
+ << "Unable to create requested font resource from truetype data";
+ return nullptr;
+ }
+}
+
+already_AddRefed<UnscaledFont> Factory::CreateUnscaledFontFromFontDescriptor(
+ FontType aType, const uint8_t* aData, uint32_t aDataLength,
+ uint32_t aIndex) {
+ switch (aType) {
+#ifdef WIN32
+ case FontType::DWRITE:
+ return UnscaledFontDWrite::CreateFromFontDescriptor(aData, aDataLength,
+ aIndex);
+ case FontType::GDI:
+ return UnscaledFontGDI::CreateFromFontDescriptor(aData, aDataLength,
+ aIndex);
+#elif defined(XP_DARWIN)
+ case FontType::MAC:
+ return UnscaledFontMac::CreateFromFontDescriptor(aData, aDataLength,
+ aIndex);
+#elif defined(MOZ_WIDGET_GTK)
+ case FontType::FONTCONFIG:
+ return UnscaledFontFontconfig::CreateFromFontDescriptor(
+ aData, aDataLength, aIndex);
+#elif defined(MOZ_WIDGET_ANDROID)
+ case FontType::FREETYPE:
+ return UnscaledFontFreeType::CreateFromFontDescriptor(aData, aDataLength,
+ aIndex);
+#endif
+ default:
+ gfxWarning() << "Invalid type specified for UnscaledFont font descriptor";
+ return nullptr;
+ }
+}
+
+#ifdef XP_DARWIN
+already_AddRefed<ScaledFont> Factory::CreateScaledFontForMacFont(
+ CGFontRef aCGFont, const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ const DeviceColor& aFontSmoothingBackgroundColor, bool aUseFontSmoothing,
+ bool aApplySyntheticBold, bool aHasColorGlyphs) {
+ return MakeAndAddRef<ScaledFontMac>(
+ aCGFont, aUnscaledFont, aSize, false, aFontSmoothingBackgroundColor,
+ aUseFontSmoothing, aApplySyntheticBold, aHasColorGlyphs);
+}
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+already_AddRefed<ScaledFont> Factory::CreateScaledFontForFontconfigFont(
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ RefPtr<SharedFTFace> aFace, FcPattern* aPattern) {
+ return MakeAndAddRef<ScaledFontFontconfig>(std::move(aFace), aPattern,
+ aUnscaledFont, aSize);
+}
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+already_AddRefed<ScaledFont> Factory::CreateScaledFontForFreeTypeFont(
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ RefPtr<SharedFTFace> aFace, bool aApplySyntheticBold) {
+ return MakeAndAddRef<ScaledFontFreeType>(std::move(aFace), aUnscaledFont,
+ aSize, aApplySyntheticBold);
+}
+#endif
+
+void Factory::SetBGRSubpixelOrder(bool aBGR) { mBGRSubpixelOrder = aBGR; }
+
+bool Factory::GetBGRSubpixelOrder() { return mBGRSubpixelOrder; }
+
+#ifdef MOZ_ENABLE_FREETYPE
+SharedFTFace::SharedFTFace(FT_Face aFace, SharedFTFaceData* aData)
+ : mFace(aFace),
+ mData(aData),
+ mLock("SharedFTFace::mLock"),
+ mLastLockOwner(nullptr) {
+ if (mData) {
+ mData->BindData();
+ }
+}
+
+SharedFTFace::~SharedFTFace() {
+ Factory::ReleaseFTFace(mFace);
+ if (mData) {
+ mData->ReleaseData();
+ }
+}
+
+void Factory::SetFTLibrary(FT_Library aFTLibrary) { mFTLibrary = aFTLibrary; }
+
+FT_Library Factory::GetFTLibrary() {
+ MOZ_ASSERT(mFTLibrary);
+ return mFTLibrary;
+}
+
+FT_Library Factory::NewFTLibrary() {
+ FT_Library library;
+ if (FT_Init_FreeType(&library) != FT_Err_Ok) {
+ return nullptr;
+ }
+ return library;
+}
+
+void Factory::ReleaseFTLibrary(FT_Library aFTLibrary) {
+ FT_Done_FreeType(aFTLibrary);
+}
+
+void Factory::LockFTLibrary(FT_Library aFTLibrary)
+ MOZ_CAPABILITY_ACQUIRE(mFTLock) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mFTLock.Lock();
+}
+
+void Factory::UnlockFTLibrary(FT_Library aFTLibrary)
+ MOZ_CAPABILITY_RELEASE(mFTLock) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mFTLock.Unlock();
+}
+
+FT_Face Factory::NewFTFace(FT_Library aFTLibrary, const char* aFileName,
+ int aFaceIndex) {
+ StaticMutexAutoLock lock(mFTLock);
+ if (!aFTLibrary) {
+ aFTLibrary = mFTLibrary;
+ }
+ FT_Face face;
+ if (FT_New_Face(aFTLibrary, aFileName, aFaceIndex, &face) != FT_Err_Ok) {
+ return nullptr;
+ }
+ return face;
+}
+
+already_AddRefed<SharedFTFace> Factory::NewSharedFTFace(FT_Library aFTLibrary,
+ const char* aFilename,
+ int aFaceIndex) {
+ FT_Face face = NewFTFace(aFTLibrary, aFilename, aFaceIndex);
+ if (!face) {
+ return nullptr;
+ }
+
+ RefPtr<FTUserFontData> data;
+# ifdef ANDROID
+ // If the font has variations, we may later need to "clone" it in
+ // UnscaledFontFreeType::CreateScaledFont. To support this, we attach an
+ // FTUserFontData that records the filename used to instantiate the face.
+ if (face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
+ data = new FTUserFontData(aFilename);
+ }
+# endif
+ return MakeAndAddRef<SharedFTFace>(face, data);
+}
+
+FT_Face Factory::NewFTFaceFromData(FT_Library aFTLibrary, const uint8_t* aData,
+ size_t aDataSize, int aFaceIndex) {
+ StaticMutexAutoLock lock(mFTLock);
+ if (!aFTLibrary) {
+ aFTLibrary = mFTLibrary;
+ }
+ FT_Face face;
+ if (FT_New_Memory_Face(aFTLibrary, aData, aDataSize, aFaceIndex, &face) !=
+ FT_Err_Ok) {
+ return nullptr;
+ }
+ return face;
+}
+
+already_AddRefed<SharedFTFace> Factory::NewSharedFTFaceFromData(
+ FT_Library aFTLibrary, const uint8_t* aData, size_t aDataSize,
+ int aFaceIndex, SharedFTFaceData* aSharedData) {
+ if (FT_Face face =
+ NewFTFaceFromData(aFTLibrary, aData, aDataSize, aFaceIndex)) {
+ return MakeAndAddRef<SharedFTFace>(face, aSharedData);
+ } else {
+ return nullptr;
+ }
+}
+
+void Factory::ReleaseFTFace(FT_Face aFace) {
+ StaticMutexAutoLock lock(mFTLock);
+ FT_Done_Face(aFace);
+}
+
+FT_Error Factory::LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex,
+ int32_t aFlags) {
+ StaticMutexAutoLock lock(mFTLock);
+ return FT_Load_Glyph(aFace, aGlyphIndex, aFlags);
+}
+#endif
+
+AutoSerializeWithMoz2D::AutoSerializeWithMoz2D(BackendType aBackendType) {
+#ifdef WIN32
+ // We use a multi-threaded ID2D1Factory1, so that makes the calls through the
+ // Direct2D API thread-safe. However, if the Moz2D objects are using Direct3D
+ // resources we need to make sure that calls through the Direct3D or DXGI API
+ // use the Direct2D synchronization. It's possible that this should be pushed
+ // down into the TextureD3D11 objects, so that we always use this.
+ if (aBackendType == BackendType::DIRECT2D1_1 ||
+ aBackendType == BackendType::DIRECT2D) {
+ auto factory = D2DFactory();
+ if (factory) {
+ factory->QueryInterface(
+ static_cast<ID2D1Multithread**>(getter_AddRefs(mMT)));
+ if (mMT) {
+ mMT->Enter();
+ }
+ }
+ }
+#endif
+}
+
+AutoSerializeWithMoz2D::~AutoSerializeWithMoz2D() {
+#ifdef WIN32
+ if (mMT) {
+ mMT->Leave();
+ }
+#endif
+};
+
+#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;
+ return retVal.forget();
+ }
+
+ gfxWarning() << "Failed to create draw target for D3D11 texture.";
+
+ // Failed
+ return nullptr;
+}
+
+bool Factory::SetDirect3D11Device(ID3D11Device* aDevice) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ // D2DFactory already takes the device lock, so we get the factory before
+ // entering the lock scope.
+ RefPtr<ID2D1Factory1> factory = D2DFactory();
+
+ StaticMutexAutoLock lock(mDeviceLock);
+
+ mD3D11Device = aDevice;
+
+ if (mD2D1Device) {
+ mD2D1Device = nullptr;
+ mMTDC = nullptr;
+ mOffMTDC = nullptr;
+ }
+
+ if (!aDevice) {
+ return true;
+ }
+
+ RefPtr<IDXGIDevice> device;
+ aDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(device));
+
+ RefPtr<ID2D1Device> d2dDevice;
+ HRESULT hr = factory->CreateDevice(device, getter_AddRefs(d2dDevice));
+ if (FAILED(hr)) {
+ gfxCriticalError()
+ << "[D2D1] Failed to create gfx factory's D2D1 device, code: "
+ << hexa(hr);
+
+ mD3D11Device = nullptr;
+ return false;
+ }
+
+ mDeviceSeq++;
+ mD2D1Device = d2dDevice;
+ return true;
+}
+
+RefPtr<ID3D11Device> Factory::GetDirect3D11Device() {
+ StaticMutexAutoLock lock(mDeviceLock);
+ return mD3D11Device;
+}
+
+RefPtr<ID2D1Device> Factory::GetD2D1Device(uint32_t* aOutSeqNo) {
+ StaticMutexAutoLock lock(mDeviceLock);
+ if (aOutSeqNo) {
+ *aOutSeqNo = mDeviceSeq;
+ }
+ return mD2D1Device.get();
+}
+
+bool Factory::HasD2D1Device() { return !!GetD2D1Device(); }
+
+RefPtr<IDWriteFactory> Factory::GetDWriteFactory() {
+ StaticMutexAutoLock lock(mDeviceLock);
+ return mDWriteFactory;
+}
+
+RefPtr<IDWriteFactory> Factory::EnsureDWriteFactory() {
+ StaticMutexAutoLock lock(mDeviceLock);
+
+ if (mDWriteFactoryInitialized) {
+ return mDWriteFactory;
+ }
+
+ mDWriteFactoryInitialized = true;
+
+ HMODULE dwriteModule = LoadLibrarySystem32(L"dwrite.dll");
+ decltype(DWriteCreateFactory)* createDWriteFactory =
+ (decltype(DWriteCreateFactory)*)GetProcAddress(dwriteModule,
+ "DWriteCreateFactory");
+
+ if (!createDWriteFactory) {
+ gfxWarning() << "Failed to locate DWriteCreateFactory function.";
+ return nullptr;
+ }
+
+ HRESULT hr =
+ createDWriteFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(&mDWriteFactory));
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to create DWrite Factory.";
+ }
+
+ return mDWriteFactory;
+}
+
+RefPtr<IDWriteFontCollection> Factory::GetDWriteSystemFonts(bool aUpdate) {
+ StaticMutexAutoLock lock(mDeviceLock);
+
+ if (mDWriteSystemFonts && !aUpdate) {
+ return mDWriteSystemFonts;
+ }
+
+ if (!mDWriteFactory) {
+ if ((rand() & 0x3f) == 0) {
+ gfxCriticalError(int(gfx::LogOptions::AssertOnCall))
+ << "Failed to create DWrite factory";
+ } else {
+ gfxWarning() << "Failed to create DWrite factory";
+ }
+
+ return nullptr;
+ }
+
+ RefPtr<IDWriteFontCollection> systemFonts;
+ HRESULT hr =
+ mDWriteFactory->GetSystemFontCollection(getter_AddRefs(systemFonts));
+ if (FAILED(hr) || !systemFonts) {
+ // only crash some of the time so those experiencing this problem
+ // don't stop using Firefox
+ if ((rand() & 0x3f) == 0) {
+ gfxCriticalError(int(gfx::LogOptions::AssertOnCall))
+ << "Failed to create DWrite system font collection";
+ } else {
+ gfxWarning() << "Failed to create DWrite system font collection";
+ }
+ return nullptr;
+ }
+ mDWriteSystemFonts = systemFonts;
+
+ return mDWriteSystemFonts;
+}
+
+RefPtr<ID2D1DeviceContext> Factory::GetD2DDeviceContext() {
+ StaticRefPtr<ID2D1DeviceContext>* ptr;
+
+ if (NS_IsMainThread()) {
+ ptr = &mMTDC;
+ } else {
+ ptr = &mOffMTDC;
+ }
+
+ if (*ptr) {
+ return *ptr;
+ }
+
+ RefPtr<ID2D1Device> device = GetD2D1Device();
+
+ if (!device) {
+ return nullptr;
+ }
+
+ RefPtr<ID2D1DeviceContext> dc;
+ HRESULT hr = device->CreateDeviceContext(
+ D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
+ getter_AddRefs(dc));
+
+ if (FAILED(hr)) {
+ gfxCriticalError() << "Failed to create global device context";
+ return nullptr;
+ }
+
+ *ptr = dc;
+
+ return *ptr;
+}
+
+bool Factory::SupportsD2D1() { return !!D2DFactory(); }
+
+BYTE sSystemTextQuality = CLEARTYPE_QUALITY;
+void Factory::SetSystemTextQuality(uint8_t aQuality) {
+ sSystemTextQuality = aQuality;
+}
+
+uint64_t Factory::GetD2DVRAMUsageDrawTarget() {
+ return DrawTargetD2D1::mVRAMUsageDT;
+}
+
+uint64_t Factory::GetD2DVRAMUsageSourceSurface() {
+ return DrawTargetD2D1::mVRAMUsageSS;
+}
+
+void Factory::D2DCleanup() {
+ StaticMutexAutoLock lock(mDeviceLock);
+ if (mD2D1Device) {
+ mD2D1Device = nullptr;
+ }
+ DrawTargetD2D1::CleanupD2D();
+}
+
+already_AddRefed<ScaledFont> Factory::CreateScaledFontForDWriteFont(
+ IDWriteFontFace* aFontFace, const gfxFontStyle* aStyle,
+ const RefPtr<UnscaledFont>& aUnscaledFont, float aSize,
+ bool aUseEmbeddedBitmap, bool aUseMultistrikeBold, bool aGDIForced) {
+ return MakeAndAddRef<ScaledFontDWrite>(
+ aFontFace, aUnscaledFont, aSize, aUseEmbeddedBitmap, aUseMultistrikeBold,
+ aGDIForced, aStyle);
+}
+
+already_AddRefed<ScaledFont> Factory::CreateScaledFontForGDIFont(
+ const void* aLogFont, const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize) {
+ return MakeAndAddRef<ScaledFontWin>(static_cast<const LOGFONT*>(aLogFont),
+ aUnscaledFont, aSize);
+}
+#endif // WIN32
+
+already_AddRefed<DrawTarget> Factory::CreateDrawTargetWithSkCanvas(
+ SkCanvas* aCanvas) {
+ RefPtr<DrawTargetSkia> newTarget = new DrawTargetSkia();
+ if (!newTarget->Init(aCanvas)) {
+ return nullptr;
+ }
+ return newTarget.forget();
+}
+
+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;
+ }
+#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();
+}
+
+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;
+}
+
+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);
+
+ 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;
+ }
+
+ SwizzleData(srcMap.mData, srcMap.mStride, aSource->GetFormat(), destMap.mData,
+ destMap.mStride, aDest->GetFormat(), aSource->GetSize());
+
+ aSource->Unmap();
+ aDest->Unmap();
+}
+
+#ifdef WIN32
+
+/* static */
+already_AddRefed<DataSourceSurface>
+Factory::CreateBGRA8DataSourceSurfaceForD3D11Texture(
+ ID3D11Texture2D* aSrcTexture, uint32_t aArrayIndex) {
+ D3D11_TEXTURE2D_DESC srcDesc = {0};
+ aSrcTexture->GetDesc(&srcDesc);
+
+ RefPtr<gfx::DataSourceSurface> destTexture =
+ gfx::Factory::CreateDataSourceSurface(
+ IntSize(srcDesc.Width, srcDesc.Height), gfx::SurfaceFormat::B8G8R8A8);
+ if (NS_WARN_IF(!destTexture)) {
+ return nullptr;
+ }
+ if (!ReadbackTexture(destTexture, aSrcTexture, aArrayIndex)) {
+ return nullptr;
+ }
+ return destTexture.forget();
+}
+
+/* static */
+template <typename DestTextureT>
+bool Factory::ConvertSourceAndRetryReadback(DestTextureT* aDestCpuTexture,
+ ID3D11Texture2D* aSrcTexture,
+ uint32_t aArrayIndex) {
+ RefPtr<ID3D11Device> device;
+ aSrcTexture->GetDevice(getter_AddRefs(device));
+ if (!device) {
+ gfxWarning() << "Failed to get D3D11 device from source texture";
+ return false;
+ }
+
+ nsAutoCString error;
+ std::unique_ptr<DXVA2Manager> manager(
+ DXVA2Manager::CreateD3D11DXVA(nullptr, error, device));
+ if (!manager) {
+ gfxWarning() << "Failed to create DXVA2 manager!";
+ return false;
+ }
+
+ RefPtr<ID3D11Texture2D> newSrcTexture;
+ HRESULT hr = manager->CopyToBGRATexture(aSrcTexture, aArrayIndex,
+ getter_AddRefs(newSrcTexture));
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to copy to BGRA texture.";
+ return false;
+ }
+
+ return ReadbackTexture(aDestCpuTexture, newSrcTexture);
+}
+
+/* static */
+bool Factory::ReadbackTexture(layers::TextureData* aDestCpuTexture,
+ ID3D11Texture2D* aSrcTexture) {
+ layers::MappedTextureData mappedData;
+ if (!aDestCpuTexture->BorrowMappedData(mappedData)) {
+ gfxWarning() << "Could not access in-memory texture";
+ return false;
+ }
+
+ D3D11_TEXTURE2D_DESC srcDesc = {0};
+ aSrcTexture->GetDesc(&srcDesc);
+
+ // Special case: If the source and destination have different formats and the
+ // destination is B8G8R8A8 then convert the source to B8G8R8A8 and readback.
+ if ((srcDesc.Format != DXGIFormat(mappedData.format)) &&
+ (mappedData.format == SurfaceFormat::B8G8R8A8)) {
+ return ConvertSourceAndRetryReadback(aDestCpuTexture, aSrcTexture);
+ }
+
+ if ((IntSize(srcDesc.Width, srcDesc.Height) != mappedData.size) ||
+ (srcDesc.Format != DXGIFormat(mappedData.format))) {
+ gfxWarning() << "Attempted readback between incompatible textures";
+ return false;
+ }
+
+ return ReadbackTexture(mappedData.data, mappedData.stride, aSrcTexture);
+}
+
+/* static */
+bool Factory::ReadbackTexture(DataSourceSurface* aDestCpuTexture,
+ ID3D11Texture2D* aSrcTexture,
+ uint32_t aArrayIndex) {
+ D3D11_TEXTURE2D_DESC srcDesc = {0};
+ aSrcTexture->GetDesc(&srcDesc);
+
+ // Special case: If the source and destination have different formats and the
+ // destination is B8G8R8A8 then convert the source to B8G8R8A8 and readback.
+ if ((srcDesc.Format != DXGIFormat(aDestCpuTexture->GetFormat())) &&
+ (aDestCpuTexture->GetFormat() == SurfaceFormat::B8G8R8A8)) {
+ return ConvertSourceAndRetryReadback(aDestCpuTexture, aSrcTexture,
+ aArrayIndex);
+ }
+
+ if ((IntSize(srcDesc.Width, srcDesc.Height) != aDestCpuTexture->GetSize()) ||
+ (srcDesc.Format != DXGIFormat(aDestCpuTexture->GetFormat()))) {
+ gfxWarning() << "Attempted readback between incompatible textures";
+ return false;
+ }
+
+ gfx::DataSourceSurface::MappedSurface mappedSurface;
+ if (!aDestCpuTexture->Map(gfx::DataSourceSurface::WRITE, &mappedSurface)) {
+ return false;
+ }
+
+ MOZ_ASSERT(aArrayIndex == 0);
+
+ bool ret =
+ ReadbackTexture(mappedSurface.mData, mappedSurface.mStride, aSrcTexture);
+ aDestCpuTexture->Unmap();
+ return ret;
+}
+
+/* static */
+bool Factory::ReadbackTexture(uint8_t* aDestData, int32_t aDestStride,
+ ID3D11Texture2D* aSrcTexture) {
+ MOZ_ASSERT(aDestData && aDestStride && aSrcTexture);
+
+ RefPtr<ID3D11Device> device;
+ aSrcTexture->GetDevice(getter_AddRefs(device));
+ if (!device) {
+ gfxWarning() << "Failed to get D3D11 device from source texture";
+ return false;
+ }
+
+ RefPtr<ID3D11DeviceContext> context;
+ device->GetImmediateContext(getter_AddRefs(context));
+ if (!context) {
+ gfxWarning() << "Could not get an immediate D3D11 context";
+ return false;
+ }
+
+ RefPtr<IDXGIKeyedMutex> mutex;
+ HRESULT hr = aSrcTexture->QueryInterface(__uuidof(IDXGIKeyedMutex),
+ (void**)getter_AddRefs(mutex));
+ if (SUCCEEDED(hr) && mutex) {
+ hr = mutex->AcquireSync(0, 2000);
+ if (hr != S_OK) {
+ gfxWarning() << "Could not acquire DXGI surface lock in 2 seconds";
+ return false;
+ }
+ }
+
+ D3D11_TEXTURE2D_DESC srcDesc = {0};
+ aSrcTexture->GetDesc(&srcDesc);
+ srcDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ srcDesc.Usage = D3D11_USAGE_STAGING;
+ srcDesc.BindFlags = 0;
+ srcDesc.MiscFlags = 0;
+ srcDesc.MipLevels = 1;
+ RefPtr<ID3D11Texture2D> srcCpuTexture;
+ hr =
+ device->CreateTexture2D(&srcDesc, nullptr, getter_AddRefs(srcCpuTexture));
+ if (FAILED(hr)) {
+ gfxWarning() << "Could not create source texture for mapping";
+ if (mutex) {
+ mutex->ReleaseSync(0);
+ }
+ return false;
+ }
+
+ context->CopyResource(srcCpuTexture, aSrcTexture);
+
+ if (mutex) {
+ mutex->ReleaseSync(0);
+ mutex = nullptr;
+ }
+
+ D3D11_MAPPED_SUBRESOURCE srcMap;
+ hr = context->Map(srcCpuTexture, 0, D3D11_MAP_READ, 0, &srcMap);
+ if (FAILED(hr)) {
+ gfxWarning() << "Could not map source texture";
+ return false;
+ }
+
+ uint32_t width = srcDesc.Width;
+ uint32_t height = srcDesc.Height;
+ int bpp = BytesPerPixel(gfx::ToPixelFormat(srcDesc.Format));
+ for (uint32_t y = 0; y < height; y++) {
+ memcpy(aDestData + aDestStride * y,
+ (unsigned char*)(srcMap.pData) + srcMap.RowPitch * y, width * bpp);
+ }
+
+ context->Unmap(srcCpuTexture, 0);
+ return true;
+}
+
+#endif // WIN32
+
+// 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);
+ }
+}
+
+#ifdef WIN32
+void LogWStr(const wchar_t* aWStr, std::stringstream& aOut) {
+ int n =
+ WideCharToMultiByte(CP_ACP, 0, aWStr, -1, nullptr, 0, nullptr, nullptr);
+ if (n > 1) {
+ std::vector<char> str(n);
+ WideCharToMultiByte(CP_ACP, 0, aWStr, -1, str.data(), n, nullptr, nullptr);
+ aOut << str.data();
+ }
+}
+#endif
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/FilterNodeD2D1.cpp b/gfx/2d/FilterNodeD2D1.cpp
new file mode 100644
index 0000000000..bc9026a8b8
--- /dev/null
+++ b/gfx/2d/FilterNodeD2D1.cpp
@@ -0,0 +1,1140 @@
+/* -*- 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 "FilterNodeD2D1.h"
+
+#include "Logging.h"
+
+#include "SourceSurfaceD2D1.h"
+#include "DrawTargetD2D1.h"
+#include "ExtendInputEffectD2D1.h"
+
+namespace mozilla {
+namespace gfx {
+
+D2D1_COLORMATRIX_ALPHA_MODE D2DAlphaMode(uint32_t aMode) {
+ switch (aMode) {
+ case ALPHA_MODE_PREMULTIPLIED:
+ return D2D1_COLORMATRIX_ALPHA_MODE_PREMULTIPLIED;
+ case ALPHA_MODE_STRAIGHT:
+ return D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT;
+ default:
+ MOZ_CRASH("GFX: Unknown enum value D2DAlphaMode!");
+ }
+
+ return D2D1_COLORMATRIX_ALPHA_MODE_PREMULTIPLIED;
+}
+
+D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE D2DAffineTransformInterpolationMode(
+ SamplingFilter aSamplingFilter) {
+ switch (aSamplingFilter) {
+ case SamplingFilter::GOOD:
+ return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_LINEAR;
+ case SamplingFilter::LINEAR:
+ return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_LINEAR;
+ case SamplingFilter::POINT:
+ return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
+ default:
+ MOZ_CRASH("GFX: Unknown enum value D2DAffineTIM!");
+ }
+
+ return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_LINEAR;
+}
+
+D2D1_BLEND_MODE D2DBlendMode(uint32_t aMode) {
+ switch (aMode) {
+ case BLEND_MODE_DARKEN:
+ return D2D1_BLEND_MODE_DARKEN;
+ case BLEND_MODE_LIGHTEN:
+ return D2D1_BLEND_MODE_LIGHTEN;
+ case BLEND_MODE_MULTIPLY:
+ return D2D1_BLEND_MODE_MULTIPLY;
+ case BLEND_MODE_SCREEN:
+ return D2D1_BLEND_MODE_SCREEN;
+ case BLEND_MODE_OVERLAY:
+ return D2D1_BLEND_MODE_OVERLAY;
+ case BLEND_MODE_COLOR_DODGE:
+ return D2D1_BLEND_MODE_COLOR_DODGE;
+ case BLEND_MODE_COLOR_BURN:
+ return D2D1_BLEND_MODE_COLOR_BURN;
+ case BLEND_MODE_HARD_LIGHT:
+ return D2D1_BLEND_MODE_HARD_LIGHT;
+ case BLEND_MODE_SOFT_LIGHT:
+ return D2D1_BLEND_MODE_SOFT_LIGHT;
+ case BLEND_MODE_DIFFERENCE:
+ return D2D1_BLEND_MODE_DIFFERENCE;
+ case BLEND_MODE_EXCLUSION:
+ return D2D1_BLEND_MODE_EXCLUSION;
+ case BLEND_MODE_HUE:
+ return D2D1_BLEND_MODE_HUE;
+ case BLEND_MODE_SATURATION:
+ return D2D1_BLEND_MODE_SATURATION;
+ case BLEND_MODE_COLOR:
+ return D2D1_BLEND_MODE_COLOR;
+ case BLEND_MODE_LUMINOSITY:
+ return D2D1_BLEND_MODE_LUMINOSITY;
+
+ default:
+ MOZ_CRASH("GFX: Unknown enum value D2DBlendMode!");
+ }
+
+ return D2D1_BLEND_MODE_DARKEN;
+}
+
+D2D1_MORPHOLOGY_MODE D2DMorphologyMode(uint32_t aMode) {
+ switch (aMode) {
+ case MORPHOLOGY_OPERATOR_DILATE:
+ return D2D1_MORPHOLOGY_MODE_DILATE;
+ case MORPHOLOGY_OPERATOR_ERODE:
+ return D2D1_MORPHOLOGY_MODE_ERODE;
+ }
+
+ MOZ_CRASH("GFX: Unknown enum value D2DMorphologyMode!");
+ return D2D1_MORPHOLOGY_MODE_DILATE;
+}
+
+D2D1_TURBULENCE_NOISE D2DTurbulenceNoise(uint32_t aMode) {
+ switch (aMode) {
+ case TURBULENCE_TYPE_FRACTAL_NOISE:
+ return D2D1_TURBULENCE_NOISE_FRACTAL_SUM;
+ case TURBULENCE_TYPE_TURBULENCE:
+ return D2D1_TURBULENCE_NOISE_TURBULENCE;
+ }
+
+ MOZ_CRASH("GFX: Unknown enum value D2DTurbulenceNoise!");
+ return D2D1_TURBULENCE_NOISE_TURBULENCE;
+}
+
+D2D1_COMPOSITE_MODE D2DFilterCompositionMode(uint32_t aMode) {
+ switch (aMode) {
+ case COMPOSITE_OPERATOR_OVER:
+ return D2D1_COMPOSITE_MODE_SOURCE_OVER;
+ case COMPOSITE_OPERATOR_IN:
+ return D2D1_COMPOSITE_MODE_SOURCE_IN;
+ case COMPOSITE_OPERATOR_OUT:
+ return D2D1_COMPOSITE_MODE_SOURCE_OUT;
+ case COMPOSITE_OPERATOR_ATOP:
+ return D2D1_COMPOSITE_MODE_SOURCE_ATOP;
+ case COMPOSITE_OPERATOR_XOR:
+ return D2D1_COMPOSITE_MODE_XOR;
+ case COMPOSITE_OPERATOR_LIGHTER:
+ return D2D1_COMPOSITE_MODE_PLUS;
+ }
+
+ MOZ_CRASH("GFX: Unknown enum value D2DFilterCompositionMode!");
+ return D2D1_COMPOSITE_MODE_SOURCE_OVER;
+}
+
+D2D1_CHANNEL_SELECTOR D2DChannelSelector(uint32_t aMode) {
+ switch (aMode) {
+ case COLOR_CHANNEL_R:
+ return D2D1_CHANNEL_SELECTOR_R;
+ case COLOR_CHANNEL_G:
+ return D2D1_CHANNEL_SELECTOR_G;
+ case COLOR_CHANNEL_B:
+ return D2D1_CHANNEL_SELECTOR_B;
+ case COLOR_CHANNEL_A:
+ return D2D1_CHANNEL_SELECTOR_A;
+ }
+
+ MOZ_CRASH("GFX: Unknown enum value D2DChannelSelector!");
+ return D2D1_CHANNEL_SELECTOR_R;
+}
+
+already_AddRefed<ID2D1Image> GetImageForSourceSurface(DrawTarget* aDT,
+ SourceSurface* aSurface) {
+ if (aDT->IsTiledDrawTarget()) {
+ gfxDevCrash(LogReason::FilterNodeD2D1Target)
+ << "Incompatible draw target type! " << (int)aDT->IsTiledDrawTarget();
+ return nullptr;
+ }
+ switch (aDT->GetBackendType()) {
+ case BackendType::DIRECT2D1_1:
+ return static_cast<DrawTargetD2D1*>(aDT)->GetImageForSurface(
+ aSurface, ExtendMode::CLAMP);
+ default:
+ gfxDevCrash(LogReason::FilterNodeD2D1Backend)
+ << "Unknown draw target type! " << (int)aDT->GetBackendType();
+ return nullptr;
+ }
+}
+
+uint32_t ConvertValue(FilterType aType, uint32_t aAttribute, uint32_t aValue) {
+ switch (aType) {
+ case FilterType::COLOR_MATRIX:
+ if (aAttribute == ATT_COLOR_MATRIX_ALPHA_MODE) {
+ aValue = D2DAlphaMode(aValue);
+ }
+ break;
+ case FilterType::TRANSFORM:
+ if (aAttribute == ATT_TRANSFORM_FILTER) {
+ aValue = D2DAffineTransformInterpolationMode(SamplingFilter(aValue));
+ }
+ break;
+ case FilterType::BLEND:
+ if (aAttribute == ATT_BLEND_BLENDMODE) {
+ aValue = D2DBlendMode(aValue);
+ }
+ break;
+ case FilterType::MORPHOLOGY:
+ if (aAttribute == ATT_MORPHOLOGY_OPERATOR) {
+ aValue = D2DMorphologyMode(aValue);
+ }
+ break;
+ case FilterType::DISPLACEMENT_MAP:
+ if (aAttribute == ATT_DISPLACEMENT_MAP_X_CHANNEL ||
+ aAttribute == ATT_DISPLACEMENT_MAP_Y_CHANNEL) {
+ aValue = D2DChannelSelector(aValue);
+ }
+ break;
+ case FilterType::TURBULENCE:
+ if (aAttribute == ATT_TURBULENCE_TYPE) {
+ aValue = D2DTurbulenceNoise(aValue);
+ }
+ break;
+ case FilterType::COMPOSITE:
+ if (aAttribute == ATT_COMPOSITE_OPERATOR) {
+ aValue = D2DFilterCompositionMode(aValue);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return aValue;
+}
+
+void ConvertValue(FilterType aType, uint32_t aAttribute, IntSize& aValue) {
+ switch (aType) {
+ case FilterType::MORPHOLOGY:
+ if (aAttribute == ATT_MORPHOLOGY_RADII) {
+ aValue.width *= 2;
+ aValue.width += 1;
+ aValue.height *= 2;
+ aValue.height += 1;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+UINT32
+GetD2D1InputForInput(FilterType aType, uint32_t aIndex) { return aIndex; }
+
+#define CONVERT_PROP(moz2dname, d2dname) \
+ case ATT_##moz2dname: \
+ return D2D1_##d2dname
+
+UINT32
+GetD2D1PropForAttribute(FilterType aType, uint32_t aIndex) {
+ switch (aType) {
+ case FilterType::COLOR_MATRIX:
+ switch (aIndex) {
+ CONVERT_PROP(COLOR_MATRIX_MATRIX, COLORMATRIX_PROP_COLOR_MATRIX);
+ CONVERT_PROP(COLOR_MATRIX_ALPHA_MODE, COLORMATRIX_PROP_ALPHA_MODE);
+ }
+ break;
+ case FilterType::TRANSFORM:
+ switch (aIndex) {
+ CONVERT_PROP(TRANSFORM_MATRIX, 2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX);
+ CONVERT_PROP(TRANSFORM_FILTER,
+ 2DAFFINETRANSFORM_PROP_INTERPOLATION_MODE);
+ }
+ case FilterType::BLEND:
+ switch (aIndex) { CONVERT_PROP(BLEND_BLENDMODE, BLEND_PROP_MODE); }
+ break;
+ case FilterType::MORPHOLOGY:
+ switch (aIndex) {
+ CONVERT_PROP(MORPHOLOGY_OPERATOR, MORPHOLOGY_PROP_MODE);
+ }
+ break;
+ case FilterType::FLOOD:
+ switch (aIndex) { CONVERT_PROP(FLOOD_COLOR, FLOOD_PROP_COLOR); }
+ break;
+ case FilterType::TILE:
+ switch (aIndex) { CONVERT_PROP(TILE_SOURCE_RECT, TILE_PROP_RECT); }
+ break;
+ case FilterType::TABLE_TRANSFER:
+ switch (aIndex) {
+ CONVERT_PROP(TABLE_TRANSFER_DISABLE_R, TABLETRANSFER_PROP_RED_DISABLE);
+ CONVERT_PROP(TABLE_TRANSFER_DISABLE_G,
+ TABLETRANSFER_PROP_GREEN_DISABLE);
+ CONVERT_PROP(TABLE_TRANSFER_DISABLE_B, TABLETRANSFER_PROP_BLUE_DISABLE);
+ CONVERT_PROP(TABLE_TRANSFER_DISABLE_A,
+ TABLETRANSFER_PROP_ALPHA_DISABLE);
+ CONVERT_PROP(TABLE_TRANSFER_TABLE_R, TABLETRANSFER_PROP_RED_TABLE);
+ CONVERT_PROP(TABLE_TRANSFER_TABLE_G, TABLETRANSFER_PROP_GREEN_TABLE);
+ CONVERT_PROP(TABLE_TRANSFER_TABLE_B, TABLETRANSFER_PROP_BLUE_TABLE);
+ CONVERT_PROP(TABLE_TRANSFER_TABLE_A, TABLETRANSFER_PROP_ALPHA_TABLE);
+ }
+ break;
+ case FilterType::DISCRETE_TRANSFER:
+ switch (aIndex) {
+ CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_R,
+ DISCRETETRANSFER_PROP_RED_DISABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_G,
+ DISCRETETRANSFER_PROP_GREEN_DISABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_B,
+ DISCRETETRANSFER_PROP_BLUE_DISABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_A,
+ DISCRETETRANSFER_PROP_ALPHA_DISABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_TABLE_R,
+ DISCRETETRANSFER_PROP_RED_TABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_TABLE_G,
+ DISCRETETRANSFER_PROP_GREEN_TABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_TABLE_B,
+ DISCRETETRANSFER_PROP_BLUE_TABLE);
+ CONVERT_PROP(DISCRETE_TRANSFER_TABLE_A,
+ DISCRETETRANSFER_PROP_ALPHA_TABLE);
+ }
+ break;
+ case FilterType::LINEAR_TRANSFER:
+ switch (aIndex) {
+ CONVERT_PROP(LINEAR_TRANSFER_DISABLE_R,
+ LINEARTRANSFER_PROP_RED_DISABLE);
+ CONVERT_PROP(LINEAR_TRANSFER_DISABLE_G,
+ LINEARTRANSFER_PROP_GREEN_DISABLE);
+ CONVERT_PROP(LINEAR_TRANSFER_DISABLE_B,
+ LINEARTRANSFER_PROP_BLUE_DISABLE);
+ CONVERT_PROP(LINEAR_TRANSFER_DISABLE_A,
+ LINEARTRANSFER_PROP_ALPHA_DISABLE);
+ CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_R,
+ LINEARTRANSFER_PROP_RED_Y_INTERCEPT);
+ CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_G,
+ LINEARTRANSFER_PROP_GREEN_Y_INTERCEPT);
+ CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_B,
+ LINEARTRANSFER_PROP_BLUE_Y_INTERCEPT);
+ CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_A,
+ LINEARTRANSFER_PROP_ALPHA_Y_INTERCEPT);
+ CONVERT_PROP(LINEAR_TRANSFER_SLOPE_R, LINEARTRANSFER_PROP_RED_SLOPE);
+ CONVERT_PROP(LINEAR_TRANSFER_SLOPE_G, LINEARTRANSFER_PROP_GREEN_SLOPE);
+ CONVERT_PROP(LINEAR_TRANSFER_SLOPE_B, LINEARTRANSFER_PROP_BLUE_SLOPE);
+ CONVERT_PROP(LINEAR_TRANSFER_SLOPE_A, LINEARTRANSFER_PROP_ALPHA_SLOPE);
+ }
+ break;
+ case FilterType::GAMMA_TRANSFER:
+ switch (aIndex) {
+ CONVERT_PROP(GAMMA_TRANSFER_DISABLE_R, GAMMATRANSFER_PROP_RED_DISABLE);
+ CONVERT_PROP(GAMMA_TRANSFER_DISABLE_G,
+ GAMMATRANSFER_PROP_GREEN_DISABLE);
+ CONVERT_PROP(GAMMA_TRANSFER_DISABLE_B, GAMMATRANSFER_PROP_BLUE_DISABLE);
+ CONVERT_PROP(GAMMA_TRANSFER_DISABLE_A,
+ GAMMATRANSFER_PROP_ALPHA_DISABLE);
+ CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_R,
+ GAMMATRANSFER_PROP_RED_AMPLITUDE);
+ CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_G,
+ GAMMATRANSFER_PROP_GREEN_AMPLITUDE);
+ CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_B,
+ GAMMATRANSFER_PROP_BLUE_AMPLITUDE);
+ CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_A,
+ GAMMATRANSFER_PROP_ALPHA_AMPLITUDE);
+ CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_R,
+ GAMMATRANSFER_PROP_RED_EXPONENT);
+ CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_G,
+ GAMMATRANSFER_PROP_GREEN_EXPONENT);
+ CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_B,
+ GAMMATRANSFER_PROP_BLUE_EXPONENT);
+ CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_A,
+ GAMMATRANSFER_PROP_ALPHA_EXPONENT);
+ CONVERT_PROP(GAMMA_TRANSFER_OFFSET_R, GAMMATRANSFER_PROP_RED_OFFSET);
+ CONVERT_PROP(GAMMA_TRANSFER_OFFSET_G, GAMMATRANSFER_PROP_GREEN_OFFSET);
+ CONVERT_PROP(GAMMA_TRANSFER_OFFSET_B, GAMMATRANSFER_PROP_BLUE_OFFSET);
+ CONVERT_PROP(GAMMA_TRANSFER_OFFSET_A, GAMMATRANSFER_PROP_ALPHA_OFFSET);
+ }
+ break;
+ case FilterType::CONVOLVE_MATRIX:
+ switch (aIndex) {
+ CONVERT_PROP(CONVOLVE_MATRIX_BIAS, CONVOLVEMATRIX_PROP_BIAS);
+ CONVERT_PROP(CONVOLVE_MATRIX_KERNEL_MATRIX,
+ CONVOLVEMATRIX_PROP_KERNEL_MATRIX);
+ CONVERT_PROP(CONVOLVE_MATRIX_DIVISOR, CONVOLVEMATRIX_PROP_DIVISOR);
+ CONVERT_PROP(CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH,
+ CONVOLVEMATRIX_PROP_KERNEL_UNIT_LENGTH);
+ CONVERT_PROP(CONVOLVE_MATRIX_PRESERVE_ALPHA,
+ CONVOLVEMATRIX_PROP_PRESERVE_ALPHA);
+ }
+ case FilterType::DISPLACEMENT_MAP:
+ switch (aIndex) {
+ CONVERT_PROP(DISPLACEMENT_MAP_SCALE, DISPLACEMENTMAP_PROP_SCALE);
+ CONVERT_PROP(DISPLACEMENT_MAP_X_CHANNEL,
+ DISPLACEMENTMAP_PROP_X_CHANNEL_SELECT);
+ CONVERT_PROP(DISPLACEMENT_MAP_Y_CHANNEL,
+ DISPLACEMENTMAP_PROP_Y_CHANNEL_SELECT);
+ }
+ break;
+ case FilterType::TURBULENCE:
+ switch (aIndex) {
+ CONVERT_PROP(TURBULENCE_BASE_FREQUENCY, TURBULENCE_PROP_BASE_FREQUENCY);
+ CONVERT_PROP(TURBULENCE_NUM_OCTAVES, TURBULENCE_PROP_NUM_OCTAVES);
+ CONVERT_PROP(TURBULENCE_SEED, TURBULENCE_PROP_SEED);
+ CONVERT_PROP(TURBULENCE_STITCHABLE, TURBULENCE_PROP_STITCHABLE);
+ CONVERT_PROP(TURBULENCE_TYPE, TURBULENCE_PROP_NOISE);
+ }
+ break;
+ case FilterType::ARITHMETIC_COMBINE:
+ switch (aIndex) {
+ CONVERT_PROP(ARITHMETIC_COMBINE_COEFFICIENTS,
+ ARITHMETICCOMPOSITE_PROP_COEFFICIENTS);
+ }
+ break;
+ case FilterType::COMPOSITE:
+ switch (aIndex) { CONVERT_PROP(COMPOSITE_OPERATOR, COMPOSITE_PROP_MODE); }
+ break;
+ case FilterType::GAUSSIAN_BLUR:
+ switch (aIndex) {
+ CONVERT_PROP(GAUSSIAN_BLUR_STD_DEVIATION,
+ GAUSSIANBLUR_PROP_STANDARD_DEVIATION);
+ }
+ break;
+ case FilterType::DIRECTIONAL_BLUR:
+ switch (aIndex) {
+ CONVERT_PROP(DIRECTIONAL_BLUR_STD_DEVIATION,
+ DIRECTIONALBLUR_PROP_STANDARD_DEVIATION);
+ CONVERT_PROP(DIRECTIONAL_BLUR_DIRECTION, DIRECTIONALBLUR_PROP_ANGLE);
+ }
+ break;
+ case FilterType::POINT_DIFFUSE:
+ switch (aIndex) {
+ CONVERT_PROP(POINT_DIFFUSE_DIFFUSE_CONSTANT,
+ POINTDIFFUSE_PROP_DIFFUSE_CONSTANT);
+ CONVERT_PROP(POINT_DIFFUSE_POSITION, POINTDIFFUSE_PROP_LIGHT_POSITION);
+ CONVERT_PROP(POINT_DIFFUSE_COLOR, POINTDIFFUSE_PROP_COLOR);
+ CONVERT_PROP(POINT_DIFFUSE_SURFACE_SCALE,
+ POINTDIFFUSE_PROP_SURFACE_SCALE);
+ CONVERT_PROP(POINT_DIFFUSE_KERNEL_UNIT_LENGTH,
+ POINTDIFFUSE_PROP_KERNEL_UNIT_LENGTH);
+ }
+ break;
+ case FilterType::SPOT_DIFFUSE:
+ switch (aIndex) {
+ CONVERT_PROP(SPOT_DIFFUSE_DIFFUSE_CONSTANT,
+ SPOTDIFFUSE_PROP_DIFFUSE_CONSTANT);
+ CONVERT_PROP(SPOT_DIFFUSE_POINTS_AT, SPOTDIFFUSE_PROP_POINTS_AT);
+ CONVERT_PROP(SPOT_DIFFUSE_FOCUS, SPOTDIFFUSE_PROP_FOCUS);
+ CONVERT_PROP(SPOT_DIFFUSE_LIMITING_CONE_ANGLE,
+ SPOTDIFFUSE_PROP_LIMITING_CONE_ANGLE);
+ CONVERT_PROP(SPOT_DIFFUSE_POSITION, SPOTDIFFUSE_PROP_LIGHT_POSITION);
+ CONVERT_PROP(SPOT_DIFFUSE_COLOR, SPOTDIFFUSE_PROP_COLOR);
+ CONVERT_PROP(SPOT_DIFFUSE_SURFACE_SCALE,
+ SPOTDIFFUSE_PROP_SURFACE_SCALE);
+ CONVERT_PROP(SPOT_DIFFUSE_KERNEL_UNIT_LENGTH,
+ SPOTDIFFUSE_PROP_KERNEL_UNIT_LENGTH);
+ }
+ break;
+ case FilterType::DISTANT_DIFFUSE:
+ switch (aIndex) {
+ CONVERT_PROP(DISTANT_DIFFUSE_DIFFUSE_CONSTANT,
+ DISTANTDIFFUSE_PROP_DIFFUSE_CONSTANT);
+ CONVERT_PROP(DISTANT_DIFFUSE_AZIMUTH, DISTANTDIFFUSE_PROP_AZIMUTH);
+ CONVERT_PROP(DISTANT_DIFFUSE_ELEVATION, DISTANTDIFFUSE_PROP_ELEVATION);
+ CONVERT_PROP(DISTANT_DIFFUSE_COLOR, DISTANTDIFFUSE_PROP_COLOR);
+ CONVERT_PROP(DISTANT_DIFFUSE_SURFACE_SCALE,
+ DISTANTDIFFUSE_PROP_SURFACE_SCALE);
+ CONVERT_PROP(DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH,
+ DISTANTDIFFUSE_PROP_KERNEL_UNIT_LENGTH);
+ }
+ break;
+ case FilterType::POINT_SPECULAR:
+ switch (aIndex) {
+ CONVERT_PROP(POINT_SPECULAR_SPECULAR_CONSTANT,
+ POINTSPECULAR_PROP_SPECULAR_CONSTANT);
+ CONVERT_PROP(POINT_SPECULAR_SPECULAR_EXPONENT,
+ POINTSPECULAR_PROP_SPECULAR_EXPONENT);
+ CONVERT_PROP(POINT_SPECULAR_POSITION,
+ POINTSPECULAR_PROP_LIGHT_POSITION);
+ CONVERT_PROP(POINT_SPECULAR_COLOR, POINTSPECULAR_PROP_COLOR);
+ CONVERT_PROP(POINT_SPECULAR_SURFACE_SCALE,
+ POINTSPECULAR_PROP_SURFACE_SCALE);
+ CONVERT_PROP(POINT_SPECULAR_KERNEL_UNIT_LENGTH,
+ POINTSPECULAR_PROP_KERNEL_UNIT_LENGTH);
+ }
+ break;
+ case FilterType::SPOT_SPECULAR:
+ switch (aIndex) {
+ CONVERT_PROP(SPOT_SPECULAR_SPECULAR_CONSTANT,
+ SPOTSPECULAR_PROP_SPECULAR_CONSTANT);
+ CONVERT_PROP(SPOT_SPECULAR_SPECULAR_EXPONENT,
+ SPOTSPECULAR_PROP_SPECULAR_EXPONENT);
+ CONVERT_PROP(SPOT_SPECULAR_POINTS_AT, SPOTSPECULAR_PROP_POINTS_AT);
+ CONVERT_PROP(SPOT_SPECULAR_FOCUS, SPOTSPECULAR_PROP_FOCUS);
+ CONVERT_PROP(SPOT_SPECULAR_LIMITING_CONE_ANGLE,
+ SPOTSPECULAR_PROP_LIMITING_CONE_ANGLE);
+ CONVERT_PROP(SPOT_SPECULAR_POSITION, SPOTSPECULAR_PROP_LIGHT_POSITION);
+ CONVERT_PROP(SPOT_SPECULAR_COLOR, SPOTSPECULAR_PROP_COLOR);
+ CONVERT_PROP(SPOT_SPECULAR_SURFACE_SCALE,
+ SPOTSPECULAR_PROP_SURFACE_SCALE);
+ CONVERT_PROP(SPOT_SPECULAR_KERNEL_UNIT_LENGTH,
+ SPOTSPECULAR_PROP_KERNEL_UNIT_LENGTH);
+ }
+ break;
+ case FilterType::DISTANT_SPECULAR:
+ switch (aIndex) {
+ CONVERT_PROP(DISTANT_SPECULAR_SPECULAR_CONSTANT,
+ DISTANTSPECULAR_PROP_SPECULAR_CONSTANT);
+ CONVERT_PROP(DISTANT_SPECULAR_SPECULAR_EXPONENT,
+ DISTANTSPECULAR_PROP_SPECULAR_EXPONENT);
+ CONVERT_PROP(DISTANT_SPECULAR_AZIMUTH, DISTANTSPECULAR_PROP_AZIMUTH);
+ CONVERT_PROP(DISTANT_SPECULAR_ELEVATION,
+ DISTANTSPECULAR_PROP_ELEVATION);
+ CONVERT_PROP(DISTANT_SPECULAR_COLOR, DISTANTSPECULAR_PROP_COLOR);
+ CONVERT_PROP(DISTANT_SPECULAR_SURFACE_SCALE,
+ DISTANTSPECULAR_PROP_SURFACE_SCALE);
+ CONVERT_PROP(DISTANT_SPECULAR_KERNEL_UNIT_LENGTH,
+ DISTANTSPECULAR_PROP_KERNEL_UNIT_LENGTH);
+ }
+ break;
+ case FilterType::CROP:
+ switch (aIndex) { CONVERT_PROP(CROP_RECT, CROP_PROP_RECT); }
+ break;
+ default:
+ break;
+ }
+
+ return UINT32_MAX;
+}
+
+bool GetD2D1PropsForIntSize(FilterType aType, uint32_t aIndex,
+ UINT32* aPropWidth, UINT32* aPropHeight) {
+ switch (aType) {
+ case FilterType::MORPHOLOGY:
+ if (aIndex == ATT_MORPHOLOGY_RADII) {
+ *aPropWidth = D2D1_MORPHOLOGY_PROP_WIDTH;
+ *aPropHeight = D2D1_MORPHOLOGY_PROP_HEIGHT;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+static inline REFCLSID GetCLDIDForFilterType(FilterType aType) {
+ switch (aType) {
+ case FilterType::OPACITY:
+ case FilterType::COLOR_MATRIX:
+ return CLSID_D2D1ColorMatrix;
+ case FilterType::TRANSFORM:
+ return CLSID_D2D12DAffineTransform;
+ case FilterType::BLEND:
+ return CLSID_D2D1Blend;
+ case FilterType::MORPHOLOGY:
+ return CLSID_D2D1Morphology;
+ case FilterType::FLOOD:
+ return CLSID_D2D1Flood;
+ case FilterType::TILE:
+ return CLSID_D2D1Tile;
+ case FilterType::TABLE_TRANSFER:
+ return CLSID_D2D1TableTransfer;
+ case FilterType::LINEAR_TRANSFER:
+ return CLSID_D2D1LinearTransfer;
+ case FilterType::DISCRETE_TRANSFER:
+ return CLSID_D2D1DiscreteTransfer;
+ case FilterType::GAMMA_TRANSFER:
+ return CLSID_D2D1GammaTransfer;
+ case FilterType::DISPLACEMENT_MAP:
+ return CLSID_D2D1DisplacementMap;
+ case FilterType::TURBULENCE:
+ return CLSID_D2D1Turbulence;
+ case FilterType::ARITHMETIC_COMBINE:
+ return CLSID_D2D1ArithmeticComposite;
+ case FilterType::COMPOSITE:
+ return CLSID_D2D1Composite;
+ case FilterType::GAUSSIAN_BLUR:
+ return CLSID_D2D1GaussianBlur;
+ case FilterType::DIRECTIONAL_BLUR:
+ return CLSID_D2D1DirectionalBlur;
+ case FilterType::POINT_DIFFUSE:
+ return CLSID_D2D1PointDiffuse;
+ case FilterType::POINT_SPECULAR:
+ return CLSID_D2D1PointSpecular;
+ case FilterType::SPOT_DIFFUSE:
+ return CLSID_D2D1SpotDiffuse;
+ case FilterType::SPOT_SPECULAR:
+ return CLSID_D2D1SpotSpecular;
+ case FilterType::DISTANT_DIFFUSE:
+ return CLSID_D2D1DistantDiffuse;
+ case FilterType::DISTANT_SPECULAR:
+ return CLSID_D2D1DistantSpecular;
+ case FilterType::CROP:
+ return CLSID_D2D1Crop;
+ case FilterType::PREMULTIPLY:
+ return CLSID_D2D1Premultiply;
+ case FilterType::UNPREMULTIPLY:
+ return CLSID_D2D1UnPremultiply;
+ default:
+ break;
+ }
+ return GUID_NULL;
+}
+
+static bool IsTransferFilterType(FilterType aType) {
+ switch (aType) {
+ case FilterType::LINEAR_TRANSFER:
+ case FilterType::GAMMA_TRANSFER:
+ case FilterType::TABLE_TRANSFER:
+ case FilterType::DISCRETE_TRANSFER:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool HasUnboundedOutputRegion(FilterType aType) {
+ if (IsTransferFilterType(aType)) {
+ return true;
+ }
+
+ switch (aType) {
+ case FilterType::COLOR_MATRIX:
+ case FilterType::POINT_DIFFUSE:
+ case FilterType::SPOT_DIFFUSE:
+ case FilterType::DISTANT_DIFFUSE:
+ case FilterType::POINT_SPECULAR:
+ case FilterType::SPOT_SPECULAR:
+ case FilterType::DISTANT_SPECULAR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* static */
+already_AddRefed<FilterNode> FilterNodeD2D1::Create(ID2D1DeviceContext* aDC,
+ FilterType aType) {
+ if (aType == FilterType::CONVOLVE_MATRIX) {
+ return MakeAndAddRef<FilterNodeConvolveD2D1>(aDC);
+ }
+
+ RefPtr<ID2D1Effect> effect;
+ HRESULT hr;
+
+ hr = aDC->CreateEffect(GetCLDIDForFilterType(aType), getter_AddRefs(effect));
+
+ if (FAILED(hr) || !effect) {
+ gfxCriticalErrorOnce() << "Failed to create effect for FilterType: "
+ << hexa(hr);
+ return nullptr;
+ }
+
+ if (aType == FilterType::ARITHMETIC_COMBINE) {
+ effect->SetValue(D2D1_ARITHMETICCOMPOSITE_PROP_CLAMP_OUTPUT, TRUE);
+ }
+
+ if (aType == FilterType::OPACITY) {
+ return MakeAndAddRef<FilterNodeOpacityD2D1>(effect, aType);
+ }
+
+ RefPtr<FilterNodeD2D1> filter = new FilterNodeD2D1(effect, aType);
+
+ if (HasUnboundedOutputRegion(aType)) {
+ // These filters can produce non-transparent output from transparent
+ // input pixels, and we want them to have an unbounded output region.
+ filter = new FilterNodeExtendInputAdapterD2D1(aDC, filter, aType);
+ }
+
+ if (IsTransferFilterType(aType)) {
+ // Component transfer filters should appear to apply on unpremultiplied
+ // colors, but the D2D1 effects apply on premultiplied colors.
+ filter = new FilterNodePremultiplyAdapterD2D1(aDC, filter, aType);
+ }
+
+ return filter.forget();
+}
+
+void FilterNodeD2D1::InitUnmappedProperties() {
+ switch (mType) {
+ case FilterType::COLOR_MATRIX:
+ mEffect->SetValue(D2D1_COLORMATRIX_PROP_CLAMP_OUTPUT, TRUE);
+ break;
+ case FilterType::TRANSFORM:
+ mEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_BORDER_MODE,
+ D2D1_BORDER_MODE_HARD);
+ break;
+ default:
+ break;
+ }
+}
+
+void FilterNodeD2D1::SetInput(uint32_t aIndex, SourceSurface* aSurface) {
+ UINT32 input = GetD2D1InputForInput(mType, aIndex);
+ ID2D1Effect* effect = InputEffect();
+
+ if (mType == FilterType::COMPOSITE) {
+ UINT32 inputCount = effect->GetInputCount();
+
+ if (aIndex == inputCount - 1 && aSurface == nullptr) {
+ effect->SetInputCount(inputCount - 1);
+ } else if (aIndex >= inputCount && aSurface) {
+ effect->SetInputCount(aIndex + 1);
+ }
+ }
+
+ auto inputCount = effect->GetInputCount();
+ MOZ_RELEASE_ASSERT(input < inputCount);
+
+ mInputSurfaces.resize(inputCount);
+ mInputFilters.resize(inputCount);
+
+ // In order to convert aSurface into an ID2D1Image, we need to know what
+ // DrawTarget we paint into. However, the same FilterNode object can be
+ // used on different DrawTargets, so we need to hold on to the SourceSurface
+ // objects and delay the conversion until we're actually painted and know
+ // our target DrawTarget.
+ // The conversion happens in WillDraw().
+
+ mInputSurfaces[input] = aSurface;
+ mInputFilters[input] = nullptr;
+
+ // Clear the existing image from the effect.
+ effect->SetInput(input, nullptr);
+}
+
+void FilterNodeD2D1::SetInput(uint32_t aIndex, FilterNode* aFilter) {
+ UINT32 input = GetD2D1InputForInput(mType, aIndex);
+ ID2D1Effect* effect = InputEffect();
+
+ if (mType == FilterType::COMPOSITE) {
+ UINT32 inputCount = effect->GetInputCount();
+
+ if (aIndex == inputCount - 1 && aFilter == nullptr) {
+ effect->SetInputCount(inputCount - 1);
+ } else if (aIndex >= inputCount && aFilter) {
+ effect->SetInputCount(aIndex + 1);
+ }
+ }
+
+ auto inputCount = effect->GetInputCount();
+ MOZ_RELEASE_ASSERT(input < inputCount);
+
+ if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_DIRECT2D1_1) {
+ gfxWarning() << "Unknown input FilterNode set on effect.";
+ MOZ_ASSERT(0);
+ return;
+ }
+
+ FilterNodeD2D1* filter = static_cast<FilterNodeD2D1*>(aFilter);
+
+ mInputSurfaces.resize(inputCount);
+ mInputFilters.resize(inputCount);
+
+ // We hold on to the FilterNode object so that we can call WillDraw() on it.
+ mInputSurfaces[input] = nullptr;
+ mInputFilters[input] = filter;
+
+ if (filter) {
+ effect->SetInputEffect(input, filter->OutputEffect());
+ }
+}
+
+void FilterNodeD2D1::WillDraw(DrawTarget* aDT) {
+ // Convert input SourceSurfaces into ID2D1Images and set them on the effect.
+ for (size_t inputIndex = 0; inputIndex < mInputSurfaces.size();
+ inputIndex++) {
+ if (mInputSurfaces[inputIndex]) {
+ ID2D1Effect* effect = InputEffect();
+ RefPtr<ID2D1Image> image =
+ GetImageForSourceSurface(aDT, mInputSurfaces[inputIndex]);
+ effect->SetInput(inputIndex, image);
+ }
+ }
+
+ // Call WillDraw() on our input filters.
+ for (std::vector<RefPtr<FilterNodeD2D1>>::iterator it = mInputFilters.begin();
+ it != mInputFilters.end(); it++) {
+ if (*it) {
+ (*it)->WillDraw(aDT);
+ }
+ }
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, uint32_t aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ if (mType == FilterType::TURBULENCE &&
+ aIndex == ATT_TURBULENCE_BASE_FREQUENCY) {
+ mEffect->SetValue(input, D2D1::Vector2F(FLOAT(aValue), FLOAT(aValue)));
+ return;
+ } else if (mType == FilterType::DIRECTIONAL_BLUR &&
+ aIndex == ATT_DIRECTIONAL_BLUR_DIRECTION) {
+ mEffect->SetValue(input, aValue == BLUR_DIRECTION_X ? 0 : 90.0f);
+ return;
+ }
+
+ mEffect->SetValue(input, ConvertValue(mType, aIndex, aValue));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, Float aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, aValue);
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Point& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2DPoint(aValue));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Matrix5x4& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2DMatrix5x4(aValue));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Point3D& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2DVector3D(aValue));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Size& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2D1::Vector2F(aValue.width, aValue.height));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const IntSize& aValue) {
+ UINT32 widthProp, heightProp;
+
+ if (!GetD2D1PropsForIntSize(mType, aIndex, &widthProp, &heightProp)) {
+ return;
+ }
+
+ IntSize value = aValue;
+ ConvertValue(mType, aIndex, value);
+
+ mEffect->SetValue(widthProp, (UINT)value.width);
+ mEffect->SetValue(heightProp, (UINT)value.height);
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const DeviceColor& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ switch (mType) {
+ case FilterType::POINT_DIFFUSE:
+ case FilterType::SPOT_DIFFUSE:
+ case FilterType::DISTANT_DIFFUSE:
+ case FilterType::POINT_SPECULAR:
+ case FilterType::SPOT_SPECULAR:
+ case FilterType::DISTANT_SPECULAR:
+ mEffect->SetValue(input, D2D1::Vector3F(aValue.r, aValue.g, aValue.b));
+ break;
+ default:
+ mEffect->SetValue(input,
+ D2D1::Vector4F(aValue.r * aValue.a, aValue.g * aValue.a,
+ aValue.b * aValue.a, aValue.a));
+ }
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Rect& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2DRect(aValue));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const IntRect& aValue) {
+ if (mType == FilterType::TURBULENCE) {
+ MOZ_ASSERT(aIndex == ATT_TURBULENCE_RECT);
+
+ mEffect->SetValue(D2D1_TURBULENCE_PROP_OFFSET,
+ D2D1::Vector2F(Float(aValue.X()), Float(aValue.Y())));
+ mEffect->SetValue(
+ D2D1_TURBULENCE_PROP_SIZE,
+ D2D1::Vector2F(Float(aValue.Width()), Float(aValue.Height())));
+ return;
+ }
+
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input,
+ D2D1::RectF(Float(aValue.X()), Float(aValue.Y()),
+ Float(aValue.XMost()), Float(aValue.YMost())));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, bool aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, (BOOL)aValue);
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Float* aValues,
+ uint32_t aSize) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, (BYTE*)aValues, sizeof(Float) * aSize);
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const IntPoint& aValue) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2DPoint(aValue));
+}
+
+void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Matrix& aMatrix) {
+ UINT32 input = GetD2D1PropForAttribute(mType, aIndex);
+ MOZ_ASSERT(input < mEffect->GetPropertyCount());
+
+ mEffect->SetValue(input, D2DMatrix(aMatrix));
+}
+
+void FilterNodeOpacityD2D1::SetAttribute(uint32_t aIndex, Float aValue) {
+ D2D1_MATRIX_5X4_F matrix =
+ D2D1::Matrix5x4F(aValue, 0, 0, 0, 0, aValue, 0, 0, 0, 0, aValue, 0, 0, 0,
+ 0, aValue, 0, 0, 0, 0);
+
+ mEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix);
+ mEffect->SetValue(D2D1_COLORMATRIX_PROP_ALPHA_MODE,
+ D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT);
+}
+
+FilterNodeConvolveD2D1::FilterNodeConvolveD2D1(ID2D1DeviceContext* aDC)
+ : FilterNodeD2D1(nullptr, FilterType::CONVOLVE_MATRIX),
+ mEdgeMode(EDGE_MODE_DUPLICATE) {
+ // Correctly handling the interaction of edge mode and source rect is a bit
+ // tricky with D2D1 effects. We want the edge mode to only apply outside of
+ // the source rect (as specified by the ATT_CONVOLVE_MATRIX_SOURCE_RECT
+ // attribute). So if our input surface or filter is smaller than the source
+ // rect, we need to add transparency around it until we reach the edges of
+ // the source rect, and only then do any repeating or edge duplicating.
+ // Unfortunately, the border effect does not have a source rect attribute -
+ // it only looks at the output rect of its input filter or surface. So we use
+ // our custom ExtendInput effect to adjust the output rect of our input.
+ // All of this is only necessary when our edge mode is not EDGE_MODE_NONE, so
+ // we update the filter chain dynamically in UpdateChain().
+
+ HRESULT hr;
+
+ hr = aDC->CreateEffect(CLSID_D2D1ConvolveMatrix, getter_AddRefs(mEffect));
+
+ if (FAILED(hr) || !mEffect) {
+ gfxWarning() << "Failed to create ConvolveMatrix filter!";
+ return;
+ }
+
+ mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_BORDER_MODE,
+ D2D1_BORDER_MODE_SOFT);
+
+ hr = aDC->CreateEffect(CLSID_ExtendInputEffect,
+ getter_AddRefs(mExtendInputEffect));
+
+ if (FAILED(hr) || !mExtendInputEffect) {
+ gfxWarning() << "Failed to create ConvolveMatrix filter!";
+ return;
+ }
+
+ hr = aDC->CreateEffect(CLSID_D2D1Border, getter_AddRefs(mBorderEffect));
+
+ if (FAILED(hr) || !mBorderEffect) {
+ gfxWarning() << "Failed to create ConvolveMatrix filter!";
+ return;
+ }
+
+ mBorderEffect->SetInputEffect(0, mExtendInputEffect.get());
+
+ UpdateChain();
+ UpdateSourceRect();
+}
+
+void FilterNodeConvolveD2D1::SetInput(uint32_t aIndex, FilterNode* aFilter) {
+ FilterNodeD2D1::SetInput(aIndex, aFilter);
+
+ UpdateChain();
+}
+
+void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex, uint32_t aValue) {
+ if (aIndex != ATT_CONVOLVE_MATRIX_EDGE_MODE) {
+ return FilterNodeD2D1::SetAttribute(aIndex, aValue);
+ }
+
+ mEdgeMode = (ConvolveMatrixEdgeMode)aValue;
+
+ UpdateChain();
+}
+
+ID2D1Effect* FilterNodeConvolveD2D1::InputEffect() {
+ return mEdgeMode == EDGE_MODE_NONE ? mEffect.get() : mExtendInputEffect.get();
+}
+
+void FilterNodeConvolveD2D1::UpdateChain() {
+ // The shape of the filter graph:
+ //
+ // EDGE_MODE_NONE:
+ // input --> convolvematrix
+ //
+ // EDGE_MODE_DUPLICATE or EDGE_MODE_WRAP:
+ // input --> extendinput --> border --> convolvematrix
+ //
+ // mEffect is convolvematrix.
+
+ if (mEdgeMode != EDGE_MODE_NONE) {
+ mEffect->SetInputEffect(0, mBorderEffect.get());
+ }
+
+ RefPtr<ID2D1Effect> inputEffect;
+ if (mInputFilters.size() > 0 && mInputFilters[0]) {
+ inputEffect = mInputFilters[0]->OutputEffect();
+ }
+ InputEffect()->SetInputEffect(0, inputEffect);
+
+ if (mEdgeMode == EDGE_MODE_DUPLICATE) {
+ mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_X,
+ D2D1_BORDER_EDGE_MODE_CLAMP);
+ mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_Y,
+ D2D1_BORDER_EDGE_MODE_CLAMP);
+ } else if (mEdgeMode == EDGE_MODE_WRAP) {
+ mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_X,
+ D2D1_BORDER_EDGE_MODE_WRAP);
+ mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_Y,
+ D2D1_BORDER_EDGE_MODE_WRAP);
+ }
+}
+
+void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex,
+ const IntSize& aValue) {
+ if (aIndex != ATT_CONVOLVE_MATRIX_KERNEL_SIZE) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mKernelSize = aValue;
+
+ mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_X, aValue.width);
+ mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_Y, aValue.height);
+
+ UpdateOffset();
+}
+
+void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex,
+ const IntPoint& aValue) {
+ if (aIndex != ATT_CONVOLVE_MATRIX_TARGET) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mTarget = aValue;
+
+ UpdateOffset();
+}
+
+void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex,
+ const IntRect& aValue) {
+ if (aIndex != ATT_CONVOLVE_MATRIX_SOURCE_RECT) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mSourceRect = aValue;
+
+ UpdateSourceRect();
+}
+
+void FilterNodeConvolveD2D1::UpdateOffset() {
+ D2D1_VECTOR_2F vector = D2D1::Vector2F(
+ (Float(mKernelSize.width) - 1.0f) / 2.0f - Float(mTarget.x),
+ (Float(mKernelSize.height) - 1.0f) / 2.0f - Float(mTarget.y));
+
+ mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_KERNEL_OFFSET, vector);
+}
+
+void FilterNodeConvolveD2D1::UpdateSourceRect() {
+ mExtendInputEffect->SetValue(
+ EXTENDINPUT_PROP_OUTPUT_RECT,
+ D2D1::Vector4F(Float(mSourceRect.X()), Float(mSourceRect.Y()),
+ Float(mSourceRect.XMost()), Float(mSourceRect.YMost())));
+}
+
+FilterNodeExtendInputAdapterD2D1::FilterNodeExtendInputAdapterD2D1(
+ ID2D1DeviceContext* aDC, FilterNodeD2D1* aFilterNode, FilterType aType)
+ : FilterNodeD2D1(aFilterNode->MainEffect(), aType),
+ mWrappedFilterNode(aFilterNode) {
+ // We have an mEffect that looks at the bounds of the input effect, and we
+ // want mEffect to regard its input as unbounded. So we take the input,
+ // pipe it through an ExtendInput effect (which has an infinite output rect
+ // by default), and feed the resulting unbounded composition into mEffect.
+
+ HRESULT hr;
+
+ hr = aDC->CreateEffect(CLSID_ExtendInputEffect,
+ getter_AddRefs(mExtendInputEffect));
+
+ if (FAILED(hr) || !mExtendInputEffect) {
+ gfxWarning() << "Failed to create extend input effect for filter: "
+ << hexa(hr);
+ return;
+ }
+
+ aFilterNode->InputEffect()->SetInputEffect(0, mExtendInputEffect.get());
+}
+
+FilterNodePremultiplyAdapterD2D1::FilterNodePremultiplyAdapterD2D1(
+ ID2D1DeviceContext* aDC, FilterNodeD2D1* aFilterNode, FilterType aType)
+ : FilterNodeD2D1(aFilterNode->MainEffect(), aType) {
+ // D2D1 component transfer effects do strange things when it comes to
+ // premultiplication.
+ // For our purposes we only need the transfer filters to apply straight to
+ // unpremultiplied source channels and output unpremultiplied results.
+ // However, the D2D1 effects are designed differently: They can apply to both
+ // premultiplied and unpremultiplied inputs, and they always premultiply
+ // their result - at least in those color channels that have not been
+ // disabled.
+ // In order to determine whether the input needs to be unpremultiplied as
+ // part of the transfer, the effect consults the alpha mode metadata of the
+ // input surface or the input effect. We don't have such a concept in Moz2D,
+ // and giving Moz2D users different results based on something that cannot be
+ // influenced through Moz2D APIs seems like a bad idea.
+ // We solve this by applying a premultiply effect to the input before feeding
+ // it into the transfer effect. The premultiply effect always premultiplies
+ // regardless of any alpha mode metadata on inputs, and it always marks its
+ // output as premultiplied so that the transfer effect will unpremultiply
+ // consistently. Feeding always-premultiplied input into the transfer effect
+ // also avoids another problem that would appear when individual color
+ // channels disable the transfer: In that case, the disabled channels would
+ // pass through unchanged in their unpremultiplied form and the other
+ // channels would be premultiplied, giving a mixed result.
+ // But since we now ensure that the input is premultiplied, disabled channels
+ // will pass premultiplied values through to the result, which is consistent
+ // with the enabled channels.
+ // We also add an unpremultiply effect that postprocesses the result of the
+ // transfer effect because getting unpremultiplied results from the transfer
+ // filters is part of the FilterNode API.
+ HRESULT hr;
+
+ hr = aDC->CreateEffect(CLSID_D2D1Premultiply,
+ getter_AddRefs(mPrePremultiplyEffect));
+
+ if (FAILED(hr) || !mPrePremultiplyEffect) {
+ gfxWarning() << "Failed to create ComponentTransfer filter!";
+ return;
+ }
+
+ hr = aDC->CreateEffect(CLSID_D2D1UnPremultiply,
+ getter_AddRefs(mPostUnpremultiplyEffect));
+
+ if (FAILED(hr) || !mPostUnpremultiplyEffect) {
+ gfxWarning() << "Failed to create ComponentTransfer filter!";
+ return;
+ }
+
+ aFilterNode->InputEffect()->SetInputEffect(0, mPrePremultiplyEffect.get());
+ mPostUnpremultiplyEffect->SetInputEffect(0, aFilterNode->OutputEffect());
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/FilterNodeD2D1.h b/gfx/2d/FilterNodeD2D1.h
new file mode 100644
index 0000000000..a2f6e684f9
--- /dev/null
+++ b/gfx/2d/FilterNodeD2D1.h
@@ -0,0 +1,158 @@
+/* -*- 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_FILTERNODED2D1_H_
+#define MOZILLA_GFX_FILTERNODED2D1_H_
+
+#include "2D.h"
+#include "Filters.h"
+#include <vector>
+#include <windows.h>
+#include <d2d1_1.h>
+#include <cguid.h>
+
+namespace mozilla {
+namespace gfx {
+
+class FilterNodeD2D1 : public FilterNode {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeD2D1, override)
+
+ 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 DeviceColor& 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;
+
+ private:
+ using FilterNode::SetAttribute;
+ using FilterNode::SetInput;
+};
+
+class FilterNodeConvolveD2D1 : public FilterNodeD2D1 {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveD2D1, override)
+ explicit FilterNodeConvolveD2D1(ID2D1DeviceContext* aDC);
+
+ void SetInput(uint32_t aIndex, FilterNode* aFilter) override;
+
+ void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
+ void SetAttribute(uint32_t aIndex, const IntSize& aValue) override;
+ void SetAttribute(uint32_t aIndex, const IntPoint& aValue) override;
+ void SetAttribute(uint32_t aIndex, const IntRect& aValue) override;
+
+ ID2D1Effect* InputEffect() override;
+
+ private:
+ using FilterNode::SetAttribute;
+ using FilterNode::SetInput;
+
+ void UpdateChain();
+ void UpdateOffset();
+ void UpdateSourceRect();
+
+ RefPtr<ID2D1Effect> mExtendInputEffect;
+ RefPtr<ID2D1Effect> mBorderEffect;
+ ConvolveMatrixEdgeMode mEdgeMode;
+ IntPoint mTarget;
+ IntSize mKernelSize;
+ IntRect mSourceRect;
+};
+
+class FilterNodeOpacityD2D1 : public FilterNodeD2D1 {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeOpacityD2D1, override)
+ FilterNodeOpacityD2D1(ID2D1Effect* aEffect, FilterType aType)
+ : FilterNodeD2D1(aEffect, aType) {}
+
+ void SetAttribute(uint32_t aIndex, Float aValue) override;
+};
+
+class FilterNodeExtendInputAdapterD2D1 : public FilterNodeD2D1 {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeExtendInputAdapterD2D1,
+ override)
+ FilterNodeExtendInputAdapterD2D1(ID2D1DeviceContext* aDC,
+ FilterNodeD2D1* aFilterNode,
+ FilterType aType);
+
+ ID2D1Effect* InputEffect() override { return mExtendInputEffect.get(); }
+ 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);
+
+ ID2D1Effect* InputEffect() override { return mPrePremultiplyEffect.get(); }
+ ID2D1Effect* OutputEffect() override {
+ return mPostUnpremultiplyEffect.get();
+ }
+
+ private:
+ RefPtr<ID2D1Effect> mPrePremultiplyEffect;
+ RefPtr<ID2D1Effect> mPostUnpremultiplyEffect;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp
new file mode 100644
index 0000000000..16be0b872e
--- /dev/null
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -0,0 +1,3748 @@
+/* -*- 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 <cmath>
+#include "DataSurfaceHelpers.h"
+#include "FilterNodeSoftware.h"
+#include "2D.h"
+#include "Tools.h"
+#include "Blur.h"
+#include <map>
+#include "FilterProcessing.h"
+#include "Logging.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/DebugOnly.h"
+
+// #define DEBUG_DUMP_SURFACES
+
+#ifdef DEBUG_DUMP_SURFACES
+# include "gfxUtils.h" // not part of Moz2D
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+namespace {
+
+/**
+ * This class provides a way to get a pow() results in constant-time. It works
+ * by caching 129 ((1 << sCacheIndexPrecisionBits) + 1) values for bases between
+ * 0 and 1 and a fixed exponent.
+ **/
+class PowCache {
+ public:
+ PowCache() : mNumPowTablePreSquares(-1) {}
+
+ void CacheForExponent(Float aExponent) {
+ // Since we are in the world where we only care about
+ // input and results in [0,1], there is no point in
+ // dealing with non-positive exponents.
+ if (aExponent <= 0) {
+ mNumPowTablePreSquares = -1;
+ return;
+ }
+ int numPreSquares = 0;
+ while (numPreSquares < 5 && aExponent > (1 << (numPreSquares + 2))) {
+ numPreSquares++;
+ }
+ mNumPowTablePreSquares = numPreSquares;
+ for (size_t i = 0; i < sCacheSize; i++) {
+ // sCacheSize is chosen in such a way that a takes values
+ // from 0.0 to 1.0 inclusive.
+ Float a = i / Float(1 << sCacheIndexPrecisionBits);
+ MOZ_ASSERT(0.0f <= a && a <= 1.0f,
+ "We only want to cache for bases between 0 and 1.");
+
+ for (int j = 0; j < mNumPowTablePreSquares; j++) {
+ a = sqrt(a);
+ }
+ uint32_t cachedInt = pow(a, aExponent) * (1 << sOutputIntPrecisionBits);
+ MOZ_ASSERT(cachedInt < (1 << (sizeof(mPowTable[i]) * 8)),
+ "mPowCache integer type too small");
+
+ mPowTable[i] = cachedInt;
+ }
+ }
+
+ // Only call Pow() if HasPowerTable() would return true, to avoid complicating
+ // this code and having it just return (1 << sOutputIntPrecisionBits))
+ uint16_t Pow(uint16_t aBase) {
+ MOZ_ASSERT(HasPowerTable());
+ // Results should be similar to what the following code would produce:
+ // Float x = Float(aBase) / (1 << sInputIntPrecisionBits);
+ // return uint16_t(pow(x, aExponent) * (1 << sOutputIntPrecisionBits));
+
+ MOZ_ASSERT(aBase <= (1 << sInputIntPrecisionBits),
+ "aBase needs to be between 0 and 1!");
+
+ uint32_t a = aBase;
+ for (int j = 0; j < mNumPowTablePreSquares; j++) {
+ a = a * a >> sInputIntPrecisionBits;
+ }
+ uint32_t i = a >> (sInputIntPrecisionBits - sCacheIndexPrecisionBits);
+ MOZ_ASSERT(i < sCacheSize, "out-of-bounds mPowTable access");
+ return mPowTable[i];
+ }
+
+ static const int sInputIntPrecisionBits = 15;
+ static const int sOutputIntPrecisionBits = 15;
+ static const int sCacheIndexPrecisionBits = 7;
+
+ inline bool HasPowerTable() const { return mNumPowTablePreSquares >= 0; }
+
+ private:
+ static const size_t sCacheSize = (1 << sCacheIndexPrecisionBits) + 1;
+
+ int mNumPowTablePreSquares;
+ uint16_t mPowTable[sCacheSize];
+};
+
+class PointLightSoftware {
+ public:
+ bool SetAttribute(uint32_t aIndex, Float) { return false; }
+ bool SetAttribute(uint32_t aIndex, const Point3D&);
+ void Prepare() {}
+ Point3D GetVectorToLight(const Point3D& aTargetPoint);
+ uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight);
+
+ private:
+ Point3D mPosition;
+};
+
+class SpotLightSoftware {
+ public:
+ SpotLightSoftware();
+ bool SetAttribute(uint32_t aIndex, Float);
+ bool SetAttribute(uint32_t aIndex, const Point3D&);
+ void Prepare();
+ Point3D GetVectorToLight(const Point3D& aTargetPoint);
+ uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight);
+
+ private:
+ Point3D mPosition;
+ Point3D mPointsAt;
+ Point3D mVectorFromFocusPointToLight;
+ Float mSpecularFocus;
+ Float mLimitingConeAngle;
+ Float mLimitingConeCos;
+ PowCache mPowCache;
+};
+
+class DistantLightSoftware {
+ public:
+ DistantLightSoftware();
+ bool SetAttribute(uint32_t aIndex, Float);
+ bool SetAttribute(uint32_t aIndex, const Point3D&) { return false; }
+ void Prepare();
+ Point3D GetVectorToLight(const Point3D& aTargetPoint);
+ uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight);
+
+ private:
+ Float mAzimuth;
+ Float mElevation;
+ Point3D mVectorToLight;
+};
+
+class DiffuseLightingSoftware {
+ public:
+ DiffuseLightingSoftware();
+ bool SetAttribute(uint32_t aIndex, Float);
+ void Prepare() {}
+ uint32_t LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight,
+ uint32_t aColor);
+
+ private:
+ Float mDiffuseConstant;
+};
+
+class SpecularLightingSoftware {
+ public:
+ SpecularLightingSoftware();
+ bool SetAttribute(uint32_t aIndex, Float);
+ void Prepare();
+ uint32_t LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight,
+ uint32_t aColor);
+
+ private:
+ Float mSpecularConstant;
+ Float mSpecularExponent;
+ uint32_t mSpecularConstantInt;
+ PowCache mPowCache;
+};
+
+} // unnamed namespace
+
+// from xpcom/ds/nsMathUtils.h
+static int32_t NS_lround(double x) {
+ return x >= 0.0 ? int32_t(x + 0.5) : int32_t(x - 0.5);
+}
+
+static already_AddRefed<DataSourceSurface> CloneAligned(
+ DataSourceSurface* aSource) {
+ return CreateDataSourceSurfaceByCloning(aSource);
+}
+
+static void FillRectWithPixel(DataSourceSurface* aSurface,
+ const IntRect& aFillRect, IntPoint aPixelPos) {
+ MOZ_ASSERT(!aFillRect.Overflows());
+ MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect),
+ "aFillRect needs to be completely inside the surface");
+ MOZ_ASSERT(SurfaceContainsPoint(aSurface, aPixelPos),
+ "aPixelPos needs to be inside the surface");
+
+ DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE);
+ if (MOZ2D_WARN_IF(!surfMap.IsMapped())) {
+ return;
+ }
+ uint8_t* sourcePixelData =
+ DataAtOffset(aSurface, surfMap.GetMappedSurface(), aPixelPos);
+ uint8_t* data =
+ DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft());
+ int bpp = BytesPerPixel(aSurface->GetFormat());
+
+ // Fill the first row by hand.
+ if (bpp == 4) {
+ uint32_t sourcePixel = *(uint32_t*)sourcePixelData;
+ for (int32_t x = 0; x < aFillRect.Width(); x++) {
+ *((uint32_t*)data + x) = sourcePixel;
+ }
+ } else if (BytesPerPixel(aSurface->GetFormat()) == 1) {
+ uint8_t sourcePixel = *sourcePixelData;
+ memset(data, sourcePixel, aFillRect.Width());
+ }
+
+ // Copy the first row into the other rows.
+ for (int32_t y = 1; y < aFillRect.Height(); y++) {
+ PodCopy(data + y * surfMap.GetStride(), data, aFillRect.Width() * bpp);
+ }
+}
+
+static void FillRectWithVerticallyRepeatingHorizontalStrip(
+ DataSourceSurface* aSurface, const IntRect& aFillRect,
+ const IntRect& aSampleRect) {
+ MOZ_ASSERT(!aFillRect.Overflows());
+ MOZ_ASSERT(!aSampleRect.Overflows());
+ MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect),
+ "aFillRect needs to be completely inside the surface");
+ MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aSampleRect),
+ "aSampleRect needs to be completely inside the surface");
+
+ DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE);
+ if (MOZ2D_WARN_IF(!surfMap.IsMapped())) {
+ return;
+ }
+
+ uint8_t* sampleData =
+ DataAtOffset(aSurface, surfMap.GetMappedSurface(), aSampleRect.TopLeft());
+ uint8_t* data =
+ DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft());
+ if (BytesPerPixel(aSurface->GetFormat()) == 4) {
+ for (int32_t y = 0; y < aFillRect.Height(); y++) {
+ PodCopy((uint32_t*)data, (uint32_t*)sampleData, aFillRect.Width());
+ data += surfMap.GetStride();
+ }
+ } else if (BytesPerPixel(aSurface->GetFormat()) == 1) {
+ for (int32_t y = 0; y < aFillRect.Height(); y++) {
+ PodCopy(data, sampleData, aFillRect.Width());
+ data += surfMap.GetStride();
+ }
+ }
+}
+
+static void FillRectWithHorizontallyRepeatingVerticalStrip(
+ DataSourceSurface* aSurface, const IntRect& aFillRect,
+ const IntRect& aSampleRect) {
+ MOZ_ASSERT(!aFillRect.Overflows());
+ MOZ_ASSERT(!aSampleRect.Overflows());
+ MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect),
+ "aFillRect needs to be completely inside the surface");
+ MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aSampleRect),
+ "aSampleRect needs to be completely inside the surface");
+
+ DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE);
+ if (MOZ2D_WARN_IF(!surfMap.IsMapped())) {
+ return;
+ }
+
+ uint8_t* sampleData =
+ DataAtOffset(aSurface, surfMap.GetMappedSurface(), aSampleRect.TopLeft());
+ uint8_t* data =
+ DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft());
+ if (BytesPerPixel(aSurface->GetFormat()) == 4) {
+ for (int32_t y = 0; y < aFillRect.Height(); y++) {
+ int32_t sampleColor = *((uint32_t*)sampleData);
+ for (int32_t x = 0; x < aFillRect.Width(); x++) {
+ *((uint32_t*)data + x) = sampleColor;
+ }
+ data += surfMap.GetStride();
+ sampleData += surfMap.GetStride();
+ }
+ } else if (BytesPerPixel(aSurface->GetFormat()) == 1) {
+ for (int32_t y = 0; y < aFillRect.Height(); y++) {
+ uint8_t sampleColor = *sampleData;
+ memset(data, sampleColor, aFillRect.Width());
+ data += surfMap.GetStride();
+ sampleData += surfMap.GetStride();
+ }
+ }
+}
+
+static void DuplicateEdges(DataSourceSurface* aSurface,
+ const IntRect& aFromRect) {
+ MOZ_ASSERT(!aFromRect.Overflows());
+ MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFromRect),
+ "aFromRect needs to be completely inside the surface");
+
+ IntSize size = aSurface->GetSize();
+ IntRect fill;
+ IntRect sampleRect;
+ for (int32_t ix = 0; ix < 3; ix++) {
+ switch (ix) {
+ case 0:
+ fill.SetRectX(0, aFromRect.X());
+ sampleRect.SetRectX(fill.XMost(), 1);
+ break;
+ case 1:
+ fill.SetRectX(aFromRect.X(), aFromRect.Width());
+ sampleRect.SetRectX(fill.X(), fill.Width());
+ break;
+ case 2:
+ fill.MoveToX(aFromRect.XMost());
+ fill.SetRightEdge(size.width);
+ sampleRect.SetRectX(fill.X() - 1, 1);
+ break;
+ }
+ if (fill.Width() <= 0) {
+ continue;
+ }
+ bool xIsMiddle = (ix == 1);
+ for (int32_t iy = 0; iy < 3; iy++) {
+ switch (iy) {
+ case 0:
+ fill.SetRectY(0, aFromRect.Y());
+ sampleRect.SetRectY(fill.YMost(), 1);
+ break;
+ case 1:
+ fill.SetRectY(aFromRect.Y(), aFromRect.Height());
+ sampleRect.SetRectY(fill.Y(), fill.Height());
+ break;
+ case 2:
+ fill.MoveToY(aFromRect.YMost());
+ fill.SetBottomEdge(size.height);
+ sampleRect.SetRectY(fill.Y() - 1, 1);
+ break;
+ }
+ if (fill.Height() <= 0) {
+ continue;
+ }
+ bool yIsMiddle = (iy == 1);
+ if (!xIsMiddle && !yIsMiddle) {
+ // Corner
+ FillRectWithPixel(aSurface, fill, sampleRect.TopLeft());
+ }
+ if (xIsMiddle && !yIsMiddle) {
+ // Top middle or bottom middle
+ FillRectWithVerticallyRepeatingHorizontalStrip(aSurface, fill,
+ sampleRect);
+ }
+ if (!xIsMiddle && yIsMiddle) {
+ // Left middle or right middle
+ FillRectWithHorizontallyRepeatingVerticalStrip(aSurface, fill,
+ sampleRect);
+ }
+ }
+ }
+}
+
+static IntPoint TileIndex(const IntRect& aFirstTileRect,
+ const IntPoint& aPoint) {
+ return IntPoint(int32_t(floor(double(aPoint.x - aFirstTileRect.X()) /
+ aFirstTileRect.Width())),
+ int32_t(floor(double(aPoint.y - aFirstTileRect.Y()) /
+ aFirstTileRect.Height())));
+}
+
+static void TileSurface(DataSourceSurface* aSource, DataSourceSurface* aTarget,
+ const IntPoint& aOffset) {
+ IntRect sourceRect(aOffset, aSource->GetSize());
+ IntRect targetRect(IntPoint(0, 0), aTarget->GetSize());
+ IntPoint startIndex = TileIndex(sourceRect, targetRect.TopLeft());
+ IntPoint endIndex = TileIndex(sourceRect, targetRect.BottomRight());
+
+ for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) {
+ for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) {
+ IntPoint destPoint(sourceRect.X() + ix * sourceRect.Width(),
+ sourceRect.Y() + iy * sourceRect.Height());
+ IntRect destRect(destPoint, sourceRect.Size());
+ destRect = destRect.Intersect(targetRect);
+ IntRect srcRect = destRect - destPoint;
+ CopyRect(aSource, aTarget, srcRect, destRect.TopLeft());
+ }
+ }
+}
+
+static already_AddRefed<DataSourceSurface> GetDataSurfaceInRect(
+ SourceSurface* aSurface, const IntRect& aSurfaceRect,
+ const IntRect& aDestRect, ConvolveMatrixEdgeMode aEdgeMode) {
+ MOZ_ASSERT(aSurface ? aSurfaceRect.Size() == aSurface->GetSize()
+ : aSurfaceRect.IsEmpty());
+
+ if (aSurfaceRect.Overflows() || aDestRect.Overflows()) {
+ // We can't rely on the intersection calculations below to make sense when
+ // XMost() or YMost() overflow. Bail out.
+ return nullptr;
+ }
+
+ IntRect sourceRect = aSurfaceRect;
+
+ if (sourceRect.IsEqualEdges(aDestRect)) {
+ return aSurface ? aSurface->GetDataSurface() : nullptr;
+ }
+
+ IntRect intersect = sourceRect.Intersect(aDestRect);
+
+ // create rects that are in surface local space.
+ IntRect intersectInSourceSpace = intersect - sourceRect.TopLeft();
+ IntRect intersectInDestSpace = intersect - aDestRect.TopLeft();
+ SurfaceFormat format =
+ aSurface ? aSurface->GetFormat() : SurfaceFormat(SurfaceFormat::B8G8R8A8);
+
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(aDestRect.Size(), format, true);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ if (!aSurface) {
+ return target.forget();
+ }
+
+ RefPtr<DataSourceSurface> dataSource = aSurface->GetDataSurface();
+ MOZ_ASSERT(dataSource);
+
+ if (aEdgeMode == EDGE_MODE_WRAP) {
+ TileSurface(dataSource, target, intersectInDestSpace.TopLeft());
+ return target.forget();
+ }
+
+ CopyRect(dataSource, target, intersectInSourceSpace,
+ intersectInDestSpace.TopLeft());
+
+ if (aEdgeMode == EDGE_MODE_DUPLICATE) {
+ DuplicateEdges(target, intersectInDestSpace);
+ }
+
+ return target.forget();
+}
+
+/* static */
+already_AddRefed<FilterNode> FilterNodeSoftware::Create(FilterType aType) {
+ RefPtr<FilterNodeSoftware> filter;
+ switch (aType) {
+ case FilterType::BLEND:
+ filter = new FilterNodeBlendSoftware();
+ break;
+ case FilterType::TRANSFORM:
+ filter = new FilterNodeTransformSoftware();
+ break;
+ case FilterType::MORPHOLOGY:
+ filter = new FilterNodeMorphologySoftware();
+ break;
+ case FilterType::COLOR_MATRIX:
+ filter = new FilterNodeColorMatrixSoftware();
+ break;
+ case FilterType::FLOOD:
+ filter = new FilterNodeFloodSoftware();
+ break;
+ case FilterType::TILE:
+ filter = new FilterNodeTileSoftware();
+ break;
+ case FilterType::TABLE_TRANSFER:
+ filter = new FilterNodeTableTransferSoftware();
+ break;
+ case FilterType::DISCRETE_TRANSFER:
+ filter = new FilterNodeDiscreteTransferSoftware();
+ break;
+ case FilterType::LINEAR_TRANSFER:
+ filter = new FilterNodeLinearTransferSoftware();
+ break;
+ case FilterType::GAMMA_TRANSFER:
+ filter = new FilterNodeGammaTransferSoftware();
+ break;
+ case FilterType::CONVOLVE_MATRIX:
+ filter = new FilterNodeConvolveMatrixSoftware();
+ break;
+ case FilterType::DISPLACEMENT_MAP:
+ filter = new FilterNodeDisplacementMapSoftware();
+ break;
+ case FilterType::TURBULENCE:
+ filter = new FilterNodeTurbulenceSoftware();
+ break;
+ case FilterType::ARITHMETIC_COMBINE:
+ filter = new FilterNodeArithmeticCombineSoftware();
+ break;
+ case FilterType::COMPOSITE:
+ filter = new FilterNodeCompositeSoftware();
+ break;
+ case FilterType::GAUSSIAN_BLUR:
+ filter = new FilterNodeGaussianBlurSoftware();
+ break;
+ case FilterType::DIRECTIONAL_BLUR:
+ filter = new FilterNodeDirectionalBlurSoftware();
+ break;
+ case FilterType::CROP:
+ filter = new FilterNodeCropSoftware();
+ break;
+ case FilterType::PREMULTIPLY:
+ filter = new FilterNodePremultiplySoftware();
+ break;
+ case FilterType::UNPREMULTIPLY:
+ filter = new FilterNodeUnpremultiplySoftware();
+ break;
+ case FilterType::OPACITY:
+ filter = new FilterNodeOpacitySoftware();
+ break;
+ case FilterType::POINT_DIFFUSE:
+ filter = new FilterNodeLightingSoftware<PointLightSoftware,
+ DiffuseLightingSoftware>(
+ "FilterNodeLightingSoftware<PointLight, DiffuseLighting>");
+ break;
+ case FilterType::POINT_SPECULAR:
+ filter = new FilterNodeLightingSoftware<PointLightSoftware,
+ SpecularLightingSoftware>(
+ "FilterNodeLightingSoftware<PointLight, SpecularLighting>");
+ break;
+ case FilterType::SPOT_DIFFUSE:
+ filter = new FilterNodeLightingSoftware<SpotLightSoftware,
+ DiffuseLightingSoftware>(
+ "FilterNodeLightingSoftware<SpotLight, DiffuseLighting>");
+ break;
+ case FilterType::SPOT_SPECULAR:
+ filter = new FilterNodeLightingSoftware<SpotLightSoftware,
+ SpecularLightingSoftware>(
+ "FilterNodeLightingSoftware<SpotLight, SpecularLighting>");
+ break;
+ case FilterType::DISTANT_DIFFUSE:
+ filter = new FilterNodeLightingSoftware<DistantLightSoftware,
+ DiffuseLightingSoftware>(
+ "FilterNodeLightingSoftware<DistantLight, DiffuseLighting>");
+ break;
+ case FilterType::DISTANT_SPECULAR:
+ filter = new FilterNodeLightingSoftware<DistantLightSoftware,
+ SpecularLightingSoftware>(
+ "FilterNodeLightingSoftware<DistantLight, SpecularLighting>");
+ break;
+ }
+ return filter.forget();
+}
+
+void FilterNodeSoftware::Draw(DrawTarget* aDrawTarget, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+#ifdef DEBUG_DUMP_SURFACES
+ printf("<style>section{margin:10px;}</style><pre>\nRendering filter %s...\n",
+ GetName());
+#endif
+
+ Rect renderRect = aSourceRect;
+ renderRect.RoundOut();
+ IntRect renderIntRect;
+ if (!renderRect.ToIntRect(&renderIntRect)) {
+#ifdef DEBUG_DUMP_SURFACES
+ printf("render rect overflowed, not painting anything\n");
+ printf("</pre>\n");
+#endif
+ return;
+ }
+
+ IntRect outputRect = GetOutputRectInRect(renderIntRect);
+ if (outputRect.Overflows()) {
+#ifdef DEBUG_DUMP_SURFACES
+ printf("output rect overflowed, not painting anything\n");
+ printf("</pre>\n");
+#endif
+ return;
+ }
+
+ RefPtr<DataSourceSurface> result;
+ if (!outputRect.IsEmpty()) {
+ result = GetOutput(outputRect);
+ }
+
+ if (!result) {
+ // Null results are allowed and treated as transparent. Don't draw anything.
+#ifdef DEBUG_DUMP_SURFACES
+ printf("output returned null\n");
+ printf("</pre>\n");
+#endif
+ return;
+ }
+
+#ifdef DEBUG_DUMP_SURFACES
+ printf("output from %s:\n", GetName());
+ printf("<img src='");
+ gfxUtils::DumpAsDataURL(result);
+ printf("'>\n");
+ printf("</pre>\n");
+#endif
+
+ Point sourceToDestOffset = aDestPoint - aSourceRect.TopLeft();
+ Rect renderedSourceRect = Rect(outputRect).Intersect(aSourceRect);
+ Rect renderedDestRect = renderedSourceRect + sourceToDestOffset;
+ if (result->GetFormat() == SurfaceFormat::A8) {
+ // Interpret the result as having implicitly black color channels.
+ aDrawTarget->PushClipRect(renderedDestRect);
+ aDrawTarget->MaskSurface(
+ ColorPattern(DeviceColor::MaskOpaqueBlack()), result,
+ Point(outputRect.TopLeft()) + sourceToDestOffset, aOptions);
+ aDrawTarget->PopClip();
+ } else {
+ aDrawTarget->DrawSurface(result, renderedDestRect,
+ renderedSourceRect - Point(outputRect.TopLeft()),
+ DrawSurfaceOptions(), aOptions);
+ }
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeSoftware::GetOutput(
+ const IntRect& aRect) {
+ MOZ_ASSERT(GetOutputRectInRect(aRect).Contains(aRect));
+
+ if (aRect.Overflows()) {
+ return nullptr;
+ }
+
+ IntRect cachedRect;
+ IntRect requestedRect;
+ RefPtr<DataSourceSurface> cachedOutput;
+
+ // Retrieve a cached surface if we have one and it can
+ // satisfy this request, or else request a rect we will compute and cache
+ if (!mCachedRect.Contains(aRect)) {
+ RequestRect(aRect);
+ requestedRect = mRequestedRect;
+ } else {
+ MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?");
+ cachedRect = mCachedRect;
+ cachedOutput = mCachedOutput;
+ }
+
+ if (!cachedOutput) {
+ // Compute the output
+ cachedOutput = Render(requestedRect);
+
+ // Update the cache for future requests
+ mCachedOutput = cachedOutput;
+ if (!mCachedOutput) {
+ mCachedRect = IntRect();
+ mRequestedRect = IntRect();
+ return nullptr;
+ }
+ mCachedRect = requestedRect;
+ mRequestedRect = IntRect();
+
+ cachedRect = mCachedRect;
+ }
+
+ return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE);
+}
+
+void FilterNodeSoftware::RequestRect(const IntRect& aRect) {
+ if (mRequestedRect.Contains(aRect)) {
+ // Bail out now. Otherwise pathological filters can spend time exponential
+ // in the number of primitives, e.g. if each primitive takes the
+ // previous primitive as its two inputs.
+ return;
+ }
+ mRequestedRect = mRequestedRect.Union(aRect);
+ RequestFromInputsForRect(aRect);
+}
+
+IntRect FilterNodeSoftware::MapInputRectToSource(uint32_t aInputEnumIndex,
+ const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ int32_t inputIndex = InputIndex(aInputEnumIndex);
+ if (inputIndex < 0) {
+ gfxDevCrash(LogReason::FilterInputError)
+ << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs();
+ return aMax;
+ }
+ if ((uint32_t)inputIndex < NumberOfSetInputs()) {
+ RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
+ // If we have any input filters call into them to do the mapping,
+ // otherwise we can assume an input surface will be used
+ // and just return aRect.
+ if (filter) {
+ return filter->MapRectToSource(aRect, aMax, aSourceNode);
+ }
+ }
+ // We have an input surface instead of a filter
+ // so check if we're the target node.
+ if (this == aSourceNode) {
+ return aRect;
+ }
+ return IntRect();
+}
+
+void FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex,
+ const IntRect& aRect) {
+ if (aRect.Overflows()) {
+ return;
+ }
+
+ int32_t inputIndex = InputIndex(aInputEnumIndex);
+ if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
+ gfxDevCrash(LogReason::FilterInputError)
+ << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs();
+ return;
+ }
+ if (mInputSurfaces[inputIndex]) {
+ return;
+ }
+ RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
+ MOZ_ASSERT(filter, "missing input");
+
+ filter->RequestRect(filter->GetOutputRectInRect(aRect));
+}
+
+SurfaceFormat FilterNodeSoftware::DesiredFormat(SurfaceFormat aCurrentFormat,
+ FormatHint aFormatHint) {
+ if (aCurrentFormat == SurfaceFormat::A8 && aFormatHint == CAN_HANDLE_A8) {
+ return SurfaceFormat::A8;
+ }
+ return SurfaceFormat::B8G8R8A8;
+}
+
+already_AddRefed<DataSourceSurface>
+FilterNodeSoftware::GetInputDataSourceSurface(
+ uint32_t aInputEnumIndex, const IntRect& aRect, FormatHint aFormatHint,
+ ConvolveMatrixEdgeMode aEdgeMode,
+ const IntRect* aTransparencyPaddedSourceRect) {
+ if (aRect.Overflows()) {
+ return nullptr;
+ }
+
+#ifdef DEBUG_DUMP_SURFACES
+ printf(
+ "<section><h1>GetInputDataSourceSurface with aRect: %d, %d, %d, "
+ "%d</h1>\n",
+ aRect.x, aRect.y, aRect.Width(), aRect.Height());
+#endif
+ int32_t inputIndex = InputIndex(aInputEnumIndex);
+ if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
+ gfxDevCrash(LogReason::FilterInputData)
+ << "Invalid data " << inputIndex << " vs. " << NumberOfSetInputs();
+ return nullptr;
+ }
+
+ if (aRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> surface;
+ IntRect surfaceRect;
+
+ if (mInputSurfaces[inputIndex]) {
+ // Input from input surface
+ surface = mInputSurfaces[inputIndex];
+#ifdef DEBUG_DUMP_SURFACES
+ printf("input from input surface:\n");
+#endif
+ surfaceRect = surface->GetRect();
+ } else {
+ // Input from input filter
+#ifdef DEBUG_DUMP_SURFACES
+ printf("getting input from input filter %s...\n",
+ mInputFilters[inputIndex]->GetName());
+#endif
+ RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
+ MOZ_ASSERT(filter, "missing input");
+ IntRect inputFilterOutput = filter->GetOutputRectInRect(aRect);
+ if (!inputFilterOutput.IsEmpty()) {
+ surface = filter->GetOutput(inputFilterOutput);
+ }
+#ifdef DEBUG_DUMP_SURFACES
+ printf("input from input filter %s:\n",
+ mInputFilters[inputIndex]->GetName());
+#endif
+ surfaceRect = inputFilterOutput;
+ MOZ_ASSERT(!surface || surfaceRect.Size() == surface->GetSize());
+ }
+
+ if (surface && surface->GetFormat() == SurfaceFormat::UNKNOWN) {
+#ifdef DEBUG_DUMP_SURFACES
+ printf("wrong input format</section>\n\n");
+#endif
+ return nullptr;
+ }
+
+ if (!surfaceRect.IsEmpty() && !surface) {
+#ifdef DEBUG_DUMP_SURFACES
+ printf(" -- no input --</section>\n\n");
+#endif
+ return nullptr;
+ }
+
+ if (aTransparencyPaddedSourceRect &&
+ !aTransparencyPaddedSourceRect->IsEmpty()) {
+ IntRect srcRect = aTransparencyPaddedSourceRect->Intersect(aRect);
+ surface =
+ GetDataSurfaceInRect(surface, surfaceRect, srcRect, EDGE_MODE_NONE);
+ if (surface) {
+ surfaceRect = srcRect;
+ } else {
+ // Padding the surface with transparency failed, probably due to size
+ // restrictions. Since |surface| is now null, set the surfaceRect to
+ // empty so that we're consistent.
+ surfaceRect.SetEmpty();
+ }
+ }
+
+ RefPtr<DataSourceSurface> result =
+ GetDataSurfaceInRect(surface, surfaceRect, aRect, aEdgeMode);
+
+ if (result) {
+ // TODO: This isn't safe since we don't have a guarantee
+ // that future Maps will have the same stride
+ DataSourceSurface::MappedSurface map;
+ if (result->Map(DataSourceSurface::READ, &map)) {
+ // Unmap immediately since CloneAligned hasn't been updated
+ // to use the Map API yet. We can still read the stride/data
+ // values as long as we don't try to dereference them.
+ result->Unmap();
+ if (map.mStride != GetAlignedStride<16>(map.mStride, 1) ||
+ reinterpret_cast<uintptr_t>(map.mData) % 16 != 0) {
+ // Align unaligned surface.
+ result = CloneAligned(result);
+ }
+ } else {
+ result = nullptr;
+ }
+ }
+
+ if (!result) {
+#ifdef DEBUG_DUMP_SURFACES
+ printf(" -- no input --</section>\n\n");
+#endif
+ return nullptr;
+ }
+
+ SurfaceFormat currentFormat = result->GetFormat();
+ if (DesiredFormat(currentFormat, aFormatHint) == SurfaceFormat::B8G8R8A8 &&
+ currentFormat != SurfaceFormat::B8G8R8A8) {
+ result = FilterProcessing::ConvertToB8G8R8A8(result);
+ }
+
+#ifdef DEBUG_DUMP_SURFACES
+ printf("<img src='");
+ gfxUtils::DumpAsDataURL(result);
+ printf("'></section>");
+#endif
+
+ MOZ_ASSERT(!result || result->GetSize() == aRect.Size(),
+ "wrong surface size");
+
+ return result.forget();
+}
+
+IntRect FilterNodeSoftware::GetInputRectInRect(uint32_t aInputEnumIndex,
+ const IntRect& aInRect) {
+ if (aInRect.Overflows()) {
+ return IntRect();
+ }
+
+ int32_t inputIndex = InputIndex(aInputEnumIndex);
+ if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
+ gfxDevCrash(LogReason::FilterInputRect)
+ << "Invalid rect " << inputIndex << " vs. " << NumberOfSetInputs();
+ return IntRect();
+ }
+ if (mInputSurfaces[inputIndex]) {
+ return aInRect.Intersect(mInputSurfaces[inputIndex]->GetRect());
+ }
+ RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
+ MOZ_ASSERT(filter, "missing input");
+ return filter->GetOutputRectInRect(aInRect);
+}
+
+size_t FilterNodeSoftware::NumberOfSetInputs() {
+ return std::max(mInputSurfaces.size(), mInputFilters.size());
+}
+
+void FilterNodeSoftware::AddInvalidationListener(
+ FilterInvalidationListener* aListener) {
+ MOZ_ASSERT(aListener, "null listener");
+ mInvalidationListeners.push_back(aListener);
+}
+
+void FilterNodeSoftware::RemoveInvalidationListener(
+ FilterInvalidationListener* aListener) {
+ MOZ_ASSERT(aListener, "null listener");
+ std::vector<FilterInvalidationListener*>::iterator it = std::find(
+ mInvalidationListeners.begin(), mInvalidationListeners.end(), aListener);
+ mInvalidationListeners.erase(it);
+}
+
+void FilterNodeSoftware::FilterInvalidated(FilterNodeSoftware* aFilter) {
+ Invalidate();
+}
+
+void FilterNodeSoftware::Invalidate() {
+ mCachedOutput = nullptr;
+ mCachedRect = IntRect();
+ for (std::vector<FilterInvalidationListener*>::iterator it =
+ mInvalidationListeners.begin();
+ it != mInvalidationListeners.end(); it++) {
+ (*it)->FilterInvalidated(this);
+ }
+}
+
+FilterNodeSoftware::FilterNodeSoftware() {}
+
+FilterNodeSoftware::~FilterNodeSoftware() {
+ MOZ_ASSERT(
+ mInvalidationListeners.empty(),
+ "All invalidation listeners should have unsubscribed themselves by now!");
+
+ for (std::vector<RefPtr<FilterNodeSoftware> >::iterator it =
+ mInputFilters.begin();
+ it != mInputFilters.end(); it++) {
+ if (*it) {
+ (*it)->RemoveInvalidationListener(this);
+ }
+ }
+}
+
+void FilterNodeSoftware::SetInput(uint32_t aIndex, FilterNode* aFilter) {
+ if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_SOFTWARE) {
+ MOZ_ASSERT(false, "can only take software filters as inputs");
+ return;
+ }
+ SetInput(aIndex, nullptr, static_cast<FilterNodeSoftware*>(aFilter));
+}
+
+void FilterNodeSoftware::SetInput(uint32_t aIndex, SourceSurface* aSurface) {
+ SetInput(aIndex, aSurface, nullptr);
+}
+
+void FilterNodeSoftware::SetInput(uint32_t aInputEnumIndex,
+ SourceSurface* aSurface,
+ FilterNodeSoftware* aFilter) {
+ int32_t inputIndex = InputIndex(aInputEnumIndex);
+ if (inputIndex < 0) {
+ gfxDevCrash(LogReason::FilterInputSet) << "Invalid set " << inputIndex;
+ return;
+ }
+ if ((uint32_t)inputIndex >= NumberOfSetInputs()) {
+ mInputSurfaces.resize(inputIndex + 1);
+ mInputFilters.resize(inputIndex + 1);
+ }
+ mInputSurfaces[inputIndex] = aSurface;
+ if (mInputFilters[inputIndex]) {
+ mInputFilters[inputIndex]->RemoveInvalidationListener(this);
+ }
+ if (aFilter) {
+ aFilter->AddInvalidationListener(this);
+ }
+ mInputFilters[inputIndex] = aFilter;
+ if (!aSurface && !aFilter && (size_t)inputIndex == NumberOfSetInputs()) {
+ mInputSurfaces.resize(inputIndex);
+ mInputFilters.resize(inputIndex);
+ }
+ Invalidate();
+}
+
+FilterNodeBlendSoftware::FilterNodeBlendSoftware()
+ : mBlendMode(BLEND_MODE_MULTIPLY) {}
+
+int32_t FilterNodeBlendSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_BLEND_IN:
+ return 0;
+ case IN_BLEND_IN2:
+ return 1;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeBlendSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aBlendMode) {
+ MOZ_ASSERT(aIndex == ATT_BLEND_BLENDMODE);
+ mBlendMode = static_cast<BlendMode>(aBlendMode);
+ Invalidate();
+}
+
+static CompositionOp ToBlendOp(BlendMode aOp) {
+ switch (aOp) {
+ case BLEND_MODE_MULTIPLY:
+ return CompositionOp::OP_MULTIPLY;
+ case BLEND_MODE_SCREEN:
+ return CompositionOp::OP_SCREEN;
+ case BLEND_MODE_OVERLAY:
+ return CompositionOp::OP_OVERLAY;
+ case BLEND_MODE_DARKEN:
+ return CompositionOp::OP_DARKEN;
+ case BLEND_MODE_LIGHTEN:
+ return CompositionOp::OP_LIGHTEN;
+ case BLEND_MODE_COLOR_DODGE:
+ return CompositionOp::OP_COLOR_DODGE;
+ case BLEND_MODE_COLOR_BURN:
+ return CompositionOp::OP_COLOR_BURN;
+ case BLEND_MODE_HARD_LIGHT:
+ return CompositionOp::OP_HARD_LIGHT;
+ case BLEND_MODE_SOFT_LIGHT:
+ return CompositionOp::OP_SOFT_LIGHT;
+ case BLEND_MODE_DIFFERENCE:
+ return CompositionOp::OP_DIFFERENCE;
+ case BLEND_MODE_EXCLUSION:
+ return CompositionOp::OP_EXCLUSION;
+ case BLEND_MODE_HUE:
+ return CompositionOp::OP_HUE;
+ case BLEND_MODE_SATURATION:
+ return CompositionOp::OP_SATURATION;
+ case BLEND_MODE_COLOR:
+ return CompositionOp::OP_COLOR;
+ case BLEND_MODE_LUMINOSITY:
+ return CompositionOp::OP_LUMINOSITY;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unexpected BlendMode");
+ return CompositionOp::OP_OVER;
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeBlendSoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> input1 =
+ GetInputDataSourceSurface(IN_BLEND_IN, aRect, NEED_COLOR_CHANNELS);
+ RefPtr<DataSourceSurface> input2 =
+ GetInputDataSourceSurface(IN_BLEND_IN2, aRect, NEED_COLOR_CHANNELS);
+
+ // Null inputs need to be treated as transparent.
+
+ // First case: both are transparent.
+ if (!input1 && !input2) {
+ // Then the result is transparent, too.
+ return nullptr;
+ }
+
+ // Second case: one of them is transparent. Return the non-transparent one.
+ if (!input1 || !input2) {
+ return input1 ? input1.forget() : input2.forget();
+ }
+
+ // Third case: both are non-transparent.
+ // Apply normal filtering.
+ RefPtr<DataSourceSurface> target =
+ FilterProcessing::ApplyBlending(input1, input2, mBlendMode);
+ if (target != nullptr) {
+ return target.forget();
+ }
+
+ IntSize size = input1->GetSize();
+ target = Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ CopyRect(input1, target, IntRect(IntPoint(), size), IntPoint());
+
+ // This needs to stay in scope until the draw target has been flushed.
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::READ_WRITE);
+ if (MOZ2D_WARN_IF(!targetMap.IsMapped())) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::SKIA, targetMap.GetData(), target->GetSize(),
+ targetMap.GetStride(), target->GetFormat());
+
+ if (!dt) {
+ gfxWarning()
+ << "FilterNodeBlendSoftware::Render failed in CreateDrawTargetForData";
+ return nullptr;
+ }
+
+ Rect r(0, 0, size.width, size.height);
+ dt->DrawSurface(input2, r, r, DrawSurfaceOptions(),
+ DrawOptions(1.0f, ToBlendOp(mBlendMode)));
+ dt->Flush();
+ return target.forget();
+}
+
+void FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect& aRect) {
+ RequestInputRect(IN_BLEND_IN, aRect);
+ RequestInputRect(IN_BLEND_IN2, aRect);
+}
+
+IntRect FilterNodeBlendSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ IntRect result = MapInputRectToSource(IN_BLEND_IN, aRect, aMax, aSourceNode);
+ result.OrWith(MapInputRectToSource(IN_BLEND_IN2, aRect, aMax, aSourceNode));
+ return result;
+}
+
+IntRect FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ return GetInputRectInRect(IN_BLEND_IN, aRect)
+ .Union(GetInputRectInRect(IN_BLEND_IN2, aRect))
+ .Intersect(aRect);
+}
+
+FilterNodeTransformSoftware::FilterNodeTransformSoftware()
+ : mSamplingFilter(SamplingFilter::GOOD) {}
+
+int32_t FilterNodeTransformSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_TRANSFORM_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aFilter) {
+ MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER);
+ mSamplingFilter = static_cast<SamplingFilter>(aFilter);
+ Invalidate();
+}
+
+void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex,
+ const Matrix& aMatrix) {
+ MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX);
+ mMatrix = aMatrix;
+ Invalidate();
+}
+
+IntRect FilterNodeTransformSoftware::SourceRectForOutputRect(
+ const IntRect& aRect) {
+ if (aRect.IsEmpty()) {
+ return IntRect();
+ }
+
+ Matrix inverted(mMatrix);
+ if (!inverted.Invert()) {
+ return IntRect();
+ }
+
+ Rect neededRect = inverted.TransformBounds(Rect(aRect));
+ neededRect.RoundOut();
+ IntRect neededIntRect;
+ if (!neededRect.ToIntRect(&neededIntRect)) {
+ return IntRect();
+ }
+ return GetInputRectInRect(IN_TRANSFORM_IN, neededIntRect);
+}
+
+IntRect FilterNodeTransformSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ if (aRect.IsEmpty()) {
+ return IntRect();
+ }
+
+ Matrix inverted(mMatrix);
+ if (!inverted.Invert()) {
+ return aMax;
+ }
+
+ Rect neededRect = inverted.TransformBounds(Rect(aRect));
+ neededRect.RoundOut();
+ IntRect neededIntRect;
+ if (!neededRect.ToIntRect(&neededIntRect)) {
+ return aMax;
+ }
+ return MapInputRectToSource(IN_TRANSFORM_IN, neededIntRect, aMax,
+ aSourceNode);
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeTransformSoftware::Render(
+ const IntRect& aRect) {
+ IntRect srcRect = SourceRectForOutputRect(aRect);
+
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_TRANSFORM_IN, srcRect);
+
+ if (!input) {
+ return nullptr;
+ }
+
+ Matrix transform = Matrix::Translation(srcRect.X(), srcRect.Y()) * mMatrix *
+ Matrix::Translation(-aRect.X(), -aRect.Y());
+ if (transform.IsIdentity() && srcRect.Size() == aRect.Size()) {
+ return input.forget();
+ }
+
+ RefPtr<DataSourceSurface> surf =
+ Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat(), true);
+
+ if (!surf) {
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface mapping;
+ if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
+ gfxCriticalError()
+ << "FilterNodeTransformSoftware::Render failed to map surface";
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::SKIA, mapping.mData, surf->GetSize(), mapping.mStride,
+ surf->GetFormat());
+ if (!dt) {
+ gfxWarning() << "FilterNodeTransformSoftware::Render failed in "
+ "CreateDrawTargetForData";
+ return nullptr;
+ }
+
+ Rect r(0, 0, srcRect.Width(), srcRect.Height());
+ dt->SetTransform(transform);
+ dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter));
+
+ dt->Flush();
+ surf->Unmap();
+ return surf.forget();
+}
+
+void FilterNodeTransformSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect));
+}
+
+IntRect FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ IntRect srcRect = SourceRectForOutputRect(aRect);
+ if (srcRect.IsEmpty()) {
+ return IntRect();
+ }
+
+ Rect outRect = mMatrix.TransformBounds(Rect(srcRect));
+ outRect.RoundOut();
+ IntRect outIntRect;
+ if (!outRect.ToIntRect(&outIntRect)) {
+ return IntRect();
+ }
+ return outIntRect.Intersect(aRect);
+}
+
+FilterNodeMorphologySoftware::FilterNodeMorphologySoftware()
+ : mOperator(MORPHOLOGY_OPERATOR_ERODE) {}
+
+int32_t FilterNodeMorphologySoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_MORPHOLOGY_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex,
+ const IntSize& aRadii) {
+ MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII);
+ mRadii.width = std::min(std::max(aRadii.width, 0), 100000);
+ mRadii.height = std::min(std::max(aRadii.height, 0), 100000);
+ Invalidate();
+}
+
+void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aOperator) {
+ MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR);
+ mOperator = static_cast<MorphologyOperator>(aOperator);
+ Invalidate();
+}
+
+static already_AddRefed<DataSourceSurface> ApplyMorphology(
+ const IntRect& aSourceRect, DataSourceSurface* aInput,
+ const IntRect& aDestRect, int32_t rx, int32_t ry,
+ MorphologyOperator aOperator) {
+ IntRect srcRect = aSourceRect - aDestRect.TopLeft();
+ IntRect destRect = aDestRect - aDestRect.TopLeft();
+ IntRect tmpRect(destRect.X(), srcRect.Y(), destRect.Width(),
+ srcRect.Height());
+#ifdef DEBUG
+ IntMargin margin = srcRect - destRect;
+ MOZ_ASSERT(margin.top >= ry && margin.right >= rx && margin.bottom >= ry &&
+ margin.left >= rx,
+ "insufficient margin");
+#endif
+
+ RefPtr<DataSourceSurface> tmp;
+ if (rx == 0) {
+ tmp = aInput;
+ } else {
+ tmp = Factory::CreateDataSourceSurface(tmpRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!tmp)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !tmpMap.IsMapped())) {
+ return nullptr;
+ }
+ uint8_t* sourceData = DataAtOffset(aInput, sourceMap.GetMappedSurface(),
+ destRect.TopLeft() - srcRect.TopLeft());
+ uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(),
+ destRect.TopLeft() - tmpRect.TopLeft());
+
+ FilterProcessing::ApplyMorphologyHorizontal(
+ sourceData, sourceMap.GetStride(), tmpData, tmpMap.GetStride(), tmpRect,
+ rx, aOperator);
+ }
+
+ RefPtr<DataSourceSurface> dest;
+ if (ry == 0) {
+ dest = tmp;
+ } else {
+ dest = Factory::CreateDataSourceSurface(destRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!dest)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap destMap(dest, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!tmpMap.IsMapped() || !destMap.IsMapped())) {
+ return nullptr;
+ }
+ int32_t tmpStride = tmpMap.GetStride();
+ uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(),
+ destRect.TopLeft() - tmpRect.TopLeft());
+
+ int32_t destStride = destMap.GetStride();
+ uint8_t* destData = destMap.GetData();
+
+ FilterProcessing::ApplyMorphologyVertical(
+ tmpData, tmpStride, destData, destStride, destRect, ry, aOperator);
+ }
+
+ return dest.forget();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeMorphologySoftware::Render(
+ const IntRect& aRect) {
+ IntRect srcRect = aRect;
+ srcRect.Inflate(mRadii);
+
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_MORPHOLOGY_IN, srcRect, NEED_COLOR_CHANNELS);
+ if (!input) {
+ return nullptr;
+ }
+
+ int32_t rx = mRadii.width;
+ int32_t ry = mRadii.height;
+
+ if (rx == 0 && ry == 0) {
+ return input.forget();
+ }
+
+ return ApplyMorphology(srcRect, input, aRect, rx, ry, mOperator);
+}
+
+void FilterNodeMorphologySoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ IntRect srcRect = aRect;
+ srcRect.Inflate(mRadii);
+ RequestInputRect(IN_MORPHOLOGY_IN, srcRect);
+}
+
+IntRect FilterNodeMorphologySoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ IntRect inflatedSourceRect = aRect;
+ inflatedSourceRect.Inflate(mRadii);
+ IntRect inputRect = GetInputRectInRect(IN_MORPHOLOGY_IN, inflatedSourceRect);
+ if (mOperator == MORPHOLOGY_OPERATOR_ERODE) {
+ inputRect.Deflate(mRadii);
+ } else {
+ inputRect.Inflate(mRadii);
+ }
+ return inputRect.Intersect(aRect);
+}
+
+int32_t FilterNodeColorMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_COLOR_MATRIX_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex,
+ const Matrix5x4& aMatrix) {
+ MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_MATRIX);
+ mMatrix = aMatrix;
+ Invalidate();
+}
+
+void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aAlphaMode) {
+ MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_ALPHA_MODE);
+ mAlphaMode = (AlphaMode)aAlphaMode;
+ Invalidate();
+}
+
+static already_AddRefed<DataSourceSurface> Premultiply(
+ DataSourceSurface* aSurface) {
+ if (aSurface->GetFormat() == SurfaceFormat::A8) {
+ RefPtr<DataSourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ IntSize size = aSurface->GetSize();
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) {
+ return nullptr;
+ }
+
+ uint8_t* inputData = inputMap.GetData();
+ int32_t inputStride = inputMap.GetStride();
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ FilterProcessing::DoPremultiplicationCalculation(
+ size, targetData, targetStride, inputData, inputStride);
+
+ return target.forget();
+}
+
+static already_AddRefed<DataSourceSurface> Unpremultiply(
+ DataSourceSurface* aSurface) {
+ if (aSurface->GetFormat() == SurfaceFormat::A8) {
+ RefPtr<DataSourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ IntSize size = aSurface->GetSize();
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) {
+ return nullptr;
+ }
+
+ uint8_t* inputData = inputMap.GetData();
+ int32_t inputStride = inputMap.GetStride();
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ FilterProcessing::DoUnpremultiplicationCalculation(
+ size, targetData, targetStride, inputData, inputStride);
+
+ return target.forget();
+}
+
+static already_AddRefed<DataSourceSurface> Opacity(DataSourceSurface* aSurface,
+ Float aValue) {
+ if (aValue == 1.0f) {
+ RefPtr<DataSourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ IntSize size = aSurface->GetSize();
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, aSurface->GetFormat());
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) {
+ return nullptr;
+ }
+
+ uint8_t* inputData = inputMap.GetData();
+ int32_t inputStride = inputMap.GetStride();
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ if (aSurface->GetFormat() == SurfaceFormat::A8) {
+ FilterProcessing::DoOpacityCalculationA8(size, targetData, targetStride,
+ inputData, inputStride, aValue);
+ } else {
+ MOZ_ASSERT(aSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
+ FilterProcessing::DoOpacityCalculation(size, targetData, targetStride,
+ inputData, inputStride, aValue);
+ }
+
+ return target.forget();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeColorMatrixSoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_COLOR_MATRIX_IN, aRect, NEED_COLOR_CHANNELS);
+ if (!input) {
+ return nullptr;
+ }
+
+ if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) {
+ input = Unpremultiply(input);
+ }
+
+ RefPtr<DataSourceSurface> result =
+ FilterProcessing::ApplyColorMatrix(input, mMatrix);
+
+ if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) {
+ result = Premultiply(result);
+ }
+
+ return result.forget();
+}
+
+void FilterNodeColorMatrixSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_COLOR_MATRIX_IN, aRect);
+}
+
+IntRect FilterNodeColorMatrixSoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_COLOR_MATRIX_IN, aRect, aMax, aSourceNode);
+}
+
+IntRect FilterNodeColorMatrixSoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ if (mMatrix._54 > 0.0f) {
+ return aRect;
+ }
+ return GetInputRectInRect(IN_COLOR_MATRIX_IN, aRect);
+}
+
+void FilterNodeFloodSoftware::SetAttribute(uint32_t aIndex,
+ const DeviceColor& aColor) {
+ MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR);
+ mColor = aColor;
+ Invalidate();
+}
+
+static uint32_t ColorToBGRA(const DeviceColor& aColor) {
+ union {
+ uint32_t color;
+ uint8_t components[4];
+ };
+ components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
+ NS_lround(aColor.r * aColor.a * 255.0f);
+ components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
+ NS_lround(aColor.g * aColor.a * 255.0f);
+ components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
+ NS_lround(aColor.b * aColor.a * 255.0f);
+ components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = NS_lround(aColor.a * 255.0f);
+ return color;
+}
+
+static SurfaceFormat FormatForColor(DeviceColor aColor) {
+ if (aColor.r == 0 && aColor.g == 0 && aColor.b == 0) {
+ return SurfaceFormat::A8;
+ }
+ return SurfaceFormat::B8G8R8A8;
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeFloodSoftware::Render(
+ const IntRect& aRect) {
+ SurfaceFormat format = FormatForColor(mColor);
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(aRect.Size(), format);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!targetMap.IsMapped())) {
+ return nullptr;
+ }
+
+ uint8_t* targetData = targetMap.GetData();
+ int32_t stride = targetMap.GetStride();
+
+ if (format == SurfaceFormat::B8G8R8A8) {
+ uint32_t color = ColorToBGRA(mColor);
+ for (int32_t y = 0; y < aRect.Height(); y++) {
+ for (int32_t x = 0; x < aRect.Width(); x++) {
+ *((uint32_t*)targetData + x) = color;
+ }
+ PodZero(&targetData[aRect.Width() * 4], stride - aRect.Width() * 4);
+ targetData += stride;
+ }
+ } else if (format == SurfaceFormat::A8) {
+ uint8_t alpha = NS_lround(mColor.a * 255.0f);
+ for (int32_t y = 0; y < aRect.Height(); y++) {
+ for (int32_t x = 0; x < aRect.Width(); x++) {
+ targetData[x] = alpha;
+ }
+ PodZero(&targetData[aRect.Width()], stride - aRect.Width());
+ targetData += stride;
+ }
+ } else {
+ gfxDevCrash(LogReason::FilterInputFormat)
+ << "Bad format in flood render " << (int)format;
+ return nullptr;
+ }
+
+ return target.forget();
+}
+
+// Override GetOutput to get around caching. Rendering simple floods is
+// comparatively fast.
+already_AddRefed<DataSourceSurface> FilterNodeFloodSoftware::GetOutput(
+ const IntRect& aRect) {
+ return Render(aRect);
+}
+
+IntRect FilterNodeFloodSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ return IntRect();
+}
+
+IntRect FilterNodeFloodSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ if (mColor.a == 0.0f) {
+ return IntRect();
+ }
+ return aRect;
+}
+
+int32_t FilterNodeTileSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_TILE_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeTileSoftware::SetAttribute(uint32_t aIndex,
+ const IntRect& aSourceRect) {
+ MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT);
+ mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()),
+ int32_t(aSourceRect.Width()),
+ int32_t(aSourceRect.Height()));
+ Invalidate();
+}
+
+namespace {
+struct CompareIntRects {
+ bool operator()(const IntRect& a, const IntRect& b) const {
+ if (a.X() != b.X()) {
+ return a.X() < b.X();
+ }
+ if (a.Y() != b.Y()) {
+ return a.Y() < b.Y();
+ }
+ if (a.Width() != b.Width()) {
+ return a.Width() < b.Width();
+ }
+ return a.Height() < b.Height();
+ }
+};
+
+} // namespace
+
+already_AddRefed<DataSourceSurface> FilterNodeTileSoftware::Render(
+ const IntRect& aRect) {
+ if (mSourceRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (mSourceRect.Contains(aRect)) {
+ return GetInputDataSourceSurface(IN_TILE_IN, aRect);
+ }
+
+ RefPtr<DataSourceSurface> target;
+
+ typedef std::map<IntRect, RefPtr<DataSourceSurface>, CompareIntRects>
+ InputMap;
+ InputMap inputs;
+
+ IntPoint startIndex = TileIndex(mSourceRect, aRect.TopLeft());
+ IntPoint endIndex = TileIndex(mSourceRect, aRect.BottomRight());
+ for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) {
+ for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) {
+ IntPoint sourceToDestOffset(ix * mSourceRect.Width(),
+ iy * mSourceRect.Height());
+ IntRect destRect = aRect.Intersect(mSourceRect + sourceToDestOffset);
+ IntRect srcRect = destRect - sourceToDestOffset;
+ if (srcRect.IsEmpty()) {
+ continue;
+ }
+
+ RefPtr<DataSourceSurface> input;
+ InputMap::iterator it = inputs.find(srcRect);
+ if (it == inputs.end()) {
+ input = GetInputDataSourceSurface(IN_TILE_IN, srcRect);
+ inputs[srcRect] = input;
+ } else {
+ input = it->second;
+ }
+ if (!input) {
+ return nullptr;
+ }
+ if (!target) {
+ // We delay creating the target until now because we want to use the
+ // same format as our input filter, and we do not actually know the
+ // input format before we call GetInputDataSourceSurface.
+ target =
+ Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat());
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+ }
+
+ if (input->GetFormat() != target->GetFormat()) {
+ // Different rectangles of the input can have different formats. If
+ // that happens, just convert everything to B8G8R8A8.
+ target = FilterProcessing::ConvertToB8G8R8A8(target);
+ input = FilterProcessing::ConvertToB8G8R8A8(input);
+ if (MOZ2D_WARN_IF(!target) || MOZ2D_WARN_IF(!input)) {
+ return nullptr;
+ }
+ }
+
+ CopyRect(input, target, srcRect - srcRect.TopLeft(),
+ destRect.TopLeft() - aRect.TopLeft());
+ }
+ }
+
+ return target.forget();
+}
+
+void FilterNodeTileSoftware::RequestFromInputsForRect(const IntRect& aRect) {
+ // Do not request anything.
+ // Source rects for the tile filter can be discontinuous with large gaps
+ // between them. Requesting those from our input filter might cause it to
+ // render the whole bounding box of all of them, which would be wasteful.
+}
+
+IntRect FilterNodeTileSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ return aRect;
+}
+
+FilterNodeComponentTransferSoftware::FilterNodeComponentTransferSoftware()
+ : mDisableR(true), mDisableG(true), mDisableB(true), mDisableA(true) {}
+
+void FilterNodeComponentTransferSoftware::SetAttribute(uint32_t aIndex,
+ bool aDisable) {
+ switch (aIndex) {
+ case ATT_TRANSFER_DISABLE_R:
+ mDisableR = aDisable;
+ break;
+ case ATT_TRANSFER_DISABLE_G:
+ mDisableG = aDisable;
+ break;
+ case ATT_TRANSFER_DISABLE_B:
+ mDisableB = aDisable;
+ break;
+ case ATT_TRANSFER_DISABLE_A:
+ mDisableA = aDisable;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeComponentTransferSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeComponentTransferSoftware::GenerateLookupTable(
+ ptrdiff_t aComponent, uint8_t aTables[4][256], bool aDisabled) {
+ if (aDisabled) {
+ for (int32_t i = 0; i < 256; ++i) {
+ aTables[aComponent][i] = i;
+ }
+ } else {
+ FillLookupTable(aComponent, aTables[aComponent]);
+ }
+}
+
+template <uint32_t BytesPerPixel>
+static void TransferComponents(
+ DataSourceSurface* aInput, DataSourceSurface* aTarget,
+ const uint8_t aLookupTables[BytesPerPixel][256]) {
+ MOZ_ASSERT(aInput->GetFormat() == aTarget->GetFormat(), "different formats");
+ IntSize size = aInput->GetSize();
+
+ DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(aTarget, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !targetMap.IsMapped())) {
+ return;
+ }
+
+ uint8_t* sourceData = sourceMap.GetData();
+ int32_t sourceStride = sourceMap.GetStride();
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ MOZ_ASSERT(sourceStride <= targetStride, "target smaller than source");
+
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x++) {
+ uint32_t sourceIndex = y * sourceStride + x * BytesPerPixel;
+ uint32_t targetIndex = y * targetStride + x * BytesPerPixel;
+ for (uint32_t i = 0; i < BytesPerPixel; i++) {
+ targetData[targetIndex + i] =
+ aLookupTables[i][sourceData[sourceIndex + i]];
+ }
+ }
+
+ // Zero padding to keep valgrind happy.
+ PodZero(&targetData[y * targetStride + size.width * BytesPerPixel],
+ targetStride - size.width * BytesPerPixel);
+ }
+}
+
+static bool IsAllZero(const uint8_t aLookupTable[256]) {
+ for (int32_t i = 0; i < 256; i++) {
+ if (aLookupTable[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeComponentTransferSoftware::Render(
+ const IntRect& aRect) {
+ if (mDisableR && mDisableG && mDisableB && mDisableA) {
+ return GetInputDataSourceSurface(IN_TRANSFER_IN, aRect);
+ }
+
+ uint8_t lookupTables[4][256];
+ GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_R, lookupTables, mDisableR);
+ GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_G, lookupTables, mDisableG);
+ GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_B, lookupTables, mDisableB);
+ GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_A, lookupTables, mDisableA);
+
+ bool needColorChannels =
+ lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_R][0] != 0 ||
+ lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_G][0] != 0 ||
+ lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_B][0] != 0;
+
+ FormatHint pref = needColorChannels ? NEED_COLOR_CHANNELS : CAN_HANDLE_A8;
+
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_TRANSFER_IN, aRect, pref);
+ if (!input) {
+ return nullptr;
+ }
+
+ if (input->GetFormat() == SurfaceFormat::B8G8R8A8 && !needColorChannels) {
+ bool colorChannelsBecomeBlack =
+ IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) &&
+ IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) &&
+ IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_B]);
+
+ if (colorChannelsBecomeBlack) {
+ input = FilterProcessing::ExtractAlpha(input);
+ }
+ }
+
+ SurfaceFormat format = input->GetFormat();
+ if (format == SurfaceFormat::A8 && mDisableA) {
+ return input.forget();
+ }
+
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(aRect.Size(), format);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ if (format == SurfaceFormat::A8) {
+ TransferComponents<1>(input, target,
+ &lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_A]);
+ } else {
+ TransferComponents<4>(input, target, lookupTables);
+ }
+
+ return target.forget();
+}
+
+void FilterNodeComponentTransferSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_TRANSFER_IN, aRect);
+}
+
+IntRect FilterNodeComponentTransferSoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_TRANSFER_IN, aRect, aMax, aSourceNode);
+}
+
+IntRect FilterNodeComponentTransferSoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ if (mDisableA) {
+ return GetInputRectInRect(IN_TRANSFER_IN, aRect);
+ }
+ return aRect;
+}
+
+int32_t FilterNodeComponentTransferSoftware::InputIndex(
+ uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_TRANSFER_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeTableTransferSoftware::SetAttribute(uint32_t aIndex,
+ const Float* aFloat,
+ uint32_t aSize) {
+ std::vector<Float> table(aFloat, aFloat + aSize);
+ switch (aIndex) {
+ case ATT_TABLE_TRANSFER_TABLE_R:
+ mTableR = table;
+ break;
+ case ATT_TABLE_TRANSFER_TABLE_G:
+ mTableG = table;
+ break;
+ case ATT_TABLE_TRANSFER_TABLE_B:
+ mTableB = table;
+ break;
+ case ATT_TABLE_TRANSFER_TABLE_A:
+ mTableA = table;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeTableTransferSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
+ uint8_t aTable[256]) {
+ switch (aComponent) {
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
+ FillLookupTableImpl(mTableR, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
+ FillLookupTableImpl(mTableG, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
+ FillLookupTableImpl(mTableB, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
+ FillLookupTableImpl(mTableA, aTable);
+ break;
+ default:
+ MOZ_ASSERT(false, "unknown component");
+ break;
+ }
+}
+
+void FilterNodeTableTransferSoftware::FillLookupTableImpl(
+ std::vector<Float>& aTableValues, uint8_t aTable[256]) {
+ uint32_t tvLength = aTableValues.size();
+ if (tvLength < 2) {
+ return;
+ }
+
+ for (size_t i = 0; i < 256; i++) {
+ uint32_t k = (i * (tvLength - 1)) / 255;
+ Float v1 = aTableValues[k];
+ Float v2 = aTableValues[std::min(k + 1, tvLength - 1)];
+ int32_t val = int32_t(255 * (v1 + (i / 255.0f - k / float(tvLength - 1)) *
+ (tvLength - 1) * (v2 - v1)));
+ val = std::min(255, val);
+ val = std::max(0, val);
+ aTable[i] = val;
+ }
+}
+
+void FilterNodeDiscreteTransferSoftware::SetAttribute(uint32_t aIndex,
+ const Float* aFloat,
+ uint32_t aSize) {
+ std::vector<Float> discrete(aFloat, aFloat + aSize);
+ switch (aIndex) {
+ case ATT_DISCRETE_TRANSFER_TABLE_R:
+ mTableR = discrete;
+ break;
+ case ATT_DISCRETE_TRANSFER_TABLE_G:
+ mTableG = discrete;
+ break;
+ case ATT_DISCRETE_TRANSFER_TABLE_B:
+ mTableB = discrete;
+ break;
+ case ATT_DISCRETE_TRANSFER_TABLE_A:
+ mTableA = discrete;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeDiscreteTransferSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
+ uint8_t aTable[256]) {
+ switch (aComponent) {
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
+ FillLookupTableImpl(mTableR, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
+ FillLookupTableImpl(mTableG, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
+ FillLookupTableImpl(mTableB, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
+ FillLookupTableImpl(mTableA, aTable);
+ break;
+ default:
+ MOZ_ASSERT(false, "unknown component");
+ break;
+ }
+}
+
+void FilterNodeDiscreteTransferSoftware::FillLookupTableImpl(
+ std::vector<Float>& aTableValues, uint8_t aTable[256]) {
+ uint32_t tvLength = aTableValues.size();
+ if (tvLength < 1) {
+ return;
+ }
+
+ for (size_t i = 0; i < 256; i++) {
+ uint32_t k = (i * tvLength) / 255;
+ k = std::min(k, tvLength - 1);
+ Float v = aTableValues[k];
+ int32_t val = NS_lround(255 * v);
+ val = std::min(255, val);
+ val = std::max(0, val);
+ aTable[i] = val;
+ }
+}
+
+FilterNodeLinearTransferSoftware::FilterNodeLinearTransferSoftware()
+ : mSlopeR(0),
+ mSlopeG(0),
+ mSlopeB(0),
+ mSlopeA(0),
+ mInterceptR(0),
+ mInterceptG(0),
+ mInterceptB(0),
+ mInterceptA(0) {}
+
+void FilterNodeLinearTransferSoftware::SetAttribute(uint32_t aIndex,
+ Float aValue) {
+ switch (aIndex) {
+ case ATT_LINEAR_TRANSFER_SLOPE_R:
+ mSlopeR = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_INTERCEPT_R:
+ mInterceptR = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_SLOPE_G:
+ mSlopeG = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_INTERCEPT_G:
+ mInterceptG = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_SLOPE_B:
+ mSlopeB = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_INTERCEPT_B:
+ mInterceptB = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_SLOPE_A:
+ mSlopeA = aValue;
+ break;
+ case ATT_LINEAR_TRANSFER_INTERCEPT_A:
+ mInterceptA = aValue;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeLinearTransferSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
+ uint8_t aTable[256]) {
+ switch (aComponent) {
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
+ FillLookupTableImpl(mSlopeR, mInterceptR, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
+ FillLookupTableImpl(mSlopeG, mInterceptG, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
+ FillLookupTableImpl(mSlopeB, mInterceptB, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
+ FillLookupTableImpl(mSlopeA, mInterceptA, aTable);
+ break;
+ default:
+ MOZ_ASSERT(false, "unknown component");
+ break;
+ }
+}
+
+void FilterNodeLinearTransferSoftware::FillLookupTableImpl(
+ Float aSlope, Float aIntercept, uint8_t aTable[256]) {
+ for (size_t i = 0; i < 256; i++) {
+ int32_t val = NS_lround(aSlope * i + 255 * aIntercept);
+ val = std::min(255, val);
+ val = std::max(0, val);
+ aTable[i] = val;
+ }
+}
+
+FilterNodeGammaTransferSoftware::FilterNodeGammaTransferSoftware()
+ : mAmplitudeR(0),
+ mAmplitudeG(0),
+ mAmplitudeB(0),
+ mAmplitudeA(0),
+ mExponentR(0),
+ mExponentG(0),
+ mExponentB(0),
+ mExponentA(0),
+ mOffsetR(0.0),
+ mOffsetG(0.0),
+ mOffsetB(0.0),
+ mOffsetA(0.0) {}
+
+void FilterNodeGammaTransferSoftware::SetAttribute(uint32_t aIndex,
+ Float aValue) {
+ switch (aIndex) {
+ case ATT_GAMMA_TRANSFER_AMPLITUDE_R:
+ mAmplitudeR = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_EXPONENT_R:
+ mExponentR = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_OFFSET_R:
+ mOffsetR = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_AMPLITUDE_G:
+ mAmplitudeG = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_EXPONENT_G:
+ mExponentG = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_OFFSET_G:
+ mOffsetG = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_AMPLITUDE_B:
+ mAmplitudeB = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_EXPONENT_B:
+ mExponentB = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_OFFSET_B:
+ mOffsetB = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_AMPLITUDE_A:
+ mAmplitudeA = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_EXPONENT_A:
+ mExponentA = aValue;
+ break;
+ case ATT_GAMMA_TRANSFER_OFFSET_A:
+ mOffsetA = aValue;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeGammaTransferSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
+ uint8_t aTable[256]) {
+ switch (aComponent) {
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
+ FillLookupTableImpl(mAmplitudeR, mExponentR, mOffsetR, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
+ FillLookupTableImpl(mAmplitudeG, mExponentG, mOffsetG, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
+ FillLookupTableImpl(mAmplitudeB, mExponentB, mOffsetB, aTable);
+ break;
+ case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
+ FillLookupTableImpl(mAmplitudeA, mExponentA, mOffsetA, aTable);
+ break;
+ default:
+ MOZ_ASSERT(false, "unknown component");
+ break;
+ }
+}
+
+void FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude,
+ Float aExponent,
+ Float aOffset,
+ uint8_t aTable[256]) {
+ for (size_t i = 0; i < 256; i++) {
+ int32_t val =
+ NS_lround(255 * (aAmplitude * pow(i / 255.0f, aExponent) + aOffset));
+ val = std::min(255, val);
+ val = std::max(0, val);
+ aTable[i] = val;
+ }
+}
+
+FilterNodeConvolveMatrixSoftware::FilterNodeConvolveMatrixSoftware()
+ : mDivisor(0),
+ mBias(0),
+ mEdgeMode(EDGE_MODE_DUPLICATE),
+ mPreserveAlpha(false) {}
+
+int32_t FilterNodeConvolveMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_CONVOLVE_MATRIX_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(
+ uint32_t aIndex, const IntSize& aKernelSize) {
+ MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_SIZE);
+ mKernelSize = aKernelSize;
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
+ const Float* aMatrix,
+ uint32_t aSize) {
+ MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_MATRIX);
+ mKernelMatrix = std::vector<Float>(aMatrix, aMatrix + aSize);
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
+ Float aValue) {
+ switch (aIndex) {
+ case ATT_CONVOLVE_MATRIX_DIVISOR:
+ mDivisor = aValue;
+ break;
+ case ATT_CONVOLVE_MATRIX_BIAS:
+ mBias = aValue;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(
+ uint32_t aIndex, const Size& aKernelUnitLength) {
+ switch (aIndex) {
+ case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH:
+ mKernelUnitLength = aKernelUnitLength;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
+ const IntPoint& aTarget) {
+ MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_TARGET);
+ mTarget = aTarget;
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(
+ uint32_t aIndex, const IntRect& aSourceRect) {
+ MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_SOURCE_RECT);
+ mSourceRect = aSourceRect;
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aEdgeMode) {
+ MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_EDGE_MODE);
+ mEdgeMode = static_cast<ConvolveMatrixEdgeMode>(aEdgeMode);
+ Invalidate();
+}
+
+void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
+ bool aPreserveAlpha) {
+ MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA);
+ mPreserveAlpha = aPreserveAlpha;
+ Invalidate();
+}
+
+#ifdef DEBUG
+static inline void DebugOnlyCheckColorSamplingAccess(
+ const uint8_t* aSampleAddress, const uint8_t* aBoundsBegin,
+ const uint8_t* aBoundsEnd) {
+ MOZ_ASSERT(aSampleAddress >= aBoundsBegin, "accessing before start");
+ MOZ_ASSERT(aSampleAddress < aBoundsEnd, "accessing after end");
+}
+#else
+# define DebugOnlyCheckColorSamplingAccess(address, boundsBegin, boundsEnd)
+#endif
+
+static inline uint8_t ColorComponentAtPoint(const uint8_t* aData,
+ ptrdiff_t aStride,
+ const uint8_t* aBoundsBegin,
+ const uint8_t* aBoundsEnd,
+ int32_t x, int32_t y, ptrdiff_t bpp,
+ ptrdiff_t c) {
+ DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c],
+ aBoundsBegin, aBoundsEnd);
+ return aData[y * aStride + bpp * x + c];
+}
+
+static inline int32_t ColorAtPoint(const uint8_t* aData, ptrdiff_t aStride,
+ const uint8_t* aBoundsBegin,
+ const uint8_t* aBoundsEnd, int32_t x,
+ int32_t y) {
+ DebugOnlyCheckColorSamplingAccess(aData + y * aStride + 4 * x, aBoundsBegin,
+ aBoundsEnd);
+ return *(uint32_t*)(aData + y * aStride + 4 * x);
+}
+
+// Accepts fractional x & y and does bilinear interpolation.
+// Only call this if the pixel (floor(x)+1, floor(y)+1) is accessible.
+static inline uint8_t ColorComponentAtPoint(
+ const uint8_t* aData, ptrdiff_t aStride, const uint8_t* aBoundsBegin,
+ const uint8_t* aBoundsEnd, Float x, Float y, ptrdiff_t bpp, ptrdiff_t c) {
+ const uint32_t f = 256;
+ const int32_t lx = floor(x);
+ const int32_t ly = floor(y);
+ const int32_t tux = uint32_t((x - lx) * f);
+ const int32_t tlx = f - tux;
+ const int32_t tuy = uint32_t((y - ly) * f);
+ const int32_t tly = f - tuy;
+ const uint8_t& cll = ColorComponentAtPoint(aData, aStride, aBoundsBegin,
+ aBoundsEnd, lx, ly, bpp, c);
+ const uint8_t& cul = ColorComponentAtPoint(aData, aStride, aBoundsBegin,
+ aBoundsEnd, lx + 1, ly, bpp, c);
+ const uint8_t& clu = ColorComponentAtPoint(aData, aStride, aBoundsBegin,
+ aBoundsEnd, lx, ly + 1, bpp, c);
+ const uint8_t& cuu = ColorComponentAtPoint(
+ aData, aStride, aBoundsBegin, aBoundsEnd, lx + 1, ly + 1, bpp, c);
+ return ((cll * tlx + cul * tux) * tly + (clu * tlx + cuu * tux) * tuy +
+ f * f / 2) /
+ (f * f);
+}
+
+static int32_t ClampToNonZero(int32_t a) { return a * (a >= 0); }
+
+template <typename CoordType>
+static void ConvolvePixel(const uint8_t* aSourceData, uint8_t* aTargetData,
+ int32_t aWidth, int32_t aHeight,
+ int32_t aSourceStride, int32_t aTargetStride,
+ const uint8_t* aSourceBegin,
+ const uint8_t* aSourceEnd, int32_t aX, int32_t aY,
+ const int32_t* aKernel, int32_t aBias, int32_t shiftL,
+ int32_t shiftR, bool aPreserveAlpha, int32_t aOrderX,
+ int32_t aOrderY, int32_t aTargetX, int32_t aTargetY,
+ CoordType aKernelUnitLengthX,
+ CoordType aKernelUnitLengthY) {
+ int32_t sum[4] = {0, 0, 0, 0};
+ int32_t offsets[4] = {
+ B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G,
+ B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A};
+ int32_t channels = aPreserveAlpha ? 3 : 4;
+ int32_t roundingAddition = shiftL == 0 ? 0 : 1 << (shiftL - 1);
+
+ for (int32_t y = 0; y < aOrderY; y++) {
+ CoordType sampleY = aY + (y - aTargetY) * aKernelUnitLengthY;
+ for (int32_t x = 0; x < aOrderX; x++) {
+ CoordType sampleX = aX + (x - aTargetX) * aKernelUnitLengthX;
+ for (int32_t i = 0; i < channels; i++) {
+ sum[i] +=
+ aKernel[aOrderX * y + x] *
+ ColorComponentAtPoint(aSourceData, aSourceStride, aSourceBegin,
+ aSourceEnd, sampleX, sampleY, 4, offsets[i]);
+ }
+ }
+ }
+ for (int32_t i = 0; i < channels; i++) {
+ int32_t clamped =
+ umin(ClampToNonZero(sum[i] + aBias), 255 << shiftL >> shiftR);
+ aTargetData[aY * aTargetStride + 4 * aX + offsets[i]] =
+ (clamped + roundingAddition) << shiftR >> shiftL;
+ }
+ if (aPreserveAlpha) {
+ aTargetData[aY * aTargetStride + 4 * aX + B8G8R8A8_COMPONENT_BYTEOFFSET_A] =
+ aSourceData[aY * aSourceStride + 4 * aX +
+ B8G8R8A8_COMPONENT_BYTEOFFSET_A];
+ }
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::Render(
+ const IntRect& aRect) {
+ if (mKernelUnitLength.width == floor(mKernelUnitLength.width) &&
+ mKernelUnitLength.height == floor(mKernelUnitLength.height)) {
+ return DoRender(aRect, (int32_t)mKernelUnitLength.width,
+ (int32_t)mKernelUnitLength.height);
+ }
+ return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height);
+}
+
+static std::vector<Float> ReversedVector(const std::vector<Float>& aVector) {
+ size_t length = aVector.size();
+ std::vector<Float> result(length, 0);
+ for (size_t i = 0; i < length; i++) {
+ result[length - 1 - i] = aVector[i];
+ }
+ return result;
+}
+
+static std::vector<Float> ScaledVector(const std::vector<Float>& aVector,
+ Float aDivisor) {
+ size_t length = aVector.size();
+ std::vector<Float> result(length, 0);
+ for (size_t i = 0; i < length; i++) {
+ result[i] = aVector[i] / aDivisor;
+ }
+ return result;
+}
+
+static Float MaxVectorSum(const std::vector<Float>& aVector) {
+ Float sum = 0;
+ size_t length = aVector.size();
+ for (size_t i = 0; i < length; i++) {
+ if (aVector[i] > 0) {
+ sum += aVector[i];
+ }
+ }
+ return sum;
+}
+
+// Returns shiftL and shiftR in such a way that
+// a << shiftL >> shiftR is roughly a * aFloat.
+static void TranslateDoubleToShifts(double aDouble, int32_t& aShiftL,
+ int32_t& aShiftR) {
+ aShiftL = 0;
+ aShiftR = 0;
+ if (aDouble <= 0) {
+ MOZ_CRASH("GFX: TranslateDoubleToShifts");
+ }
+ if (aDouble < 1) {
+ while (1 << (aShiftR + 1) < 1 / aDouble) {
+ aShiftR++;
+ }
+ } else {
+ while (1 << (aShiftL + 1) < aDouble) {
+ aShiftL++;
+ }
+ }
+}
+
+template <typename CoordType>
+already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::DoRender(
+ const IntRect& aRect, CoordType aKernelUnitLengthX,
+ CoordType aKernelUnitLengthY) {
+ if (mKernelSize.width <= 0 || mKernelSize.height <= 0 ||
+ mKernelMatrix.size() !=
+ uint32_t(mKernelSize.width * mKernelSize.height) ||
+ !IntRect(IntPoint(0, 0), mKernelSize).Contains(mTarget) ||
+ mDivisor == 0) {
+ return Factory::CreateDataSourceSurface(aRect.Size(),
+ SurfaceFormat::B8G8R8A8, true);
+ }
+
+ IntRect srcRect = InflatedSourceRect(aRect);
+
+ // Inflate the source rect by another pixel because the bilinear filtering in
+ // ColorComponentAtPoint may want to access the margins.
+ srcRect.Inflate(1);
+
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect,
+ NEED_COLOR_CHANNELS, mEdgeMode, &mSourceRect);
+
+ if (!input) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> target = Factory::CreateDataSourceSurface(
+ aRect.Size(), SurfaceFormat::B8G8R8A8, true);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
+
+ DataSourceSurface::ScopedMap sourceMap(input, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !targetMap.IsMapped())) {
+ return nullptr;
+ }
+
+ uint8_t* sourceData =
+ DataAtOffset(input, sourceMap.GetMappedSurface(), offset);
+ int32_t sourceStride = sourceMap.GetStride();
+ uint8_t* sourceBegin = sourceMap.GetData();
+ uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height;
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ // Why exactly are we reversing the kernel?
+ std::vector<Float> kernel = ReversedVector(mKernelMatrix);
+ kernel = ScaledVector(kernel, mDivisor);
+ Float maxResultAbs = std::max(MaxVectorSum(kernel) + mBias,
+ MaxVectorSum(ScaledVector(kernel, -1)) - mBias);
+ maxResultAbs = std::max(maxResultAbs, 1.0f);
+
+ double idealFactor = INT32_MAX / 2.0 / maxResultAbs / 255.0 * 0.999;
+ MOZ_ASSERT(255.0 * maxResultAbs * idealFactor <= INT32_MAX / 2.0,
+ "badly chosen float-to-int scale");
+ int32_t shiftL, shiftR;
+ TranslateDoubleToShifts(idealFactor, shiftL, shiftR);
+ double factorFromShifts = Float(1 << shiftL) / Float(1 << shiftR);
+ MOZ_ASSERT(255.0 * maxResultAbs * factorFromShifts <= INT32_MAX / 2.0,
+ "badly chosen float-to-int scale");
+
+ int32_t* intKernel = new int32_t[kernel.size()];
+ for (size_t i = 0; i < kernel.size(); i++) {
+ intKernel[i] = NS_lround(kernel[i] * factorFromShifts);
+ }
+ int32_t bias = NS_lround(mBias * 255 * factorFromShifts);
+
+ for (int32_t y = 0; y < aRect.Height(); y++) {
+ for (int32_t x = 0; x < aRect.Width(); x++) {
+ ConvolvePixel(sourceData, targetData, aRect.Width(), aRect.Height(),
+ sourceStride, targetStride, sourceBegin, sourceEnd, x, y,
+ intKernel, bias, shiftL, shiftR, mPreserveAlpha,
+ mKernelSize.width, mKernelSize.height, mTarget.x, mTarget.y,
+ aKernelUnitLengthX, aKernelUnitLengthY);
+ }
+ }
+ delete[] intKernel;
+
+ return target.forget();
+}
+
+void FilterNodeConvolveMatrixSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect));
+}
+
+IntRect FilterNodeConvolveMatrixSoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect),
+ aMax, aSourceNode);
+}
+
+IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect(
+ const IntRect& aDestRect) {
+ if (aDestRect.IsEmpty()) {
+ return IntRect();
+ }
+
+ IntMargin margin;
+ margin.left = ceil(mTarget.x * mKernelUnitLength.width);
+ margin.top = ceil(mTarget.y * mKernelUnitLength.height);
+ margin.right =
+ ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width);
+ margin.bottom =
+ ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height);
+
+ IntRect srcRect = aDestRect;
+ srcRect.Inflate(margin);
+ return srcRect;
+}
+
+IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect(
+ const IntRect& aSourceRect) {
+ if (aSourceRect.IsEmpty()) {
+ return IntRect();
+ }
+
+ IntMargin margin;
+ margin.left =
+ ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width);
+ margin.top =
+ ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height);
+ margin.right = ceil(mTarget.x * mKernelUnitLength.width);
+ margin.bottom = ceil(mTarget.y * mKernelUnitLength.height);
+
+ IntRect destRect = aSourceRect;
+ destRect.Inflate(margin);
+ return destRect;
+}
+
+IntRect FilterNodeConvolveMatrixSoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ IntRect srcRequest = InflatedSourceRect(aRect);
+ IntRect srcOutput = GetInputRectInRect(IN_COLOR_MATRIX_IN, srcRequest);
+ return InflatedDestRect(srcOutput).Intersect(aRect);
+}
+
+FilterNodeDisplacementMapSoftware::FilterNodeDisplacementMapSoftware()
+ : mScale(0.0f), mChannelX(COLOR_CHANNEL_R), mChannelY(COLOR_CHANNEL_G) {}
+
+int32_t FilterNodeDisplacementMapSoftware::InputIndex(
+ uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_DISPLACEMENT_MAP_IN:
+ return 0;
+ case IN_DISPLACEMENT_MAP_IN2:
+ return 1;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
+ Float aScale) {
+ MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE);
+ mScale = aScale;
+ Invalidate();
+}
+
+void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aValue) {
+ switch (aIndex) {
+ case ATT_DISPLACEMENT_MAP_X_CHANNEL:
+ mChannelX = static_cast<ColorChannel>(aValue);
+ break;
+ case ATT_DISPLACEMENT_MAP_Y_CHANNEL:
+ mChannelY = static_cast<ColorChannel>(aValue);
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeDisplacementMapSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeDisplacementMapSoftware::Render(
+ const IntRect& aRect) {
+ IntRect srcRect = InflatedSourceOrDestRect(aRect);
+ RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
+ IN_DISPLACEMENT_MAP_IN, srcRect, NEED_COLOR_CHANNELS);
+ RefPtr<DataSourceSurface> map = GetInputDataSourceSurface(
+ IN_DISPLACEMENT_MAP_IN2, aRect, NEED_COLOR_CHANNELS);
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!(input && map && target))) {
+ return nullptr;
+ }
+
+ IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
+
+ DataSourceSurface::ScopedMap inputMap(input, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap mapMap(map, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!(inputMap.IsMapped() && mapMap.IsMapped() &&
+ targetMap.IsMapped()))) {
+ return nullptr;
+ }
+
+ uint8_t* sourceData =
+ DataAtOffset(input, inputMap.GetMappedSurface(), offset);
+ int32_t sourceStride = inputMap.GetStride();
+ uint8_t* sourceBegin = inputMap.GetData();
+ uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height;
+ uint8_t* mapData = mapMap.GetData();
+ int32_t mapStride = mapMap.GetStride();
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ static const ptrdiff_t channelMap[4] = {
+ B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G,
+ B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A};
+ uint16_t xChannel = channelMap[mChannelX];
+ uint16_t yChannel = channelMap[mChannelY];
+
+ float scaleOver255 = mScale / 255.0f;
+ float scaleAdjustment = -0.5f * mScale;
+
+ for (int32_t y = 0; y < aRect.Height(); y++) {
+ for (int32_t x = 0; x < aRect.Width(); x++) {
+ uint32_t mapIndex = y * mapStride + 4 * x;
+ uint32_t targIndex = y * targetStride + 4 * x;
+ int32_t sourceX =
+ x + scaleOver255 * mapData[mapIndex + xChannel] + scaleAdjustment;
+ int32_t sourceY =
+ y + scaleOver255 * mapData[mapIndex + yChannel] + scaleAdjustment;
+ *(uint32_t*)(targetData + targIndex) = ColorAtPoint(
+ sourceData, sourceStride, sourceBegin, sourceEnd, sourceX, sourceY);
+ }
+
+ // Keep valgrind happy.
+ PodZero(&targetData[y * targetStride + 4 * aRect.Width()],
+ targetStride - 4 * aRect.Width());
+ }
+
+ return target.forget();
+}
+
+void FilterNodeDisplacementMapSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect));
+ RequestInputRect(IN_DISPLACEMENT_MAP_IN2, aRect);
+}
+
+IntRect FilterNodeDisplacementMapSoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ IntRect result =
+ MapInputRectToSource(IN_DISPLACEMENT_MAP_IN,
+ InflatedSourceOrDestRect(aRect), aMax, aSourceNode);
+ result.OrWith(
+ MapInputRectToSource(IN_DISPLACEMENT_MAP_IN2, aRect, aMax, aSourceNode));
+ return result;
+}
+
+IntRect FilterNodeDisplacementMapSoftware::InflatedSourceOrDestRect(
+ const IntRect& aDestOrSourceRect) {
+ IntRect sourceOrDestRect = aDestOrSourceRect;
+ sourceOrDestRect.Inflate(ceil(fabs(mScale) / 2));
+ return sourceOrDestRect;
+}
+
+IntRect FilterNodeDisplacementMapSoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ IntRect srcRequest = InflatedSourceOrDestRect(aRect);
+ IntRect srcOutput = GetInputRectInRect(IN_DISPLACEMENT_MAP_IN, srcRequest);
+ return InflatedSourceOrDestRect(srcOutput).Intersect(aRect);
+}
+
+FilterNodeTurbulenceSoftware::FilterNodeTurbulenceSoftware()
+ : mNumOctaves(0),
+ mSeed(0),
+ mStitchable(false),
+ mType(TURBULENCE_TYPE_TURBULENCE) {}
+
+int32_t FilterNodeTurbulenceSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ return -1;
+}
+
+void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
+ const Size& aBaseFrequency) {
+ switch (aIndex) {
+ case ATT_TURBULENCE_BASE_FREQUENCY:
+ mBaseFrequency = aBaseFrequency;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
+ break;
+ }
+ Invalidate();
+}
+
+void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
+ const IntRect& aRect) {
+ switch (aIndex) {
+ case ATT_TURBULENCE_RECT:
+ mRenderRect = aRect;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
+ break;
+ }
+ Invalidate();
+}
+
+void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
+ bool aStitchable) {
+ MOZ_ASSERT(aIndex == ATT_TURBULENCE_STITCHABLE);
+ mStitchable = aStitchable;
+ Invalidate();
+}
+
+void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aValue) {
+ switch (aIndex) {
+ case ATT_TURBULENCE_NUM_OCTAVES:
+ mNumOctaves = aValue;
+ break;
+ case ATT_TURBULENCE_SEED:
+ mSeed = aValue;
+ break;
+ case ATT_TURBULENCE_TYPE:
+ mType = static_cast<TurbulenceType>(aValue);
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
+ break;
+ }
+ Invalidate();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeTurbulenceSoftware::Render(
+ const IntRect& aRect) {
+ return FilterProcessing::RenderTurbulence(
+ aRect.Size(), aRect.TopLeft(), mBaseFrequency, mSeed, mNumOctaves, mType,
+ mStitchable, Rect(mRenderRect));
+}
+
+IntRect FilterNodeTurbulenceSoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ return aRect.Intersect(mRenderRect);
+}
+
+IntRect FilterNodeTurbulenceSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ return IntRect();
+}
+
+FilterNodeArithmeticCombineSoftware::FilterNodeArithmeticCombineSoftware()
+ : mK1(0), mK2(0), mK3(0), mK4(0) {}
+
+int32_t FilterNodeArithmeticCombineSoftware::InputIndex(
+ uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_ARITHMETIC_COMBINE_IN:
+ return 0;
+ case IN_ARITHMETIC_COMBINE_IN2:
+ return 1;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeArithmeticCombineSoftware::SetAttribute(uint32_t aIndex,
+ const Float* aFloat,
+ uint32_t aSize) {
+ MOZ_ASSERT(aIndex == ATT_ARITHMETIC_COMBINE_COEFFICIENTS);
+ MOZ_RELEASE_ASSERT(aSize == 4);
+
+ mK1 = aFloat[0];
+ mK2 = aFloat[1];
+ mK3 = aFloat[2];
+ mK4 = aFloat[3];
+
+ Invalidate();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeArithmeticCombineSoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> input1 = GetInputDataSourceSurface(
+ IN_ARITHMETIC_COMBINE_IN, aRect, NEED_COLOR_CHANNELS);
+ RefPtr<DataSourceSurface> input2 = GetInputDataSourceSurface(
+ IN_ARITHMETIC_COMBINE_IN2, aRect, NEED_COLOR_CHANNELS);
+ if (!input1 && !input2) {
+ return nullptr;
+ }
+
+ // If one input is null, treat it as transparent by adjusting the factors.
+ Float k1 = mK1, k2 = mK2, k3 = mK3, k4 = mK4;
+ if (!input1) {
+ k1 = 0.0f;
+ k2 = 0.0f;
+ input1 = input2;
+ }
+
+ if (!input2) {
+ k1 = 0.0f;
+ k3 = 0.0f;
+ input2 = input1;
+ }
+
+ return FilterProcessing::ApplyArithmeticCombine(input1, input2, k1, k2, k3,
+ k4);
+}
+
+void FilterNodeArithmeticCombineSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_ARITHMETIC_COMBINE_IN, aRect);
+ RequestInputRect(IN_ARITHMETIC_COMBINE_IN2, aRect);
+}
+
+IntRect FilterNodeArithmeticCombineSoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ IntRect result =
+ MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN, aRect, aMax, aSourceNode);
+ result.OrWith(MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN2, aRect, aMax,
+ aSourceNode));
+ return result;
+}
+
+IntRect FilterNodeArithmeticCombineSoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ if (mK4 > 0.0f) {
+ return aRect;
+ }
+ IntRect rectFrom1 =
+ GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN, aRect).Intersect(aRect);
+ IntRect rectFrom2 =
+ GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN2, aRect).Intersect(aRect);
+ IntRect result;
+ if (mK1 > 0.0f) {
+ result = rectFrom1.Intersect(rectFrom2);
+ }
+ if (mK2 > 0.0f) {
+ result = result.Union(rectFrom1);
+ }
+ if (mK3 > 0.0f) {
+ result = result.Union(rectFrom2);
+ }
+ return result;
+}
+
+FilterNodeCompositeSoftware::FilterNodeCompositeSoftware()
+ : mOperator(COMPOSITE_OPERATOR_OVER) {}
+
+int32_t FilterNodeCompositeSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ return aInputEnumIndex - IN_COMPOSITE_IN_START;
+}
+
+void FilterNodeCompositeSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aCompositeOperator) {
+ MOZ_ASSERT(aIndex == ATT_COMPOSITE_OPERATOR);
+ mOperator = static_cast<CompositeOperator>(aCompositeOperator);
+ Invalidate();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeCompositeSoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> start = GetInputDataSourceSurface(
+ IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS);
+ RefPtr<DataSourceSurface> dest = Factory::CreateDataSourceSurface(
+ aRect.Size(), SurfaceFormat::B8G8R8A8, true);
+ if (MOZ2D_WARN_IF(!dest)) {
+ return nullptr;
+ }
+
+ if (start) {
+ CopyRect(start, dest, aRect - aRect.TopLeft(), IntPoint());
+ }
+
+ for (size_t inputIndex = 1; inputIndex < NumberOfSetInputs(); inputIndex++) {
+ RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
+ IN_COMPOSITE_IN_START + inputIndex, aRect, NEED_COLOR_CHANNELS);
+ if (input) {
+ FilterProcessing::ApplyComposition(input, dest, mOperator);
+ } else {
+ // We need to treat input as transparent. Depending on the composite
+ // operator, different things happen to dest.
+ switch (mOperator) {
+ case COMPOSITE_OPERATOR_OVER:
+ case COMPOSITE_OPERATOR_ATOP:
+ case COMPOSITE_OPERATOR_XOR:
+ case COMPOSITE_OPERATOR_LIGHTER:
+ // dest is unchanged.
+ break;
+ case COMPOSITE_OPERATOR_OUT:
+ // dest is now transparent, but it can become non-transparent again
+ // when compositing additional inputs.
+ ClearDataSourceSurface(dest);
+ break;
+ case COMPOSITE_OPERATOR_IN:
+ // Transparency always wins. We're completely transparent now and
+ // no additional input can get rid of that transparency.
+ return nullptr;
+ }
+ }
+ }
+ return dest.forget();
+}
+
+void FilterNodeCompositeSoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
+ RequestInputRect(IN_COMPOSITE_IN_START + inputIndex, aRect);
+ }
+}
+
+IntRect FilterNodeCompositeSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ IntRect result;
+ for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
+ result.OrWith(MapInputRectToSource(IN_COMPOSITE_IN_START + inputIndex,
+ aRect, aMax, aSourceNode));
+ }
+ return result;
+}
+
+IntRect FilterNodeCompositeSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ IntRect rect;
+ for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
+ IntRect inputRect =
+ GetInputRectInRect(IN_COMPOSITE_IN_START + inputIndex, aRect);
+ if (mOperator == COMPOSITE_OPERATOR_IN && inputIndex > 0) {
+ rect = rect.Intersect(inputRect);
+ } else {
+ rect = rect.Union(inputRect);
+ }
+ }
+ return rect;
+}
+
+int32_t FilterNodeBlurXYSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_GAUSSIAN_BLUR_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeBlurXYSoftware::Render(
+ const IntRect& aRect) {
+ Size sigmaXY = StdDeviationXY();
+ IntSize d =
+ AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height));
+
+ if (d.width == 0 && d.height == 0) {
+ return GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, aRect);
+ }
+
+ IntRect srcRect = InflatedSourceOrDestRect(aRect);
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, srcRect);
+ if (!input) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> target;
+ Rect r(0, 0, srcRect.Width(), srcRect.Height());
+
+ if (input->GetFormat() == SurfaceFormat::A8) {
+ target =
+ Factory::CreateDataSourceSurface(srcRect.Size(), SurfaceFormat::A8);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+ CopyRect(input, target, IntRect(IntPoint(), input->GetSize()), IntPoint());
+
+ DataSourceSurface::ScopedMap targetMap(target,
+ DataSourceSurface::READ_WRITE);
+ if (MOZ2D_WARN_IF(!targetMap.IsMapped())) {
+ return nullptr;
+ }
+ AlphaBoxBlur blur(r, targetMap.GetStride(), sigmaXY.width, sigmaXY.height);
+ blur.Blur(targetMap.GetData());
+ } else {
+ RefPtr<DataSourceSurface> channel0, channel1, channel2, channel3;
+ FilterProcessing::SeparateColorChannels(input, channel0, channel1, channel2,
+ channel3);
+ if (MOZ2D_WARN_IF(!(channel0 && channel1 && channel2 && channel3))) {
+ return nullptr;
+ }
+ {
+ DataSourceSurface::ScopedMap channel0Map(channel0,
+ DataSourceSurface::READ_WRITE);
+ DataSourceSurface::ScopedMap channel1Map(channel1,
+ DataSourceSurface::READ_WRITE);
+ DataSourceSurface::ScopedMap channel2Map(channel2,
+ DataSourceSurface::READ_WRITE);
+ DataSourceSurface::ScopedMap channel3Map(channel3,
+ DataSourceSurface::READ_WRITE);
+ if (MOZ2D_WARN_IF(!(channel0Map.IsMapped() && channel1Map.IsMapped() &&
+ channel2Map.IsMapped() && channel3Map.IsMapped()))) {
+ return nullptr;
+ }
+
+ AlphaBoxBlur blur(r, channel0Map.GetStride(), sigmaXY.width,
+ sigmaXY.height);
+ blur.Blur(channel0Map.GetData());
+ blur.Blur(channel1Map.GetData());
+ blur.Blur(channel2Map.GetData());
+ blur.Blur(channel3Map.GetData());
+ }
+ target = FilterProcessing::CombineColorChannels(channel0, channel1,
+ channel2, channel3);
+ }
+
+ return GetDataSurfaceInRect(target, srcRect, aRect, EDGE_MODE_NONE);
+}
+
+void FilterNodeBlurXYSoftware::RequestFromInputsForRect(const IntRect& aRect) {
+ RequestInputRect(IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect));
+}
+
+IntRect FilterNodeBlurXYSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ return MapInputRectToSource(
+ IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect), aMax, aSourceNode);
+}
+
+IntRect FilterNodeBlurXYSoftware::InflatedSourceOrDestRect(
+ const IntRect& aDestRect) {
+ Size sigmaXY = StdDeviationXY();
+ IntSize d =
+ AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height));
+ IntRect srcRect = aDestRect;
+ srcRect.Inflate(d);
+ return srcRect;
+}
+
+IntRect FilterNodeBlurXYSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ IntRect srcRequest = InflatedSourceOrDestRect(aRect);
+ IntRect srcOutput = GetInputRectInRect(IN_GAUSSIAN_BLUR_IN, srcRequest);
+ return InflatedSourceOrDestRect(srcOutput).Intersect(aRect);
+}
+
+FilterNodeGaussianBlurSoftware::FilterNodeGaussianBlurSoftware()
+ : mStdDeviation(0) {}
+
+static float ClampStdDeviation(float aStdDeviation) {
+ // Cap software blur radius for performance reasons.
+ return std::min(std::max(0.0f, aStdDeviation), 100.0f);
+}
+
+void FilterNodeGaussianBlurSoftware::SetAttribute(uint32_t aIndex,
+ float aStdDeviation) {
+ switch (aIndex) {
+ case ATT_GAUSSIAN_BLUR_STD_DEVIATION:
+ mStdDeviation = ClampStdDeviation(aStdDeviation);
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeGaussianBlurSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+Size FilterNodeGaussianBlurSoftware::StdDeviationXY() {
+ return Size(mStdDeviation, mStdDeviation);
+}
+
+FilterNodeDirectionalBlurSoftware::FilterNodeDirectionalBlurSoftware()
+ : mStdDeviation(0.0), mBlurDirection(BLUR_DIRECTION_X) {}
+
+void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex,
+ Float aStdDeviation) {
+ switch (aIndex) {
+ case ATT_DIRECTIONAL_BLUR_STD_DEVIATION:
+ mStdDeviation = ClampStdDeviation(aStdDeviation);
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex,
+ uint32_t aBlurDirection) {
+ switch (aIndex) {
+ case ATT_DIRECTIONAL_BLUR_DIRECTION:
+ mBlurDirection = (BlurDirection)aBlurDirection;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute");
+ }
+ Invalidate();
+}
+
+Size FilterNodeDirectionalBlurSoftware::StdDeviationXY() {
+ float sigmaX = mBlurDirection == BLUR_DIRECTION_X ? mStdDeviation : 0;
+ float sigmaY = mBlurDirection == BLUR_DIRECTION_Y ? mStdDeviation : 0;
+ return Size(sigmaX, sigmaY);
+}
+
+int32_t FilterNodeCropSoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_CROP_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void FilterNodeCropSoftware::SetAttribute(uint32_t aIndex,
+ const Rect& aSourceRect) {
+ MOZ_ASSERT(aIndex == ATT_CROP_RECT);
+ Rect srcRect = aSourceRect;
+ srcRect.Round();
+ if (!srcRect.ToIntRect(&mCropRect)) {
+ mCropRect = IntRect();
+ }
+ Invalidate();
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeCropSoftware::Render(
+ const IntRect& aRect) {
+ return GetInputDataSourceSurface(IN_CROP_IN, aRect.Intersect(mCropRect));
+}
+
+void FilterNodeCropSoftware::RequestFromInputsForRect(const IntRect& aRect) {
+ RequestInputRect(IN_CROP_IN, aRect.Intersect(mCropRect));
+}
+
+IntRect FilterNodeCropSoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_CROP_IN, aRect.Intersect(mCropRect), aMax,
+ aSourceNode);
+}
+
+IntRect FilterNodeCropSoftware::GetOutputRectInRect(const IntRect& aRect) {
+ return GetInputRectInRect(IN_CROP_IN, aRect).Intersect(mCropRect);
+}
+
+int32_t FilterNodePremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_PREMULTIPLY_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+already_AddRefed<DataSourceSurface> FilterNodePremultiplySoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_PREMULTIPLY_IN, aRect);
+ return input ? Premultiply(input) : nullptr;
+}
+
+void FilterNodePremultiplySoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_PREMULTIPLY_IN, aRect);
+}
+
+IntRect FilterNodePremultiplySoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_PREMULTIPLY_IN, aRect, aMax, aSourceNode);
+}
+
+IntRect FilterNodePremultiplySoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ return GetInputRectInRect(IN_PREMULTIPLY_IN, aRect);
+}
+
+int32_t FilterNodeUnpremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_UNPREMULTIPLY_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeUnpremultiplySoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_UNPREMULTIPLY_IN, aRect);
+ return input ? Unpremultiply(input) : nullptr;
+}
+
+void FilterNodeUnpremultiplySoftware::RequestFromInputsForRect(
+ const IntRect& aRect) {
+ RequestInputRect(IN_UNPREMULTIPLY_IN, aRect);
+}
+
+IntRect FilterNodeUnpremultiplySoftware::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_UNPREMULTIPLY_IN, aRect, aMax, aSourceNode);
+}
+
+IntRect FilterNodeUnpremultiplySoftware::GetOutputRectInRect(
+ const IntRect& aRect) {
+ return GetInputRectInRect(IN_UNPREMULTIPLY_IN, aRect);
+}
+
+void FilterNodeOpacitySoftware::SetAttribute(uint32_t aIndex, Float aValue) {
+ MOZ_ASSERT(aIndex == ATT_OPACITY_VALUE);
+ mValue = aValue;
+ Invalidate();
+}
+
+int32_t FilterNodeOpacitySoftware::InputIndex(uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_OPACITY_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+already_AddRefed<DataSourceSurface> FilterNodeOpacitySoftware::Render(
+ const IntRect& aRect) {
+ RefPtr<DataSourceSurface> input =
+ GetInputDataSourceSurface(IN_OPACITY_IN, aRect);
+ return input ? Opacity(input, mValue) : nullptr;
+}
+
+void FilterNodeOpacitySoftware::RequestFromInputsForRect(const IntRect& aRect) {
+ RequestInputRect(IN_OPACITY_IN, aRect);
+}
+
+IntRect FilterNodeOpacitySoftware::MapRectToSource(const IntRect& aRect,
+ const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ return MapInputRectToSource(IN_OPACITY_IN, aRect, aMax, aSourceNode);
+}
+
+IntRect FilterNodeOpacitySoftware::GetOutputRectInRect(const IntRect& aRect) {
+ return GetInputRectInRect(IN_OPACITY_IN, aRect);
+}
+
+bool PointLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) {
+ switch (aIndex) {
+ case ATT_POINT_LIGHT_POSITION:
+ mPosition = aPoint;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+SpotLightSoftware::SpotLightSoftware()
+ : mSpecularFocus(0), mLimitingConeAngle(0), mLimitingConeCos(1) {}
+
+bool SpotLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) {
+ switch (aIndex) {
+ case ATT_SPOT_LIGHT_POSITION:
+ mPosition = aPoint;
+ break;
+ case ATT_SPOT_LIGHT_POINTS_AT:
+ mPointsAt = aPoint;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool SpotLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
+ switch (aIndex) {
+ case ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE:
+ mLimitingConeAngle = aValue;
+ break;
+ case ATT_SPOT_LIGHT_FOCUS:
+ mSpecularFocus = aValue;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+DistantLightSoftware::DistantLightSoftware() : mAzimuth(0), mElevation(0) {}
+
+bool DistantLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
+ switch (aIndex) {
+ case ATT_DISTANT_LIGHT_AZIMUTH:
+ mAzimuth = aValue;
+ break;
+ case ATT_DISTANT_LIGHT_ELEVATION:
+ mElevation = aValue;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static inline Point3D Normalized(const Point3D& vec) {
+ Point3D copy(vec);
+ copy.Normalize();
+ return copy;
+}
+
+template <typename LightType, typename LightingType>
+FilterNodeLightingSoftware<LightType, LightingType>::FilterNodeLightingSoftware(
+ const char* aTypeName)
+ : mSurfaceScale(0)
+#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING)
+ ,
+ mTypeName(aTypeName)
+#endif
+{
+}
+
+template <typename LightType, typename LightingType>
+int32_t FilterNodeLightingSoftware<LightType, LightingType>::InputIndex(
+ uint32_t aInputEnumIndex) {
+ switch (aInputEnumIndex) {
+ case IN_LIGHTING_IN:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+template <typename LightType, typename LightingType>
+void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
+ uint32_t aIndex, const Point3D& aPoint) {
+ if (mLight.SetAttribute(aIndex, aPoint)) {
+ Invalidate();
+ return;
+ }
+ MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute point");
+}
+
+template <typename LightType, typename LightingType>
+void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
+ uint32_t aIndex, Float aValue) {
+ if (mLight.SetAttribute(aIndex, aValue) ||
+ mLighting.SetAttribute(aIndex, aValue)) {
+ Invalidate();
+ return;
+ }
+ switch (aIndex) {
+ case ATT_LIGHTING_SURFACE_SCALE:
+ mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float");
+ }
+ Invalidate();
+}
+
+template <typename LightType, typename LightingType>
+void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
+ uint32_t aIndex, const Size& aKernelUnitLength) {
+ switch (aIndex) {
+ case ATT_LIGHTING_KERNEL_UNIT_LENGTH:
+ mKernelUnitLength = aKernelUnitLength;
+ break;
+ default:
+ MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute size");
+ }
+ Invalidate();
+}
+
+template <typename LightType, typename LightingType>
+void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
+ uint32_t aIndex, const DeviceColor& aColor) {
+ MOZ_ASSERT(aIndex == ATT_LIGHTING_COLOR);
+ mColor = aColor;
+ Invalidate();
+}
+
+template <typename LightType, typename LightingType>
+IntRect
+FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect(
+ const IntRect& aRect) {
+ return aRect;
+}
+
+Point3D PointLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) {
+ return Normalized(mPosition - aTargetPoint);
+}
+
+uint32_t PointLightSoftware::GetColor(uint32_t aLightColor,
+ const Point3D& aVectorToLight) {
+ return aLightColor;
+}
+
+void SpotLightSoftware::Prepare() {
+ mVectorFromFocusPointToLight = Normalized(mPointsAt - mPosition);
+ mLimitingConeCos =
+ std::max<double>(cos(mLimitingConeAngle * M_PI / 180.0), 0.0);
+ mPowCache.CacheForExponent(mSpecularFocus);
+}
+
+Point3D SpotLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) {
+ return Normalized(mPosition - aTargetPoint);
+}
+
+uint32_t SpotLightSoftware::GetColor(uint32_t aLightColor,
+ const Point3D& aVectorToLight) {
+ union {
+ uint32_t color;
+ uint8_t colorC[4];
+ };
+
+ Float dot = -aVectorToLight.DotProduct(mVectorFromFocusPointToLight);
+ if (!mPowCache.HasPowerTable()) {
+ dot *= (dot >= mLimitingConeCos);
+ color = aLightColor;
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] *= dot;
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] *= dot;
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] *= dot;
+ } else {
+ color = aLightColor;
+ uint16_t doti = dot * (dot >= 0) * (1 << PowCache::sInputIntPrecisionBits);
+ uint32_t tmp = mPowCache.Pow(doti) * (dot >= mLimitingConeCos);
+ MOZ_ASSERT(tmp <= (1 << PowCache::sOutputIntPrecisionBits),
+ "pow() result must not exceed 1.0");
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
+ uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] * tmp) >>
+ PowCache::sOutputIntPrecisionBits);
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
+ uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] * tmp) >>
+ PowCache::sOutputIntPrecisionBits);
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
+ uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] * tmp) >>
+ PowCache::sOutputIntPrecisionBits);
+ }
+ colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255;
+ return color;
+}
+
+void DistantLightSoftware::Prepare() {
+ const double radPerDeg = M_PI / 180.0;
+ mVectorToLight.x = cos(mAzimuth * radPerDeg) * cos(mElevation * radPerDeg);
+ mVectorToLight.y = sin(mAzimuth * radPerDeg) * cos(mElevation * radPerDeg);
+ mVectorToLight.z = sin(mElevation * radPerDeg);
+}
+
+Point3D DistantLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) {
+ return mVectorToLight;
+}
+
+uint32_t DistantLightSoftware::GetColor(uint32_t aLightColor,
+ const Point3D& aVectorToLight) {
+ return aLightColor;
+}
+
+template <typename CoordType>
+static Point3D GenerateNormal(const uint8_t* data, int32_t stride,
+ uint8_t* boundsBegin, uint8_t* boundsEnd,
+ int32_t x, int32_t y, float surfaceScale,
+ CoordType dx, CoordType dy) {
+ const uint8_t* index = data + y * stride + x;
+
+ CoordType zero = 0;
+
+ // See this for source of constants:
+ // http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement
+ int16_t normalX = -1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, -dx, -dy, 1, 0) +
+ 1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, dx, -dy, 1, 0) +
+ -2 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, -dx, zero, 1, 0) +
+ 2 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, dx, zero, 1, 0) +
+ -1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, -dx, dy, 1, 0) +
+ 1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, dx, dy, 1, 0);
+
+ int16_t normalY = -1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, -dx, -dy, 1, 0) +
+ -2 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, zero, -dy, 1, 0) +
+ -1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, dx, -dy, 1, 0) +
+ 1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, -dx, dy, 1, 0) +
+ 2 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, zero, dy, 1, 0) +
+ 1 * ColorComponentAtPoint(index, stride, boundsBegin,
+ boundsEnd, dx, dy, 1, 0);
+
+ Point3D normal;
+ normal.x = -surfaceScale * normalX / 4.0f;
+ normal.y = -surfaceScale * normalY / 4.0f;
+ normal.z = 255;
+ return Normalized(normal);
+}
+
+template <typename LightType, typename LightingType>
+already_AddRefed<DataSourceSurface>
+FilterNodeLightingSoftware<LightType, LightingType>::Render(
+ const IntRect& aRect) {
+ if (mKernelUnitLength.width == floor(mKernelUnitLength.width) &&
+ mKernelUnitLength.height == floor(mKernelUnitLength.height)) {
+ return DoRender(aRect, (int32_t)mKernelUnitLength.width,
+ (int32_t)mKernelUnitLength.height);
+ }
+ return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height);
+}
+
+template <typename LightType, typename LightingType>
+void FilterNodeLightingSoftware<
+ LightType, LightingType>::RequestFromInputsForRect(const IntRect& aRect) {
+ IntRect srcRect = aRect;
+ srcRect.Inflate(ceil(mKernelUnitLength.width),
+ ceil(mKernelUnitLength.height));
+ RequestInputRect(IN_LIGHTING_IN, srcRect);
+}
+
+template <typename LightType, typename LightingType>
+IntRect FilterNodeLightingSoftware<LightType, LightingType>::MapRectToSource(
+ const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
+ IntRect srcRect = aRect;
+ srcRect.Inflate(ceil(mKernelUnitLength.width),
+ ceil(mKernelUnitLength.height));
+ return MapInputRectToSource(IN_LIGHTING_IN, srcRect, aMax, aSourceNode);
+}
+
+template <typename LightType, typename LightingType>
+template <typename CoordType>
+already_AddRefed<DataSourceSurface>
+FilterNodeLightingSoftware<LightType, LightingType>::DoRender(
+ const IntRect& aRect, CoordType aKernelUnitLengthX,
+ CoordType aKernelUnitLengthY) {
+ MOZ_ASSERT(aKernelUnitLengthX > 0,
+ "aKernelUnitLengthX can be a negative or zero value");
+ MOZ_ASSERT(aKernelUnitLengthY > 0,
+ "aKernelUnitLengthY can be a negative or zero value");
+
+ IntRect srcRect = aRect;
+ IntSize size = aRect.Size();
+ srcRect.Inflate(ceil(float(aKernelUnitLengthX)),
+ ceil(float(aKernelUnitLengthY)));
+
+ // Inflate the source rect by another pixel because the bilinear filtering in
+ // ColorComponentAtPoint may want to access the margins.
+ srcRect.Inflate(1);
+
+ RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
+ IN_LIGHTING_IN, srcRect, CAN_HANDLE_A8, EDGE_MODE_NONE);
+
+ if (!input) {
+ return nullptr;
+ }
+
+ if (input->GetFormat() != SurfaceFormat::A8) {
+ input = FilterProcessing::ExtractAlpha(input);
+ }
+
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (MOZ2D_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
+
+ DataSourceSurface::ScopedMap sourceMap(input, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
+ if (MOZ2D_WARN_IF(!(sourceMap.IsMapped() && targetMap.IsMapped()))) {
+ return nullptr;
+ }
+
+ uint8_t* sourceData =
+ DataAtOffset(input, sourceMap.GetMappedSurface(), offset);
+ int32_t sourceStride = sourceMap.GetStride();
+ uint8_t* sourceBegin = sourceMap.GetData();
+ uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height;
+ uint8_t* targetData = targetMap.GetData();
+ int32_t targetStride = targetMap.GetStride();
+
+ uint32_t lightColor = ColorToBGRA(mColor);
+ mLight.Prepare();
+ mLighting.Prepare();
+
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x++) {
+ int32_t sourceIndex = y * sourceStride + x;
+ int32_t targetIndex = y * targetStride + 4 * x;
+
+ Point3D normal =
+ GenerateNormal(sourceData, sourceStride, sourceBegin, sourceEnd, x, y,
+ mSurfaceScale, aKernelUnitLengthX, aKernelUnitLengthY);
+
+ IntPoint pointInFilterSpace(aRect.X() + x, aRect.Y() + y);
+ Float Z = mSurfaceScale * sourceData[sourceIndex] / 255.0f;
+ Point3D pt(pointInFilterSpace.x, pointInFilterSpace.y, Z);
+ Point3D rayDir = mLight.GetVectorToLight(pt);
+ uint32_t color = mLight.GetColor(lightColor, rayDir);
+
+ *(uint32_t*)(targetData + targetIndex) =
+ mLighting.LightPixel(normal, rayDir, color);
+ }
+
+ // Zero padding to keep valgrind happy.
+ PodZero(&targetData[y * targetStride + 4 * size.width],
+ targetStride - 4 * size.width);
+ }
+
+ return target.forget();
+}
+
+DiffuseLightingSoftware::DiffuseLightingSoftware() : mDiffuseConstant(0) {}
+
+bool DiffuseLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
+ switch (aIndex) {
+ case ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT:
+ mDiffuseConstant = aValue;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+uint32_t DiffuseLightingSoftware::LightPixel(const Point3D& aNormal,
+ const Point3D& aVectorToLight,
+ uint32_t aColor) {
+ Float dotNL = std::max(0.0f, aNormal.DotProduct(aVectorToLight));
+ Float diffuseNL = mDiffuseConstant * dotNL;
+
+ union {
+ uint32_t bgra;
+ uint8_t components[4];
+ } color = {aColor};
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = umin(
+ uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]),
+ 255U);
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = umin(
+ uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]),
+ 255U);
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = umin(
+ uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]),
+ 255U);
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255;
+ return color.bgra;
+}
+
+SpecularLightingSoftware::SpecularLightingSoftware()
+ : mSpecularConstant(0), mSpecularExponent(0), mSpecularConstantInt(0) {}
+
+bool SpecularLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
+ switch (aIndex) {
+ case ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT:
+ mSpecularConstant = std::min(std::max(aValue, 0.0f), 255.0f);
+ break;
+ case ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT:
+ mSpecularExponent = std::min(std::max(aValue, 1.0f), 128.0f);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void SpecularLightingSoftware::Prepare() {
+ mPowCache.CacheForExponent(mSpecularExponent);
+ mSpecularConstantInt = uint32_t(mSpecularConstant * (1 << 8));
+}
+
+uint32_t SpecularLightingSoftware::LightPixel(const Point3D& aNormal,
+ const Point3D& aVectorToLight,
+ uint32_t aColor) {
+ Point3D vectorToEye(0, 0, 1);
+ Point3D halfwayVector = aVectorToLight + vectorToEye;
+ Float halfwayLength = halfwayVector.Length();
+ if (halfwayLength > 0) {
+ halfwayVector /= halfwayLength;
+ }
+ Float dotNH = aNormal.DotProduct(halfwayVector);
+ uint16_t dotNHi =
+ uint16_t(dotNH * (dotNH >= 0) * (1 << PowCache::sInputIntPrecisionBits));
+ // The exponent for specular is in [1,128] range, so we don't need to check
+ // and optimize for the "default power table" scenario here.
+ MOZ_ASSERT(mPowCache.HasPowerTable());
+ uint32_t specularNHi =
+ uint32_t(mSpecularConstantInt) * mPowCache.Pow(dotNHi) >> 8;
+
+ union {
+ uint32_t bgra;
+ uint8_t components[4];
+ } color = {aColor};
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
+ umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]) >>
+ PowCache::sOutputIntPrecisionBits,
+ 255U);
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
+ umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) >>
+ PowCache::sOutputIntPrecisionBits,
+ 255U);
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
+ umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) >>
+ PowCache::sOutputIntPrecisionBits,
+ 255U);
+
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] =
+ umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B],
+ umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G],
+ color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]));
+ return color.bgra;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/FilterNodeSoftware.h b/gfx/2d/FilterNodeSoftware.h
new file mode 100644
index 0000000000..25b2481bfe
--- /dev/null
+++ b/gfx/2d/FilterNodeSoftware.h
@@ -0,0 +1,780 @@
+/* -*- 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_FILTERNODESOFTWARE_H_
+#define _MOZILLA_GFX_FILTERNODESOFTWARE_H_
+
+#include "Filters.h"
+#include "mozilla/Mutex.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)
+ FilterNodeSoftware();
+ 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);
+
+ FilterBackend GetBackendType() override { return FILTER_BACKEND_SOFTWARE; }
+ void SetInput(uint32_t aIndex, SourceSurface* aSurface) override;
+ void SetInput(uint32_t aIndex, FilterNode* aFilter) override;
+
+ virtual const char* GetName() { return "Unknown"; }
+
+ void AddInvalidationListener(FilterInvalidationListener* aListener);
+ void RemoveInvalidationListener(FilterInvalidationListener* aListener);
+
+ // FilterInvalidationListener implementation
+ 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);
+
+ /**
+ * Calls MapRectToSource on the specified input, if it's a filter.
+ */
+ IntRect MapInputRectToSource(uint32_t aInputEnumIndex, const IntRect& aRect,
+ const IntRect& aMax, FilterNode* aSourceNode);
+
+ /**
+ * 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();
+ const char* GetName() override { return "Transform"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, uint32_t aGraphicsFilter) override;
+ void SetAttribute(uint32_t aIndex, const Matrix& aMatrix) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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();
+ const char* GetName() override { return "Blend"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, uint32_t aBlendMode) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+
+ private:
+ BlendMode mBlendMode;
+};
+
+class FilterNodeMorphologySoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeMorphologySoftware,
+ override)
+ FilterNodeMorphologySoftware();
+ const char* GetName() override { return "Morphology"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const IntSize& aRadii) override;
+ void SetAttribute(uint32_t aIndex, uint32_t aOperator) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+
+ private:
+ IntSize mRadii;
+ MorphologyOperator mOperator;
+};
+
+class FilterNodeColorMatrixSoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeColorMatrixSoftware,
+ override)
+ const char* GetName() override { return "ColorMatrix"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const Matrix5x4& aMatrix) override;
+ void SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ private:
+ Matrix5x4 mMatrix;
+ AlphaMode mAlphaMode;
+};
+
+class FilterNodeFloodSoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeFloodSoftware, override)
+ const char* GetName() override { return "Flood"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const DeviceColor& aColor) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> GetOutput(const IntRect& aRect) override;
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+
+ private:
+ DeviceColor mColor;
+};
+
+class FilterNodeTileSoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTileSoftware, override)
+ const char* GetName() override { return "Tile"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const IntRect& aSourceRect) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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;
+ void SetAttribute(uint32_t aIndex, bool aDisable) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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)
+ const char* GetName() override { return "TableTransfer"; }
+ using FilterNodeComponentTransferSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const Float* aFloat,
+ uint32_t aSize) override;
+
+ protected:
+ 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)
+ const char* GetName() override { return "DiscreteTransfer"; }
+ using FilterNodeComponentTransferSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const Float* aFloat,
+ uint32_t aSize) override;
+
+ protected:
+ 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();
+ const char* GetName() override { return "LinearTransfer"; }
+ using FilterNodeComponentTransferSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float aValue) override;
+
+ protected:
+ 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();
+ const char* GetName() override { return "GammaTransfer"; }
+ using FilterNodeComponentTransferSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float aValue) override;
+
+ protected:
+ 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();
+ const char* GetName() override { return "ConvolveMatrix"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const IntSize& aKernelSize) override;
+ void SetAttribute(uint32_t aIndex, const Float* aMatrix,
+ uint32_t aSize) override;
+ void SetAttribute(uint32_t aIndex, Float aValue) override;
+ void SetAttribute(uint32_t aIndex, const Size& aKernelUnitLength) override;
+ void SetAttribute(uint32_t aIndex, const IntRect& aSourceRect) override;
+ void SetAttribute(uint32_t aIndex, const IntPoint& aTarget) override;
+ void SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) override;
+ void SetAttribute(uint32_t aIndex, bool aPreserveAlpha) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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();
+ const char* GetName() override { return "DisplacementMap"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float aScale) override;
+ void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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();
+ const char* GetName() override { return "Turbulence"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const Size& aSize) override;
+ void SetAttribute(uint32_t aIndex, const IntRect& aRenderRect) override;
+ void SetAttribute(uint32_t aIndex, bool aStitchable) override;
+ void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ 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();
+ const char* GetName() override { return "ArithmeticCombine"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const Float* aFloat,
+ uint32_t aSize) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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();
+ const char* GetName() override { return "Composite"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, uint32_t aOperator) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ private:
+ CompositeOperator mOperator;
+};
+
+// Base class for FilterNodeGaussianBlurSoftware and
+// FilterNodeDirectionalBlurSoftware.
+class FilterNodeBlurXYSoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlurXYSoftware, override)
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ IntRect InflatedSourceOrDestRect(const IntRect& aDestRect);
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ // Implemented by subclasses.
+ virtual Size StdDeviationXY() = 0;
+};
+
+class FilterNodeGaussianBlurSoftware : public FilterNodeBlurXYSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGaussianBlurSoftware,
+ override)
+ FilterNodeGaussianBlurSoftware();
+ const char* GetName() override { return "GaussianBlur"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float aStdDeviation) override;
+
+ protected:
+ Size StdDeviationXY() override;
+
+ private:
+ Float mStdDeviation;
+};
+
+class FilterNodeDirectionalBlurSoftware : public FilterNodeBlurXYSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDirectionalBlurSoftware,
+ override)
+ FilterNodeDirectionalBlurSoftware();
+ const char* GetName() override { return "DirectionalBlur"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float aStdDeviation) override;
+ void SetAttribute(uint32_t aIndex, uint32_t aBlurDirection) override;
+
+ protected:
+ Size StdDeviationXY() override;
+
+ private:
+ Float mStdDeviation;
+ BlurDirection mBlurDirection;
+};
+
+class FilterNodeCropSoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCropSoftware, override)
+ const char* GetName() override { return "Crop"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, const Rect& aSourceRect) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ private:
+ IntRect mCropRect;
+};
+
+class FilterNodePremultiplySoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplySoftware,
+ override)
+ const char* GetName() override { return "Premultiply"; }
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+};
+
+class FilterNodeUnpremultiplySoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeUnpremultiplySoftware,
+ override)
+ const char* GetName() override { return "Unpremultiply"; }
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+};
+
+class FilterNodeOpacitySoftware : public FilterNodeSoftware {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeOpacitySoftware, override)
+ const char* GetName() override { return "Opacity"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float aValue) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ void RequestFromInputsForRect(const IntRect& aRect) override;
+
+ Float mValue = 1.0f;
+};
+
+template <typename LightType, typename LightingType>
+class FilterNodeLightingSoftware : public FilterNodeSoftware {
+ public:
+#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING)
+ // Helpers for refcounted
+ const char* typeName() const override { return mTypeName; }
+ size_t typeSize() const override { return sizeof(*this); }
+#endif
+ explicit FilterNodeLightingSoftware(const char* aTypeName);
+ const char* GetName() override { return "Lighting"; }
+ using FilterNodeSoftware::SetAttribute;
+ void SetAttribute(uint32_t aIndex, Float) override;
+ void SetAttribute(uint32_t aIndex, const Size&) override;
+ void SetAttribute(uint32_t aIndex, const Point3D&) override;
+ void SetAttribute(uint32_t aIndex, const DeviceColor&) override;
+ IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) override;
+
+ protected:
+ already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
+ IntRect GetOutputRectInRect(const IntRect& aRect) override;
+ int32_t InputIndex(uint32_t aInputEnumIndex) override;
+ 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;
+ DeviceColor mColor;
+#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING)
+ const char* mTypeName;
+#endif
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_FILTERNODESOFTWARE_H_
diff --git a/gfx/2d/FilterProcessing.cpp b/gfx/2d/FilterProcessing.cpp
new file mode 100644
index 0000000000..4f26cd6249
--- /dev/null
+++ b/gfx/2d/FilterProcessing.cpp
@@ -0,0 +1,282 @@
+/* -*- 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 "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);
+ }
+}
+
+void FilterProcessing::DoOpacityCalculation(
+ const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride,
+ uint8_t* aSourceData, int32_t aSourceStride, Float aValue) {
+ if (Factory::HasSSE2()) {
+#ifdef USE_SSE2
+ DoOpacityCalculation_SSE2(aSize, aTargetData, aTargetStride, aSourceData,
+ aSourceStride, aValue);
+#endif
+ } else {
+ DoOpacityCalculation_Scalar(aSize, aTargetData, aTargetStride, aSourceData,
+ aSourceStride, aValue);
+ }
+}
+
+void FilterProcessing::DoOpacityCalculationA8(
+ const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride,
+ uint8_t* aSourceData, int32_t aSourceStride, Float aValue) {
+ DoOpacityCalculationA8_Scalar(aSize, aTargetData, aTargetStride, aSourceData,
+ aSourceStride, aValue);
+}
+
+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
diff --git a/gfx/2d/FilterProcessing.h b/gfx/2d/FilterProcessing.h
new file mode 100644
index 0000000000..d6beb62611
--- /dev/null
+++ b/gfx/2d/FilterProcessing.h
@@ -0,0 +1,209 @@
+/* -*- 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_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 void DoOpacityCalculation(const IntSize& aSize, uint8_t* aTargetData,
+ int32_t aTargetStride, uint8_t* aSourceData,
+ int32_t aSourceStride, Float aValue);
+ static void DoOpacityCalculationA8(const IntSize& aSize, uint8_t* aTargetData,
+ int32_t aTargetStride,
+ uint8_t* aSourceData,
+ int32_t aSourceStride, Float aValue);
+ 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 void DoOpacityCalculation_Scalar(const IntSize& aSize,
+ uint8_t* aTargetData,
+ int32_t aTargetStride,
+ uint8_t* aSourceData,
+ int32_t aSourceStride, Float aValue);
+ static void DoOpacityCalculationA8_Scalar(
+ const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride,
+ uint8_t* aSourceData, int32_t aSourceStride, Float aValue);
+ 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 void DoOpacityCalculation_SSE2(const IntSize& aSize,
+ uint8_t* aTargetData,
+ int32_t aTargetStride,
+ uint8_t* aSourceData,
+ int32_t aSourceStride, Float aValue);
+ 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_
diff --git a/gfx/2d/FilterProcessingSIMD-inl.h b/gfx/2d/FilterProcessingSIMD-inl.h
new file mode 100644
index 0000000000..81f30cfc9e
--- /dev/null
+++ b/gfx/2d/FilterProcessingSIMD-inl.h
@@ -0,0 +1,1299 @@
+/* -*- 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 "FilterProcessing.h"
+
+#include "SIMD.h"
+#include "SVGTurbulenceRenderer-inl.h"
+
+namespace mozilla {
+namespace gfx {
+
+template <typename u8x16_t>
+inline already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_SIMD(
+ SourceSurface* aSurface) {
+ IntSize size = aSurface->GetSize();
+ RefPtr<DataSourceSurface> output =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (!output) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> input = aSurface->GetDataSurface();
+ DataSourceSurface::ScopedMap inputMap(input, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap outputMap(output, DataSourceSurface::READ_WRITE);
+ uint8_t* inputData = inputMap.GetData();
+ uint8_t* outputData = outputMap.GetData();
+ int32_t inputStride = inputMap.GetStride();
+ int32_t outputStride = outputMap.GetStride();
+ switch (input->GetFormat()) {
+ case SurfaceFormat::B8G8R8A8:
+ output = input;
+ break;
+ case SurfaceFormat::B8G8R8X8:
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x++) {
+ int32_t inputIndex = y * inputStride + 4 * x;
+ int32_t outputIndex = y * outputStride + 4 * x;
+ outputData[outputIndex + 0] = inputData[inputIndex + 0];
+ outputData[outputIndex + 1] = inputData[inputIndex + 1];
+ outputData[outputIndex + 2] = inputData[inputIndex + 2];
+ outputData[outputIndex + 3] = 255;
+ }
+ }
+ break;
+ case SurfaceFormat::R8G8B8A8:
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x++) {
+ int32_t inputIndex = y * inputStride + 4 * x;
+ int32_t outputIndex = y * outputStride + 4 * x;
+ outputData[outputIndex + 2] = inputData[inputIndex + 0];
+ outputData[outputIndex + 1] = inputData[inputIndex + 1];
+ outputData[outputIndex + 0] = inputData[inputIndex + 2];
+ outputData[outputIndex + 3] = inputData[inputIndex + 3];
+ }
+ }
+ break;
+ case SurfaceFormat::R8G8B8X8:
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x++) {
+ int32_t inputIndex = y * inputStride + 4 * x;
+ int32_t outputIndex = y * outputStride + 4 * x;
+ outputData[outputIndex + 2] = inputData[inputIndex + 0];
+ outputData[outputIndex + 1] = inputData[inputIndex + 1];
+ outputData[outputIndex + 0] = inputData[inputIndex + 2];
+ outputData[outputIndex + 3] = 255;
+ }
+ }
+ break;
+ case SurfaceFormat::A8:
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x += 16) {
+ int32_t inputIndex = y * inputStride + x;
+ int32_t outputIndex = y * outputStride + 4 * x;
+ u8x16_t p1To16 = simd::Load8<u8x16_t>(&inputData[inputIndex]);
+ // Turn AAAAAAAAAAAAAAAA into four chunks of 000A000A000A000A by
+ // interleaving with 0000000000000000 twice.
+ u8x16_t zero = simd::FromZero8<u8x16_t>();
+ u8x16_t p1To8 = simd::InterleaveLo8(zero, p1To16);
+ u8x16_t p9To16 = simd::InterleaveHi8(zero, p1To16);
+ u8x16_t p1To4 = simd::InterleaveLo8(zero, p1To8);
+ u8x16_t p5To8 = simd::InterleaveHi8(zero, p1To8);
+ u8x16_t p9To12 = simd::InterleaveLo8(zero, p9To16);
+ u8x16_t p13To16 = simd::InterleaveHi8(zero, p9To16);
+ simd::Store8(&outputData[outputIndex], p1To4);
+ if ((x + 4) * 4 < outputStride) {
+ simd::Store8(&outputData[outputIndex + 4 * 4], p5To8);
+ }
+ if ((x + 8) * 4 < outputStride) {
+ simd::Store8(&outputData[outputIndex + 4 * 8], p9To12);
+ }
+ if ((x + 12) * 4 < outputStride) {
+ simd::Store8(&outputData[outputIndex + 4 * 12], p13To16);
+ }
+ }
+ }
+ break;
+ default:
+ output = nullptr;
+ break;
+ }
+ return output.forget();
+}
+
+template <typename u8x16_t>
+inline void ExtractAlpha_SIMD(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 += 16) {
+ // Process 16 pixels at a time.
+ // Turn up to four chunks of BGRABGRABGRABGRA into one chunk of
+ // AAAAAAAAAAAAAAAA.
+ int32_t sourceIndex = y * sourceStride + 4 * x;
+ int32_t targetIndex = y * alphaStride + x;
+
+ u8x16_t bgrabgrabgrabgra2 = simd::FromZero8<u8x16_t>();
+ u8x16_t bgrabgrabgrabgra3 = simd::FromZero8<u8x16_t>();
+ u8x16_t bgrabgrabgrabgra4 = simd::FromZero8<u8x16_t>();
+
+ u8x16_t bgrabgrabgrabgra1 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex]);
+ if (4 * (x + 4) < sourceStride) {
+ bgrabgrabgrabgra2 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 4]);
+ }
+ if (4 * (x + 8) < sourceStride) {
+ bgrabgrabgrabgra3 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 8]);
+ }
+ if (4 * (x + 12) < sourceStride) {
+ bgrabgrabgrabgra4 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 12]);
+ }
+
+ u8x16_t bbggrraabbggrraa1 =
+ simd::InterleaveLo8(bgrabgrabgrabgra1, bgrabgrabgrabgra3);
+ u8x16_t bbggrraabbggrraa2 =
+ simd::InterleaveHi8(bgrabgrabgrabgra1, bgrabgrabgrabgra3);
+ u8x16_t bbggrraabbggrraa3 =
+ simd::InterleaveLo8(bgrabgrabgrabgra2, bgrabgrabgrabgra4);
+ u8x16_t bbggrraabbggrraa4 =
+ simd::InterleaveHi8(bgrabgrabgrabgra2, bgrabgrabgrabgra4);
+ u8x16_t bbbbggggrrrraaaa1 =
+ simd::InterleaveLo8(bbggrraabbggrraa1, bbggrraabbggrraa3);
+ u8x16_t bbbbggggrrrraaaa2 =
+ simd::InterleaveHi8(bbggrraabbggrraa1, bbggrraabbggrraa3);
+ u8x16_t bbbbggggrrrraaaa3 =
+ simd::InterleaveLo8(bbggrraabbggrraa2, bbggrraabbggrraa4);
+ u8x16_t bbbbggggrrrraaaa4 =
+ simd::InterleaveHi8(bbggrraabbggrraa2, bbggrraabbggrraa4);
+ u8x16_t rrrrrrrraaaaaaaa1 =
+ simd::InterleaveHi8(bbbbggggrrrraaaa1, bbbbggggrrrraaaa3);
+ u8x16_t rrrrrrrraaaaaaaa2 =
+ simd::InterleaveHi8(bbbbggggrrrraaaa2, bbbbggggrrrraaaa4);
+ u8x16_t aaaaaaaaaaaaaaaa =
+ simd::InterleaveHi8(rrrrrrrraaaaaaaa1, rrrrrrrraaaaaaaa2);
+
+ simd::Store8(&alphaData[targetIndex], aaaaaaaaaaaaaaaa);
+ }
+ }
+}
+
+// This function calculates the result color values for four pixels, but for
+// only two color channels - either b & r or g & a. However, the a result will
+// not be used.
+// source and dest each contain 8 values, either bbbb gggg or rrrr aaaa.
+// sourceAlpha and destAlpha are of the form aaaa aaaa, where each aaaa is the
+// alpha of all four pixels (and both aaaa's are the same).
+// blendendComponent1 and blendedComponent2 are the out parameters.
+template <typename i16x8_t, typename i32x4_t, uint32_t aBlendMode>
+inline void BlendTwoComponentsOfFourPixels(i16x8_t source, i16x8_t sourceAlpha,
+ i16x8_t dest,
+ const i16x8_t& destAlpha,
+ i32x4_t& blendedComponent1,
+ i32x4_t& blendedComponent2) {
+ i16x8_t x255 = simd::FromI16<i16x8_t>(255);
+
+ switch (aBlendMode) {
+ case BLEND_MODE_MULTIPLY: {
+ // val = ((255 - destAlpha) * source + (255 - sourceAlpha + source) *
+ // dest);
+ i16x8_t twoFiftyFiveMinusDestAlpha = simd::Sub16(x255, destAlpha);
+ i16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha);
+ i16x8_t twoFiftyFiveMinusSourceAlphaPlusSource =
+ simd::Add16(twoFiftyFiveMinusSourceAlpha, source);
+
+ i16x8_t sourceInterleavedWithDest1 = simd::InterleaveLo16(source, dest);
+ i16x8_t leftFactor1 = simd::InterleaveLo16(
+ twoFiftyFiveMinusDestAlpha, twoFiftyFiveMinusSourceAlphaPlusSource);
+ blendedComponent1 =
+ simd::MulAdd16x8x2To32x4(sourceInterleavedWithDest1, leftFactor1);
+ blendedComponent1 = simd::FastDivideBy255(blendedComponent1);
+
+ i16x8_t sourceInterleavedWithDest2 = simd::InterleaveHi16(source, dest);
+ i16x8_t leftFactor2 = simd::InterleaveHi16(
+ twoFiftyFiveMinusDestAlpha, twoFiftyFiveMinusSourceAlphaPlusSource);
+ blendedComponent2 =
+ simd::MulAdd16x8x2To32x4(sourceInterleavedWithDest2, leftFactor2);
+ blendedComponent2 = simd::FastDivideBy255(blendedComponent2);
+
+ break;
+ }
+
+ case BLEND_MODE_SCREEN: {
+ // val = 255 * (source + dest) + (0 - dest) * source;
+ i16x8_t sourcePlusDest = simd::Add16(source, dest);
+ i16x8_t zeroMinusDest = simd::Sub16(simd::FromI16<i16x8_t>(0), dest);
+
+ i16x8_t twoFiftyFiveInterleavedWithZeroMinusDest1 =
+ simd::InterleaveLo16(x255, zeroMinusDest);
+ i16x8_t sourcePlusDestInterleavedWithSource1 =
+ simd::InterleaveLo16(sourcePlusDest, source);
+ blendedComponent1 =
+ simd::MulAdd16x8x2To32x4(twoFiftyFiveInterleavedWithZeroMinusDest1,
+ sourcePlusDestInterleavedWithSource1);
+ blendedComponent1 = simd::FastDivideBy255(blendedComponent1);
+
+ i16x8_t twoFiftyFiveInterleavedWithZeroMinusDest2 =
+ simd::InterleaveHi16(x255, zeroMinusDest);
+ i16x8_t sourcePlusDestInterleavedWithSource2 =
+ simd::InterleaveHi16(sourcePlusDest, source);
+ blendedComponent2 =
+ simd::MulAdd16x8x2To32x4(twoFiftyFiveInterleavedWithZeroMinusDest2,
+ sourcePlusDestInterleavedWithSource2);
+ blendedComponent2 = simd::FastDivideBy255(blendedComponent2);
+
+ break;
+ }
+
+ case BLEND_MODE_DARKEN:
+ case BLEND_MODE_LIGHTEN: {
+ // Darken:
+ // val = min((255 - destAlpha) * source + 255 * dest,
+ // 255 * source + (255 - sourceAlpha) * dest);
+ //
+ // Lighten:
+ // val = max((255 - destAlpha) * source + 255 * dest,
+ // 255 * source + (255 - sourceAlpha) * dest);
+
+ i16x8_t twoFiftyFiveMinusDestAlpha = simd::Sub16(x255, destAlpha);
+ i16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha);
+
+ i16x8_t twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive1 =
+ simd::InterleaveLo16(twoFiftyFiveMinusDestAlpha, x255);
+ i16x8_t twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha1 =
+ simd::InterleaveLo16(x255, twoFiftyFiveMinusSourceAlpha);
+ i16x8_t sourceInterleavedWithDest1 = simd::InterleaveLo16(source, dest);
+ i32x4_t product1_1 = simd::MulAdd16x8x2To32x4(
+ twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive1,
+ sourceInterleavedWithDest1);
+ i32x4_t product1_2 = simd::MulAdd16x8x2To32x4(
+ twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha1,
+ sourceInterleavedWithDest1);
+ blendedComponent1 = aBlendMode == BLEND_MODE_DARKEN
+ ? simd::Min32(product1_1, product1_2)
+ : simd::Max32(product1_1, product1_2);
+ blendedComponent1 = simd::FastDivideBy255(blendedComponent1);
+
+ i16x8_t twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive2 =
+ simd::InterleaveHi16(twoFiftyFiveMinusDestAlpha, x255);
+ i16x8_t twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha2 =
+ simd::InterleaveHi16(x255, twoFiftyFiveMinusSourceAlpha);
+ i16x8_t sourceInterleavedWithDest2 = simd::InterleaveHi16(source, dest);
+ i32x4_t product2_1 = simd::MulAdd16x8x2To32x4(
+ twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive2,
+ sourceInterleavedWithDest2);
+ i32x4_t product2_2 = simd::MulAdd16x8x2To32x4(
+ twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha2,
+ sourceInterleavedWithDest2);
+ blendedComponent2 = aBlendMode == BLEND_MODE_DARKEN
+ ? simd::Min32(product2_1, product2_2)
+ : simd::Max32(product2_1, product2_2);
+ blendedComponent2 = simd::FastDivideBy255(blendedComponent2);
+
+ break;
+ }
+ }
+}
+
+// The alpha channel is subject to a different calculation than the RGB
+// channels, and this calculation is the same for all blend modes:
+// resultAlpha * 255 = 255 * 255 - (255 - sourceAlpha) * (255 - destAlpha)
+template <typename i16x8_t, typename i32x4_t>
+inline i32x4_t BlendAlphaOfFourPixels(i16x8_t s_rrrraaaa1234,
+ i16x8_t d_rrrraaaa1234) {
+ // clang-format off
+ // We're using MulAdd16x8x2To32x4, so we need to interleave our factors
+ // appropriately. The calculation is rewritten as follows:
+ // resultAlpha[0] * 255 = 255 * 255 - (255 - sourceAlpha[0]) * (255 - destAlpha[0])
+ // = 255 * 255 + (255 - sourceAlpha[0]) * (destAlpha[0] - 255)
+ // = (255 - 0) * (510 - 255) + (255 - sourceAlpha[0]) * (destAlpha[0] - 255)
+ // = MulAdd(255 - IntLv(0, sourceAlpha), IntLv(510, destAlpha) - 255)[0]
+ // clang-format on
+ i16x8_t zeroInterleavedWithSourceAlpha =
+ simd::InterleaveHi16(simd::FromI16<i16x8_t>(0), s_rrrraaaa1234);
+ i16x8_t fiveTenInterleavedWithDestAlpha =
+ simd::InterleaveHi16(simd::FromI16<i16x8_t>(510), d_rrrraaaa1234);
+ i16x8_t f1 =
+ simd::Sub16(simd::FromI16<i16x8_t>(255), zeroInterleavedWithSourceAlpha);
+ i16x8_t f2 =
+ simd::Sub16(fiveTenInterleavedWithDestAlpha, simd::FromI16<i16x8_t>(255));
+ return simd::FastDivideBy255(simd::MulAdd16x8x2To32x4(f1, f2));
+}
+
+template <typename u8x16_t, typename i16x8_t>
+inline void UnpackAndShuffleComponents(u8x16_t bgrabgrabgrabgra1234,
+ i16x8_t& bbbbgggg1234,
+ i16x8_t& rrrraaaa1234) {
+ // bgrabgrabgrabgra1234 -> bbbbgggg1234, rrrraaaa1234
+ i16x8_t bgrabgra12 = simd::UnpackLo8x8ToI16x8(bgrabgrabgrabgra1234);
+ i16x8_t bgrabgra34 = simd::UnpackHi8x8ToI16x8(bgrabgrabgrabgra1234);
+ i16x8_t bbggrraa13 = simd::InterleaveLo16(bgrabgra12, bgrabgra34);
+ i16x8_t bbggrraa24 = simd::InterleaveHi16(bgrabgra12, bgrabgra34);
+ bbbbgggg1234 = simd::InterleaveLo16(bbggrraa13, bbggrraa24);
+ rrrraaaa1234 = simd::InterleaveHi16(bbggrraa13, bbggrraa24);
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t>
+inline u8x16_t ShuffleAndPackComponents(i32x4_t bbbb1234, i32x4_t gggg1234,
+ i32x4_t rrrr1234,
+ const i32x4_t& aaaa1234) {
+ // bbbb1234, gggg1234, rrrr1234, aaaa1234 -> bgrabgrabgrabgra1234
+ i16x8_t bbbbgggg1234 = simd::PackAndSaturate32To16(bbbb1234, gggg1234);
+ i16x8_t rrrraaaa1234 = simd::PackAndSaturate32To16(rrrr1234, aaaa1234);
+ i16x8_t brbrbrbr1234 = simd::InterleaveLo16(bbbbgggg1234, rrrraaaa1234);
+ i16x8_t gagagaga1234 = simd::InterleaveHi16(bbbbgggg1234, rrrraaaa1234);
+ i16x8_t bgrabgra12 = simd::InterleaveLo16(brbrbrbr1234, gagagaga1234);
+ i16x8_t bgrabgra34 = simd::InterleaveHi16(brbrbrbr1234, gagagaga1234);
+ return simd::PackAndSaturate16To8(bgrabgra12, bgrabgra34);
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t, BlendMode mode>
+inline void ApplyBlending_SIMD(const DataSourceSurface::ScopedMap& aInputMap1,
+ const DataSourceSurface::ScopedMap& aInputMap2,
+ const DataSourceSurface::ScopedMap& aOutputMap,
+ const IntSize& aSize) {
+ uint8_t* source1Data = aInputMap1.GetData();
+ uint8_t* source2Data = aInputMap2.GetData();
+ uint8_t* targetData = aOutputMap.GetData();
+ int32_t targetStride = aOutputMap.GetStride();
+ int32_t source1Stride = aInputMap1.GetStride();
+ int32_t source2Stride = aInputMap2.GetStride();
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x += 4) {
+ int32_t targetIndex = y * targetStride + 4 * x;
+ int32_t source1Index = y * source1Stride + 4 * x;
+ int32_t source2Index = y * source2Stride + 4 * x;
+
+ u8x16_t s1234 = simd::Load8<u8x16_t>(&source2Data[source2Index]);
+ u8x16_t d1234 = simd::Load8<u8x16_t>(&source1Data[source1Index]);
+
+ // The blending calculation for the RGB channels all need access to the
+ // alpha channel of their pixel, and the alpha calculation is different,
+ // so it makes sense to separate by channel.
+
+ i16x8_t s_bbbbgggg1234, s_rrrraaaa1234;
+ i16x8_t d_bbbbgggg1234, d_rrrraaaa1234;
+ UnpackAndShuffleComponents(s1234, s_bbbbgggg1234, s_rrrraaaa1234);
+ UnpackAndShuffleComponents(d1234, d_bbbbgggg1234, d_rrrraaaa1234);
+ i16x8_t s_aaaaaaaa1234 = simd::Shuffle32<3, 2, 3, 2>(s_rrrraaaa1234);
+ i16x8_t d_aaaaaaaa1234 = simd::Shuffle32<3, 2, 3, 2>(d_rrrraaaa1234);
+
+ // We only use blendedB, blendedG and blendedR.
+ i32x4_t blendedB, blendedG, blendedR, blendedA;
+ BlendTwoComponentsOfFourPixels<i16x8_t, i32x4_t, mode>(
+ s_bbbbgggg1234, s_aaaaaaaa1234, d_bbbbgggg1234, d_aaaaaaaa1234,
+ blendedB, blendedG);
+ BlendTwoComponentsOfFourPixels<i16x8_t, i32x4_t, mode>(
+ s_rrrraaaa1234, s_aaaaaaaa1234, d_rrrraaaa1234, d_aaaaaaaa1234,
+ blendedR, blendedA);
+
+ // Throw away blendedA and overwrite it with the correct blended alpha.
+ blendedA = BlendAlphaOfFourPixels<i16x8_t, i32x4_t>(s_rrrraaaa1234,
+ d_rrrraaaa1234);
+
+ u8x16_t result1234 = ShuffleAndPackComponents<i32x4_t, i16x8_t, u8x16_t>(
+ blendedB, blendedG, blendedR, blendedA);
+ simd::Store8(&targetData[targetIndex], result1234);
+ }
+ }
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t, BlendMode mode>
+inline already_AddRefed<DataSourceSurface> ApplyBlending_SIMD(
+ DataSourceSurface* aInput1, DataSourceSurface* aInput2) {
+ IntSize size = aInput1->GetSize();
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (!target) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap inputMap1(aInput1, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap outputMap(target, DataSourceSurface::READ_WRITE);
+ if (aInput1->Equals(aInput2)) {
+ ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, mode>(inputMap1, inputMap1,
+ outputMap, size);
+ } else {
+ DataSourceSurface::ScopedMap inputMap2(aInput2, DataSourceSurface::READ);
+ ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, mode>(inputMap1, inputMap2,
+ outputMap, size);
+ }
+
+ return target.forget();
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t>
+static already_AddRefed<DataSourceSurface> ApplyBlending_SIMD(
+ DataSourceSurface* aInput1, DataSourceSurface* aInput2,
+ BlendMode aBlendMode) {
+ switch (aBlendMode) {
+ case BLEND_MODE_MULTIPLY:
+ return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_MULTIPLY>(
+ aInput1, aInput2);
+ case BLEND_MODE_SCREEN:
+ return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_SCREEN>(
+ aInput1, aInput2);
+ case BLEND_MODE_DARKEN:
+ return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_DARKEN>(
+ aInput1, aInput2);
+ case BLEND_MODE_LIGHTEN:
+ return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_LIGHTEN>(
+ aInput1, aInput2);
+ default:
+ return nullptr;
+ }
+}
+
+template <MorphologyOperator Operator, typename u8x16_t>
+static u8x16_t Morph8(u8x16_t a, u8x16_t b) {
+ return Operator == MORPHOLOGY_OPERATOR_ERODE ? simd::Min8(a, b)
+ : simd::Max8(a, b);
+}
+
+// Set every pixel to the per-component minimum or maximum of the pixels around
+// it that are up to aRadius pixels away from it (horizontally).
+template <MorphologyOperator op, typename i16x8_t, typename u8x16_t>
+inline void ApplyMorphologyHorizontal_SIMD(
+ uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData,
+ int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius) {
+ static_assert(
+ op == MORPHOLOGY_OPERATOR_ERODE || op == MORPHOLOGY_OPERATOR_DILATE,
+ "unexpected morphology operator");
+
+ int32_t kernelSize = aRadius + 1 + aRadius;
+ MOZ_ASSERT(kernelSize >= 3, "don't call this with aRadius <= 0");
+ MOZ_ASSERT(kernelSize % 4 == 1 || kernelSize % 4 == 3);
+ int32_t completeKernelSizeForFourPixels = kernelSize + 3;
+ MOZ_ASSERT(completeKernelSizeForFourPixels % 4 == 0 ||
+ completeKernelSizeForFourPixels % 4 == 2);
+
+ // aSourceData[-aRadius] and aDestData[0] are both aligned to 16 bytes, just
+ // the way we need them to be.
+
+ IntRect sourceRect = aDestRect;
+ sourceRect.Inflate(aRadius, 0);
+
+ for (int32_t y = aDestRect.Y(); y < aDestRect.YMost(); y++) {
+ int32_t kernelStartX = aDestRect.X() - aRadius;
+ for (int32_t x = aDestRect.X(); x < aDestRect.XMost();
+ x += 4, kernelStartX += 4) {
+ // We process four pixels (16 color values) at a time.
+ // aSourceData[0] points to the pixel located at aDestRect.TopLeft();
+ // source values can be read beyond that because the source is extended
+ // by aRadius pixels.
+
+ int32_t sourceIndex = y * aSourceStride + 4 * kernelStartX;
+ u8x16_t p1234 = simd::Load8<u8x16_t>(&aSourceData[sourceIndex]);
+ u8x16_t m1234 = p1234;
+
+ for (int32_t i = 4; i < completeKernelSizeForFourPixels; i += 4) {
+ u8x16_t p5678 =
+ (kernelStartX + i < sourceRect.XMost())
+ ? simd::Load8<u8x16_t>(&aSourceData[sourceIndex + 4 * i])
+ : simd::FromZero8<u8x16_t>();
+ u8x16_t p2345 = simd::Rotate8<4>(p1234, p5678);
+ u8x16_t p3456 = simd::Rotate8<8>(p1234, p5678);
+ m1234 = Morph8<op, u8x16_t>(m1234, p2345);
+ m1234 = Morph8<op, u8x16_t>(m1234, p3456);
+ if (i + 2 < completeKernelSizeForFourPixels) {
+ u8x16_t p4567 = simd::Rotate8<12>(p1234, p5678);
+ m1234 = Morph8<op, u8x16_t>(m1234, p4567);
+ m1234 = Morph8<op, u8x16_t>(m1234, p5678);
+ }
+ p1234 = p5678;
+ }
+
+ int32_t destIndex = y * aDestStride + 4 * x;
+ simd::Store8(&aDestData[destIndex], m1234);
+ }
+ }
+}
+
+template <typename i16x8_t, typename u8x16_t>
+inline void ApplyMorphologyHorizontal_SIMD(
+ 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) {
+ ApplyMorphologyHorizontal_SIMD<MORPHOLOGY_OPERATOR_ERODE, i16x8_t, u8x16_t>(
+ aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
+ } else {
+ ApplyMorphologyHorizontal_SIMD<MORPHOLOGY_OPERATOR_DILATE, i16x8_t,
+ u8x16_t>(
+ aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
+ }
+}
+
+// Set every pixel to the per-component minimum or maximum of the pixels around
+// it that are up to aRadius pixels away from it (vertically).
+template <MorphologyOperator op, typename i16x8_t, typename u8x16_t>
+static void ApplyMorphologyVertical_SIMD(
+ uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData,
+ int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius) {
+ static_assert(
+ op == MORPHOLOGY_OPERATOR_ERODE || op == 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 += 4) {
+ int32_t sourceIndex = startY * aSourceStride + 4 * x;
+ u8x16_t u = simd::Load8<u8x16_t>(&aSourceData[sourceIndex]);
+ sourceIndex += aSourceStride;
+ for (int32_t iy = startY + 1; iy <= endY;
+ iy++, sourceIndex += aSourceStride) {
+ u8x16_t u2 = simd::Load8<u8x16_t>(&aSourceData[sourceIndex]);
+ u = Morph8<op, u8x16_t>(u, u2);
+ }
+
+ int32_t destIndex = y * aDestStride + 4 * x;
+ simd::Store8(&aDestData[destIndex], u);
+ }
+ }
+}
+
+template <typename i16x8_t, typename u8x16_t>
+inline void ApplyMorphologyVertical_SIMD(
+ 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) {
+ ApplyMorphologyVertical_SIMD<MORPHOLOGY_OPERATOR_ERODE, i16x8_t, u8x16_t>(
+ aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
+ } else {
+ ApplyMorphologyVertical_SIMD<MORPHOLOGY_OPERATOR_DILATE, i16x8_t, u8x16_t>(
+ aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius);
+ }
+}
+
+template <typename i32x4_t, typename i16x8_t>
+static i32x4_t ColorMatrixMultiply(i16x8_t p, i16x8_t rows_bg, i16x8_t rows_ra,
+ const i32x4_t& bias) {
+ // int16_t p[8] == { b, g, r, a, b, g, r, a }.
+ // int16_t rows_bg[8] == { bB, bG, bR, bA, gB, gG, gR, gA }.
+ // int16_t rows_ra[8] == { rB, rG, rR, rA, aB, aG, aR, aA }.
+ // int32_t bias[4] == { _B, _G, _R, _A }.
+
+ i32x4_t sum = bias;
+
+ // int16_t bg[8] = { b, g, b, g, b, g, b, g };
+ i16x8_t bg = simd::ShuffleHi16<1, 0, 1, 0>(simd::ShuffleLo16<1, 0, 1, 0>(p));
+ // int32_t prodsum_bg[4] =
+ // { b * bB + g * gB, b * bG + g * gG, b * bR + g * gR, b * bA + g * gA }
+ i32x4_t prodsum_bg = simd::MulAdd16x8x2To32x4(bg, rows_bg);
+ sum = simd::Add32(sum, prodsum_bg);
+
+ // uint16_t ra[8] = { r, a, r, a, r, a, r, a };
+ i16x8_t ra = simd::ShuffleHi16<3, 2, 3, 2>(simd::ShuffleLo16<3, 2, 3, 2>(p));
+ // int32_t prodsum_ra[4] =
+ // { r * rB + a * aB, r * rG + a * aG, r * rR + a * aR, r * rA + a * aA }
+ i32x4_t prodsum_ra = simd::MulAdd16x8x2To32x4(ra, rows_ra);
+ sum = simd::Add32(sum, prodsum_ra);
+
+ // int32_t sum[4] == { b * bB + g * gB + r * rB + a * aB + _B, ... }.
+ return sum;
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t>
+static already_AddRefed<DataSourceSurface> ApplyColorMatrix_SIMD(
+ DataSourceSurface* aInput, const Matrix5x4& aMatrix) {
+ IntSize size = aInput->GetSize();
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (!target) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap inputMap(aInput, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap outputMap(target, DataSourceSurface::READ_WRITE);
+
+ uint8_t* sourceData = inputMap.GetData();
+ uint8_t* targetData = outputMap.GetData();
+ int32_t sourceStride = inputMap.GetStride();
+ int32_t targetStride = outputMap.GetStride();
+
+ const int16_t factor = 128;
+ const Float floatElementMax = INT16_MAX / factor; // 255
+ MOZ_ASSERT((floatElementMax * factor) <= INT16_MAX,
+ "badly chosen float-to-int scale");
+
+ const Float* floats = &aMatrix._11;
+
+ ptrdiff_t componentOffsets[4] = {
+ B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G,
+ B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A};
+
+ // We store the color matrix in rows_bgra in the following format:
+ // { bB, bG, bR, bA, gB, gG, gR, gA }.
+ // { bB, gB, bG, gG, bR, gR, bA, gA }
+ // The way this is interleaved allows us to use the intrinsic _mm_madd_epi16
+ // which works especially well for our use case.
+ int16_t rows_bgra[2][8];
+ for (size_t rowIndex = 0; rowIndex < 4; rowIndex++) {
+ for (size_t colIndex = 0; colIndex < 4; colIndex++) {
+ const Float& floatMatrixElement = floats[rowIndex * 4 + colIndex];
+ Float clampedFloatMatrixElement = std::min(
+ std::max(floatMatrixElement, -floatElementMax), floatElementMax);
+ int16_t scaledIntMatrixElement =
+ int16_t(clampedFloatMatrixElement * factor + 0.5);
+ int8_t bg_or_ra = componentOffsets[rowIndex] / 2;
+ int8_t g_or_a = componentOffsets[rowIndex] % 2;
+ int8_t B_or_G_or_R_or_A = componentOffsets[colIndex];
+ rows_bgra[bg_or_ra][B_or_G_or_R_or_A * 2 + g_or_a] =
+ scaledIntMatrixElement;
+ }
+ }
+
+ int32_t rowBias[4];
+ Float biasMax = (INT32_MAX - 4 * 255 * INT16_MAX) / (factor * 255);
+ for (size_t colIndex = 0; colIndex < 4; colIndex++) {
+ size_t rowIndex = 4;
+ const Float& floatMatrixElement = floats[rowIndex * 4 + colIndex];
+ Float clampedFloatMatrixElement =
+ std::min(std::max(floatMatrixElement, -biasMax), biasMax);
+ int32_t scaledIntMatrixElement =
+ int32_t(clampedFloatMatrixElement * factor * 255 + 0.5);
+ rowBias[componentOffsets[colIndex]] = scaledIntMatrixElement;
+ }
+
+ i16x8_t row_bg_v = simd::FromI16<i16x8_t>(
+ rows_bgra[0][0], rows_bgra[0][1], rows_bgra[0][2], rows_bgra[0][3],
+ rows_bgra[0][4], rows_bgra[0][5], rows_bgra[0][6], rows_bgra[0][7]);
+
+ i16x8_t row_ra_v = simd::FromI16<i16x8_t>(
+ rows_bgra[1][0], rows_bgra[1][1], rows_bgra[1][2], rows_bgra[1][3],
+ rows_bgra[1][4], rows_bgra[1][5], rows_bgra[1][6], rows_bgra[1][7]);
+
+ i32x4_t rowsBias_v =
+ simd::From32<i32x4_t>(rowBias[0], rowBias[1], rowBias[2], rowBias[3]);
+
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x += 4) {
+ MOZ_ASSERT(sourceStride >= 4 * (x + 4),
+ "need to be able to read 4 pixels at this position");
+ MOZ_ASSERT(targetStride >= 4 * (x + 4),
+ "need to be able to write 4 pixels at this position");
+ int32_t sourceIndex = y * sourceStride + 4 * x;
+ int32_t targetIndex = y * targetStride + 4 * x;
+
+ // We load 4 pixels, unpack them, process them 1 pixel at a time, and
+ // finally pack and store the 4 result pixels.
+
+ u8x16_t p1234 = simd::Load8<u8x16_t>(&sourceData[sourceIndex]);
+
+ // Splat needed to get each pixel twice into i16x8
+ i16x8_t p11 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<0>(p1234));
+ i16x8_t p22 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<1>(p1234));
+ i16x8_t p33 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<2>(p1234));
+ i16x8_t p44 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<3>(p1234));
+
+ i32x4_t result_p1 =
+ ColorMatrixMultiply(p11, row_bg_v, row_ra_v, rowsBias_v);
+ i32x4_t result_p2 =
+ ColorMatrixMultiply(p22, row_bg_v, row_ra_v, rowsBias_v);
+ i32x4_t result_p3 =
+ ColorMatrixMultiply(p33, row_bg_v, row_ra_v, rowsBias_v);
+ i32x4_t result_p4 =
+ ColorMatrixMultiply(p44, row_bg_v, row_ra_v, rowsBias_v);
+
+ static_assert(factor == 1 << 7,
+ "Please adapt the calculation in the lines below for a "
+ "different factor.");
+ u8x16_t result_p1234 = simd::PackAndSaturate32To8(
+ simd::ShiftRight32<7>(result_p1), simd::ShiftRight32<7>(result_p2),
+ simd::ShiftRight32<7>(result_p3), simd::ShiftRight32<7>(result_p4));
+ simd::Store8(&targetData[targetIndex], result_p1234);
+ }
+ }
+
+ return target.forget();
+}
+
+// source / dest: bgra bgra
+// sourceAlpha / destAlpha: aaaa aaaa
+// result: bgra bgra
+template <typename i32x4_t, typename u16x8_t, uint32_t aCompositeOperator>
+static inline u16x8_t CompositeTwoPixels(u16x8_t source, u16x8_t sourceAlpha,
+ u16x8_t dest,
+ const u16x8_t& destAlpha) {
+ u16x8_t x255 = simd::FromU16<u16x8_t>(255);
+
+ switch (aCompositeOperator) {
+ case COMPOSITE_OPERATOR_OVER: {
+ // val = dest * (255 - sourceAlpha) + source * 255;
+ u16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha);
+
+ u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source);
+ u16x8_t rightFactor1 =
+ simd::InterleaveLo16(twoFiftyFiveMinusSourceAlpha, x255);
+ i32x4_t result1 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1);
+
+ u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source);
+ u16x8_t rightFactor2 =
+ simd::InterleaveHi16(twoFiftyFiveMinusSourceAlpha, x255);
+ i32x4_t result2 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2);
+
+ return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1),
+ simd::FastDivideBy255(result2));
+ }
+
+ case COMPOSITE_OPERATOR_IN: {
+ // val = source * destAlpha;
+ return simd::FastDivideBy255_16(simd::Mul16(source, destAlpha));
+ }
+
+ case COMPOSITE_OPERATOR_OUT: {
+ // val = source * (255 - destAlpha);
+ u16x8_t prod = simd::Mul16(source, simd::Sub16(x255, destAlpha));
+ return simd::FastDivideBy255_16(prod);
+ }
+
+ case COMPOSITE_OPERATOR_ATOP: {
+ // val = dest * (255 - sourceAlpha) + source * destAlpha;
+ u16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha);
+
+ u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source);
+ u16x8_t rightFactor1 =
+ simd::InterleaveLo16(twoFiftyFiveMinusSourceAlpha, destAlpha);
+ i32x4_t result1 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1);
+
+ u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source);
+ u16x8_t rightFactor2 =
+ simd::InterleaveHi16(twoFiftyFiveMinusSourceAlpha, destAlpha);
+ i32x4_t result2 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2);
+
+ return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1),
+ simd::FastDivideBy255(result2));
+ }
+
+ case COMPOSITE_OPERATOR_XOR: {
+ // val = dest * (255 - sourceAlpha) + source * (255 - destAlpha);
+ u16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha);
+ u16x8_t twoFiftyFiveMinusDestAlpha = simd::Sub16(x255, destAlpha);
+
+ u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source);
+ u16x8_t rightFactor1 = simd::InterleaveLo16(twoFiftyFiveMinusSourceAlpha,
+ twoFiftyFiveMinusDestAlpha);
+ i32x4_t result1 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1);
+
+ u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source);
+ u16x8_t rightFactor2 = simd::InterleaveHi16(twoFiftyFiveMinusSourceAlpha,
+ twoFiftyFiveMinusDestAlpha);
+ i32x4_t result2 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2);
+
+ return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1),
+ simd::FastDivideBy255(result2));
+ }
+
+ case COMPOSITE_OPERATOR_LIGHTER: {
+ // val = dest * sourceAlpha + source * destAlpha;
+ u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source);
+ u16x8_t rightFactor1 = simd::InterleaveLo16(sourceAlpha, destAlpha);
+ i32x4_t result1 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1);
+
+ u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source);
+ u16x8_t rightFactor2 = simd::InterleaveHi16(sourceAlpha, destAlpha);
+ i32x4_t result2 =
+ simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2);
+
+ return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1),
+ simd::FastDivideBy255(result2));
+ }
+
+ default:
+ return simd::FromU16<u16x8_t>(0);
+ }
+}
+
+template <typename i32x4_t, typename u16x8_t, typename u8x16_t, uint32_t op>
+static void ApplyComposition(DataSourceSurface* aSource,
+ DataSourceSurface* aDest) {
+ IntSize size = aDest->GetSize();
+
+ DataSourceSurface::ScopedMap input(aSource, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap output(aDest, DataSourceSurface::READ_WRITE);
+
+ uint8_t* sourceData = input.GetData();
+ uint8_t* destData = output.GetData();
+ uint32_t sourceStride = input.GetStride();
+ uint32_t destStride = output.GetStride();
+
+ for (int32_t y = 0; y < size.height; y++) {
+ for (int32_t x = 0; x < size.width; x += 4) {
+ uint32_t sourceIndex = y * sourceStride + 4 * x;
+ uint32_t destIndex = y * destStride + 4 * x;
+
+ u8x16_t s1234 = simd::Load8<u8x16_t>(&sourceData[sourceIndex]);
+ u8x16_t d1234 = simd::Load8<u8x16_t>(&destData[destIndex]);
+
+ u16x8_t s12 = simd::UnpackLo8x8ToU16x8(s1234);
+ u16x8_t d12 = simd::UnpackLo8x8ToU16x8(d1234);
+ u16x8_t sa12 = simd::Splat16<3, 3>(s12);
+ u16x8_t da12 = simd::Splat16<3, 3>(d12);
+ u16x8_t result12 =
+ CompositeTwoPixels<i32x4_t, u16x8_t, op>(s12, sa12, d12, da12);
+
+ u16x8_t s34 = simd::UnpackHi8x8ToU16x8(s1234);
+ u16x8_t d34 = simd::UnpackHi8x8ToU16x8(d1234);
+ u16x8_t sa34 = simd::Splat16<3, 3>(s34);
+ u16x8_t da34 = simd::Splat16<3, 3>(d34);
+ u16x8_t result34 =
+ CompositeTwoPixels<i32x4_t, u16x8_t, op>(s34, sa34, d34, da34);
+
+ u8x16_t result1234 = simd::PackAndSaturate16To8(result12, result34);
+ simd::Store8(&destData[destIndex], result1234);
+ }
+ }
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t>
+static void ApplyComposition_SIMD(DataSourceSurface* aSource,
+ DataSourceSurface* aDest,
+ CompositeOperator aOperator) {
+ switch (aOperator) {
+ case COMPOSITE_OPERATOR_OVER:
+ ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_OVER>(
+ aSource, aDest);
+ break;
+ case COMPOSITE_OPERATOR_IN:
+ ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_IN>(
+ aSource, aDest);
+ break;
+ case COMPOSITE_OPERATOR_OUT:
+ ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_OUT>(
+ aSource, aDest);
+ break;
+ case COMPOSITE_OPERATOR_ATOP:
+ ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_ATOP>(
+ aSource, aDest);
+ break;
+ case COMPOSITE_OPERATOR_XOR:
+ ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_XOR>(
+ aSource, aDest);
+ break;
+ case COMPOSITE_OPERATOR_LIGHTER:
+ ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_LIGHTER>(
+ aSource, aDest);
+ break;
+ default:
+ MOZ_CRASH("GFX: Incomplete switch");
+ }
+}
+
+template <typename u8x16_t>
+static void SeparateColorChannels_SIMD(
+ 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 += 16) {
+ // Process 16 pixels at a time.
+ int32_t sourceIndex = y * sourceStride + 4 * x;
+ int32_t targetIndex = y * channelStride + x;
+
+ u8x16_t bgrabgrabgrabgra2 = simd::FromZero8<u8x16_t>();
+ u8x16_t bgrabgrabgrabgra3 = simd::FromZero8<u8x16_t>();
+ u8x16_t bgrabgrabgrabgra4 = simd::FromZero8<u8x16_t>();
+
+ u8x16_t bgrabgrabgrabgra1 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex]);
+ if (4 * (x + 4) < sourceStride) {
+ bgrabgrabgrabgra2 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 4]);
+ }
+ if (4 * (x + 8) < sourceStride) {
+ bgrabgrabgrabgra3 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 8]);
+ }
+ if (4 * (x + 12) < sourceStride) {
+ bgrabgrabgrabgra4 =
+ simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 12]);
+ }
+
+ u8x16_t bbggrraabbggrraa1 =
+ simd::InterleaveLo8(bgrabgrabgrabgra1, bgrabgrabgrabgra3);
+ u8x16_t bbggrraabbggrraa2 =
+ simd::InterleaveHi8(bgrabgrabgrabgra1, bgrabgrabgrabgra3);
+ u8x16_t bbggrraabbggrraa3 =
+ simd::InterleaveLo8(bgrabgrabgrabgra2, bgrabgrabgrabgra4);
+ u8x16_t bbggrraabbggrraa4 =
+ simd::InterleaveHi8(bgrabgrabgrabgra2, bgrabgrabgrabgra4);
+ u8x16_t bbbbggggrrrraaaa1 =
+ simd::InterleaveLo8(bbggrraabbggrraa1, bbggrraabbggrraa3);
+ u8x16_t bbbbggggrrrraaaa2 =
+ simd::InterleaveHi8(bbggrraabbggrraa1, bbggrraabbggrraa3);
+ u8x16_t bbbbggggrrrraaaa3 =
+ simd::InterleaveLo8(bbggrraabbggrraa2, bbggrraabbggrraa4);
+ u8x16_t bbbbggggrrrraaaa4 =
+ simd::InterleaveHi8(bbggrraabbggrraa2, bbggrraabbggrraa4);
+ u8x16_t bbbbbbbbgggggggg1 =
+ simd::InterleaveLo8(bbbbggggrrrraaaa1, bbbbggggrrrraaaa3);
+ u8x16_t rrrrrrrraaaaaaaa1 =
+ simd::InterleaveHi8(bbbbggggrrrraaaa1, bbbbggggrrrraaaa3);
+ u8x16_t bbbbbbbbgggggggg2 =
+ simd::InterleaveLo8(bbbbggggrrrraaaa2, bbbbggggrrrraaaa4);
+ u8x16_t rrrrrrrraaaaaaaa2 =
+ simd::InterleaveHi8(bbbbggggrrrraaaa2, bbbbggggrrrraaaa4);
+ u8x16_t bbbbbbbbbbbbbbbb =
+ simd::InterleaveLo8(bbbbbbbbgggggggg1, bbbbbbbbgggggggg2);
+ u8x16_t gggggggggggggggg =
+ simd::InterleaveHi8(bbbbbbbbgggggggg1, bbbbbbbbgggggggg2);
+ u8x16_t rrrrrrrrrrrrrrrr =
+ simd::InterleaveLo8(rrrrrrrraaaaaaaa1, rrrrrrrraaaaaaaa2);
+ u8x16_t aaaaaaaaaaaaaaaa =
+ simd::InterleaveHi8(rrrrrrrraaaaaaaa1, rrrrrrrraaaaaaaa2);
+
+ simd::Store8(&channel0Data[targetIndex], bbbbbbbbbbbbbbbb);
+ simd::Store8(&channel1Data[targetIndex], gggggggggggggggg);
+ simd::Store8(&channel2Data[targetIndex], rrrrrrrrrrrrrrrr);
+ simd::Store8(&channel3Data[targetIndex], aaaaaaaaaaaaaaaa);
+ }
+ }
+}
+
+template <typename u8x16_t>
+static void CombineColorChannels_SIMD(
+ 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 += 16) {
+ // Process 16 pixels at a time.
+ int32_t resultIndex = y * resultStride + 4 * x;
+ int32_t channelIndex = y * channelStride + x;
+
+ u8x16_t bbbbbbbbbbbbbbbb =
+ simd::Load8<u8x16_t>(&channel0Data[channelIndex]);
+ u8x16_t gggggggggggggggg =
+ simd::Load8<u8x16_t>(&channel1Data[channelIndex]);
+ u8x16_t rrrrrrrrrrrrrrrr =
+ simd::Load8<u8x16_t>(&channel2Data[channelIndex]);
+ u8x16_t aaaaaaaaaaaaaaaa =
+ simd::Load8<u8x16_t>(&channel3Data[channelIndex]);
+
+ u8x16_t brbrbrbrbrbrbrbr1 =
+ simd::InterleaveLo8(bbbbbbbbbbbbbbbb, rrrrrrrrrrrrrrrr);
+ u8x16_t brbrbrbrbrbrbrbr2 =
+ simd::InterleaveHi8(bbbbbbbbbbbbbbbb, rrrrrrrrrrrrrrrr);
+ u8x16_t gagagagagagagaga1 =
+ simd::InterleaveLo8(gggggggggggggggg, aaaaaaaaaaaaaaaa);
+ u8x16_t gagagagagagagaga2 =
+ simd::InterleaveHi8(gggggggggggggggg, aaaaaaaaaaaaaaaa);
+
+ u8x16_t bgrabgrabgrabgra1 =
+ simd::InterleaveLo8(brbrbrbrbrbrbrbr1, gagagagagagagaga1);
+ u8x16_t bgrabgrabgrabgra2 =
+ simd::InterleaveHi8(brbrbrbrbrbrbrbr1, gagagagagagagaga1);
+ u8x16_t bgrabgrabgrabgra3 =
+ simd::InterleaveLo8(brbrbrbrbrbrbrbr2, gagagagagagagaga2);
+ u8x16_t bgrabgrabgrabgra4 =
+ simd::InterleaveHi8(brbrbrbrbrbrbrbr2, gagagagagagagaga2);
+
+ simd::Store8(&resultData[resultIndex], bgrabgrabgrabgra1);
+ if (4 * (x + 4) < resultStride) {
+ simd::Store8(&resultData[resultIndex + 4 * 4], bgrabgrabgrabgra2);
+ }
+ if (4 * (x + 8) < resultStride) {
+ simd::Store8(&resultData[resultIndex + 8 * 4], bgrabgrabgrabgra3);
+ }
+ if (4 * (x + 12) < resultStride) {
+ simd::Store8(&resultData[resultIndex + 12 * 4], bgrabgrabgrabgra4);
+ }
+ }
+ }
+}
+
+template <typename i32x4_t, typename u16x8_t, typename u8x16_t>
+static void DoPremultiplicationCalculation_SIMD(const IntSize& aSize,
+ uint8_t* aTargetData,
+ int32_t aTargetStride,
+ uint8_t* aSourceData,
+ int32_t aSourceStride) {
+ const u8x16_t alphaMask = simd::From8<u8x16_t>(0, 0, 0, 0xff, 0, 0, 0, 0xff,
+ 0, 0, 0, 0xff, 0, 0, 0, 0xff);
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x += 4) {
+ int32_t inputIndex = y * aSourceStride + 4 * x;
+ int32_t targetIndex = y * aTargetStride + 4 * x;
+
+ u8x16_t p1234 = simd::Load8<u8x16_t>(&aSourceData[inputIndex]);
+ u16x8_t p12 = simd::UnpackLo8x8ToU16x8(p1234);
+ u16x8_t p34 = simd::UnpackHi8x8ToU16x8(p1234);
+
+ // Multiply all components with alpha.
+ p12 = simd::Mul16(p12, simd::Splat16<3, 3>(p12));
+ p34 = simd::Mul16(p34, simd::Splat16<3, 3>(p34));
+
+ // Divide by 255 and pack.
+ u8x16_t result = simd::PackAndSaturate16To8(
+ simd::FastDivideBy255_16(p12), simd::FastDivideBy255_16(p34));
+
+ // Get the original alpha channel value back from p1234.
+ result = simd::Pick(alphaMask, result, p1234);
+
+ simd::Store8(&aTargetData[targetIndex], result);
+ }
+ }
+}
+
+// We use a table of precomputed factors for unpremultiplying.
+// We want to compute round(r / (alpha / 255.0f)) for arbitrary values of
+// r and alpha in constant time. This table of factors has the property that
+// (r * sAlphaFactors[alpha] + 128) >> 8 roughly gives the result we want (with
+// a maximum deviation of 1).
+//
+// sAlphaFactors[alpha] == round(255.0 * (1 << 8) / alpha)
+//
+// This table has been created using the python code
+// ", ".join("%d" % (round(255.0 * 256 / alpha) if alpha > 0 else 0) for alpha
+// in range(256))
+static const uint16_t sAlphaFactors[256] = {
+ 0, 65280, 32640, 21760, 16320, 13056, 10880, 9326, 8160, 7253, 6528,
+ 5935, 5440, 5022, 4663, 4352, 4080, 3840, 3627, 3436, 3264, 3109,
+ 2967, 2838, 2720, 2611, 2511, 2418, 2331, 2251, 2176, 2106, 2040,
+ 1978, 1920, 1865, 1813, 1764, 1718, 1674, 1632, 1592, 1554, 1518,
+ 1484, 1451, 1419, 1389, 1360, 1332, 1306, 1280, 1255, 1232, 1209,
+ 1187, 1166, 1145, 1126, 1106, 1088, 1070, 1053, 1036, 1020, 1004,
+ 989, 974, 960, 946, 933, 919, 907, 894, 882, 870, 859,
+ 848, 837, 826, 816, 806, 796, 787, 777, 768, 759, 750,
+ 742, 733, 725, 717, 710, 702, 694, 687, 680, 673, 666,
+ 659, 653, 646, 640, 634, 628, 622, 616, 610, 604, 599,
+ 593, 588, 583, 578, 573, 568, 563, 558, 553, 549, 544,
+ 540, 535, 531, 526, 522, 518, 514, 510, 506, 502, 498,
+ 495, 491, 487, 484, 480, 476, 473, 470, 466, 463, 460,
+ 457, 453, 450, 447, 444, 441, 438, 435, 432, 429, 427,
+ 424, 421, 418, 416, 413, 411, 408, 405, 403, 400, 398,
+ 396, 393, 391, 389, 386, 384, 382, 380, 377, 375, 373,
+ 371, 369, 367, 365, 363, 361, 359, 357, 355, 353, 351,
+ 349, 347, 345, 344, 342, 340, 338, 336, 335, 333, 331,
+ 330, 328, 326, 325, 323, 322, 320, 318, 317, 315, 314,
+ 312, 311, 309, 308, 306, 305, 304, 302, 301, 299, 298,
+ 297, 295, 294, 293, 291, 290, 289, 288, 286, 285, 284,
+ 283, 281, 280, 279, 278, 277, 275, 274, 273, 272, 271,
+ 270, 269, 268, 266, 265, 264, 263, 262, 261, 260, 259,
+ 258, 257, 256};
+
+template <typename u16x8_t, typename u8x16_t>
+static void DoUnpremultiplicationCalculation_SIMD(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 += 4) {
+ int32_t inputIndex = y * aSourceStride + 4 * x;
+ int32_t targetIndex = y * aTargetStride + 4 * x;
+ union {
+ u8x16_t p1234;
+ uint8_t u8[4][4];
+ };
+ p1234 = simd::Load8<u8x16_t>(&aSourceData[inputIndex]);
+
+ // Prepare the alpha factors.
+ uint16_t aF1 = sAlphaFactors[u8[0][B8G8R8A8_COMPONENT_BYTEOFFSET_A]];
+ uint16_t aF2 = sAlphaFactors[u8[1][B8G8R8A8_COMPONENT_BYTEOFFSET_A]];
+ uint16_t aF3 = sAlphaFactors[u8[2][B8G8R8A8_COMPONENT_BYTEOFFSET_A]];
+ uint16_t aF4 = sAlphaFactors[u8[3][B8G8R8A8_COMPONENT_BYTEOFFSET_A]];
+ u16x8_t aF12 =
+ simd::FromU16<u16x8_t>(aF1, aF1, aF1, 1 << 8, aF2, aF2, aF2, 1 << 8);
+ u16x8_t aF34 =
+ simd::FromU16<u16x8_t>(aF3, aF3, aF3, 1 << 8, aF4, aF4, aF4, 1 << 8);
+
+ u16x8_t p12 = simd::UnpackLo8x8ToU16x8(p1234);
+ u16x8_t p34 = simd::UnpackHi8x8ToU16x8(p1234);
+
+ // Multiply with the alpha factors, add 128 for rounding, and shift right
+ // by 8 bits.
+ p12 = simd::ShiftRight16<8>(
+ simd::Add16(simd::Mul16(p12, aF12), simd::FromU16<u16x8_t>(128)));
+ p34 = simd::ShiftRight16<8>(
+ simd::Add16(simd::Mul16(p34, aF34), simd::FromU16<u16x8_t>(128)));
+
+ u8x16_t result = simd::PackAndSaturate16To8(p12, p34);
+ simd::Store8(&aTargetData[targetIndex], result);
+ }
+ }
+}
+
+template <typename u16x8_t, typename u8x16_t>
+static void DoOpacityCalculation_SIMD(const IntSize& aSize,
+ uint8_t* aTargetData,
+ int32_t aTargetStride,
+ uint8_t* aSourceData,
+ int32_t aSourceStride, Float aOpacity) {
+ uint8_t alphaValue = uint8_t(roundf(255.f * aOpacity));
+ u16x8_t alphaValues =
+ simd::FromU16<u16x8_t>(alphaValue, alphaValue, alphaValue, alphaValue,
+ alphaValue, alphaValue, alphaValue, alphaValue);
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x += 4) {
+ int32_t inputIndex = y * aSourceStride + 4 * x;
+ int32_t targetIndex = y * aTargetStride + 4 * x;
+
+ u8x16_t p1234 = simd::Load8<u8x16_t>(&aSourceData[inputIndex]);
+ u16x8_t p12 = simd::UnpackLo8x8ToU16x8(p1234);
+ u16x8_t p34 = simd::UnpackHi8x8ToU16x8(p1234);
+
+ // Multiply all components with alpha.
+ p12 = simd::Mul16(p12, alphaValues);
+ p34 = simd::Mul16(p34, alphaValues);
+
+ // Divide by 255 and pack.
+ u8x16_t result = simd::PackAndSaturate16To8(simd::ShiftRight16<8>(p12),
+ simd::ShiftRight16<8>(p34));
+
+ simd::Store8(&aTargetData[targetIndex], result);
+ }
+ }
+}
+
+template <typename f32x4_t, typename i32x4_t, typename u8x16_t>
+static already_AddRefed<DataSourceSurface> RenderTurbulence_SIMD(
+ const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency,
+ int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch,
+ const Rect& aTileRect) {
+#define RETURN_TURBULENCE(Type, Stitch) \
+ SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t> renderer( \
+ aBaseFrequency, aSeed, aNumOctaves, aTileRect); \
+ return renderer.Render(aSize, aOffset);
+
+ switch (aType) {
+ case TURBULENCE_TYPE_TURBULENCE: {
+ if (aStitch) {
+ RETURN_TURBULENCE(TURBULENCE_TYPE_TURBULENCE, true);
+ }
+ RETURN_TURBULENCE(TURBULENCE_TYPE_TURBULENCE, false);
+ }
+ case TURBULENCE_TYPE_FRACTAL_NOISE: {
+ if (aStitch) {
+ RETURN_TURBULENCE(TURBULENCE_TYPE_FRACTAL_NOISE, true);
+ }
+ RETURN_TURBULENCE(TURBULENCE_TYPE_FRACTAL_NOISE, false);
+ }
+ }
+ return nullptr;
+#undef RETURN_TURBULENCE
+}
+
+// k1 * in1 * in2 + k2 * in1 + k3 * in2 + k4
+template <typename i32x4_t, typename i16x8_t>
+static MOZ_ALWAYS_INLINE i16x8_t ArithmeticCombineTwoPixels(
+ i16x8_t in1, i16x8_t in2, const i16x8_t& k1And4, const i16x8_t& k2And3) {
+ // Calculate input product: inProd = (in1 * in2) / 255.
+ i32x4_t inProd_1, inProd_2;
+ simd::Mul16x4x2x2To32x4x2(in1, in2, inProd_1, inProd_2);
+ i16x8_t inProd = simd::PackAndSaturate32To16(simd::FastDivideBy255(inProd_1),
+ simd::FastDivideBy255(inProd_2));
+
+ // Calculate k1 * ((in1 * in2) / 255) + (k4/128) * 128
+ i16x8_t oneTwentyEight = simd::FromI16<i16x8_t>(128);
+ i16x8_t inProd1AndOneTwentyEight =
+ simd::InterleaveLo16(inProd, oneTwentyEight);
+ i16x8_t inProd2AndOneTwentyEight =
+ simd::InterleaveHi16(inProd, oneTwentyEight);
+ i32x4_t inProdTimesK1PlusK4_1 =
+ simd::MulAdd16x8x2To32x4(k1And4, inProd1AndOneTwentyEight);
+ i32x4_t inProdTimesK1PlusK4_2 =
+ simd::MulAdd16x8x2To32x4(k1And4, inProd2AndOneTwentyEight);
+
+ // Calculate k2 * in1 + k3 * in2
+ i16x8_t in12_1 = simd::InterleaveLo16(in1, in2);
+ i16x8_t in12_2 = simd::InterleaveHi16(in1, in2);
+ i32x4_t inTimesK2K3_1 = simd::MulAdd16x8x2To32x4(k2And3, in12_1);
+ i32x4_t inTimesK2K3_2 = simd::MulAdd16x8x2To32x4(k2And3, in12_2);
+
+ // Sum everything up and truncate the fractional part.
+ i32x4_t result_1 =
+ simd::ShiftRight32<7>(simd::Add32(inProdTimesK1PlusK4_1, inTimesK2K3_1));
+ i32x4_t result_2 =
+ simd::ShiftRight32<7>(simd::Add32(inProdTimesK1PlusK4_2, inTimesK2K3_2));
+ return simd::PackAndSaturate32To16(result_1, result_2);
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t>
+static void ApplyArithmeticCombine_SIMD(
+ const DataSourceSurface::ScopedMap& aInputMap1,
+ const DataSourceSurface::ScopedMap& aInputMap2,
+ const DataSourceSurface::ScopedMap& aOutputMap, const IntSize& aSize,
+ Float aK1, Float aK2, Float aK3, Float aK4) {
+ uint8_t* source1Data = aInputMap1.GetData();
+ uint8_t* source2Data = aInputMap2.GetData();
+ uint8_t* targetData = aOutputMap.GetData();
+ uint32_t source1Stride = aInputMap1.GetStride();
+ uint32_t source2Stride = aInputMap2.GetStride();
+ uint32_t targetStride = aOutputMap.GetStride();
+
+ // The arithmetic combine filter does the following calculation:
+ // result = k1 * in1 * in2 + k2 * in1 + k3 * in2 + k4
+ //
+ // Or, with in1/2 integers between 0 and 255:
+ // result = (k1 * in1 * in2) / 255 + k2 * in1 + k3 * in2 + k4 * 255
+ //
+ // We want the whole calculation to happen in integer, with 16-bit factors.
+ // So we convert our factors to fixed-point with precision 1.8.7.
+ // K4 is premultiplied with 255, and it will be multiplied with 128 later
+ // during the actual calculation, because premultiplying it with 255 * 128
+ // would overflow int16.
+
+ i16x8_t k1 = simd::FromI16<i16x8_t>(
+ int16_t(floorf(std::min(std::max(aK1, -255.0f), 255.0f) * 128 + 0.5f)));
+ i16x8_t k2 = simd::FromI16<i16x8_t>(
+ int16_t(floorf(std::min(std::max(aK2, -255.0f), 255.0f) * 128 + 0.5f)));
+ i16x8_t k3 = simd::FromI16<i16x8_t>(
+ int16_t(floorf(std::min(std::max(aK3, -255.0f), 255.0f) * 128 + 0.5f)));
+ i16x8_t k4 = simd::FromI16<i16x8_t>(
+ int16_t(floorf(std::min(std::max(aK4, -128.0f), 128.0f) * 255 + 0.5f)));
+
+ i16x8_t k1And4 = simd::InterleaveLo16(k1, k4);
+ i16x8_t k2And3 = simd::InterleaveLo16(k2, k3);
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x += 4) {
+ uint32_t source1Index = y * source1Stride + 4 * x;
+ uint32_t source2Index = y * source2Stride + 4 * x;
+ uint32_t targetIndex = y * targetStride + 4 * x;
+
+ // Load and unpack.
+ u8x16_t in1 = simd::Load8<u8x16_t>(&source1Data[source1Index]);
+ u8x16_t in2 = simd::Load8<u8x16_t>(&source2Data[source2Index]);
+ i16x8_t in1_12 = simd::UnpackLo8x8ToI16x8(in1);
+ i16x8_t in1_34 = simd::UnpackHi8x8ToI16x8(in1);
+ i16x8_t in2_12 = simd::UnpackLo8x8ToI16x8(in2);
+ i16x8_t in2_34 = simd::UnpackHi8x8ToI16x8(in2);
+
+ // Multiply and add.
+ i16x8_t result_12 = ArithmeticCombineTwoPixels<i32x4_t, i16x8_t>(
+ in1_12, in2_12, k1And4, k2And3);
+ i16x8_t result_34 = ArithmeticCombineTwoPixels<i32x4_t, i16x8_t>(
+ in1_34, in2_34, k1And4, k2And3);
+
+ // Pack and store.
+ simd::Store8(&targetData[targetIndex],
+ simd::PackAndSaturate16To8(result_12, result_34));
+ }
+ }
+}
+
+template <typename i32x4_t, typename i16x8_t, typename u8x16_t>
+static already_AddRefed<DataSourceSurface> ApplyArithmeticCombine_SIMD(
+ DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1,
+ Float aK2, Float aK3, Float aK4) {
+ IntSize size = aInput1->GetSize();
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ if (!target) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap inputMap1(aInput1, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap outputMap(target, DataSourceSurface::READ_WRITE);
+
+ if (aInput1->Equals(aInput2)) {
+ ApplyArithmeticCombine_SIMD<i32x4_t, i16x8_t, u8x16_t>(
+ inputMap1, inputMap1, outputMap, size, aK1, aK2, aK3, aK4);
+ } else {
+ DataSourceSurface::ScopedMap inputMap2(aInput2, DataSourceSurface::READ);
+ ApplyArithmeticCombine_SIMD<i32x4_t, i16x8_t, u8x16_t>(
+ inputMap1, inputMap2, outputMap, size, aK1, aK2, aK3, aK4);
+ }
+
+ return target.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/FilterProcessingSSE2.cpp b/gfx/2d/FilterProcessingSSE2.cpp
new file mode 100644
index 0000000000..0b9e0aa3d1
--- /dev/null
+++ b/gfx/2d/FilterProcessingSSE2.cpp
@@ -0,0 +1,126 @@
+/* -*- 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/. */
+
+#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::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);
+}
+
+void FilterProcessing::DoOpacityCalculation_SSE2(
+ const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride,
+ uint8_t* aSourceData, int32_t aSourceStride, Float aValue) {
+ DoOpacityCalculation_SIMD<__m128i, __m128i>(
+ aSize, aTargetData, aTargetStride, aSourceData, aSourceStride, aValue);
+}
+
+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 mozilla::gfx
diff --git a/gfx/2d/FilterProcessingScalar.cpp b/gfx/2d/FilterProcessingScalar.cpp
new file mode 100644
index 0000000000..9cb6040349
--- /dev/null
+++ b/gfx/2d/FilterProcessingScalar.cpp
@@ -0,0 +1,299 @@
+/* -*- 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/. */
+
+#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;
+ }
+ }
+}
+
+void FilterProcessing::DoOpacityCalculation_Scalar(
+ const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride,
+ uint8_t* aSourceData, int32_t aSourceStride, Float aValue) {
+ uint8_t alpha = uint8_t(roundf(255.f * aValue));
+ 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;
+ aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
+ (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * alpha) >>
+ 8;
+ aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
+ (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * alpha) >>
+ 8;
+ aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
+ (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * alpha) >>
+ 8;
+ aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] =
+ (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] * alpha) >>
+ 8;
+ }
+ }
+}
+
+void FilterProcessing::DoOpacityCalculationA8_Scalar(
+ const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride,
+ uint8_t* aSourceData, int32_t aSourceStride, Float aValue) {
+ uint8_t alpha = uint8_t(255.f * aValue);
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x++) {
+ int32_t inputIndex = y * aSourceStride;
+ int32_t targetIndex = y * aTargetStride;
+ aTargetData[targetIndex] =
+ FastDivideBy255<uint8_t>(aSourceData[inputIndex] * 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
diff --git a/gfx/2d/Filters.h b/gfx/2d/Filters.h
new file mode 100644
index 0000000000..a9f77ce6aa
--- /dev/null
+++ b/gfx/2d/Filters.h
@@ -0,0 +1,446 @@
+/* -*- 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_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,
+ FILTER_BACKEND_CAPTURE
+};
+
+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,
+ COMPOSITE_OPERATOR_LIGHTER
+};
+
+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 };
+
+enum OpacityAtts { ATT_OPACITY_VALUE = 0 };
+
+enum OpacityInputs { IN_OPACITY_IN = 0 };
+
+class FilterNode : public external::AtomicRefCounted<FilterNode> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNode)
+ virtual ~FilterNode() = default;
+
+ 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 DeviceColor&) {
+ MOZ_CRASH("GFX: FilterNode");
+ }
+ virtual void SetAttribute(uint32_t aIndex, const Float* aFloat,
+ uint32_t aSize) {
+ MOZ_CRASH("GFX: FilterNode");
+ }
+
+ /** Maps a rectangle in filter space back to a rectangle in the space of
+ * aSourceNode's first input. aSourceNode should not have an input
+ * assigned when calling this function. */
+ virtual IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax,
+ FilterNode* aSourceNode) {
+ return aMax;
+ }
+
+ protected:
+ friend class Factory;
+
+ FilterNode() = default;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/FontVariation.h b/gfx/2d/FontVariation.h
new file mode 100644
index 0000000000..04a880d4fb
--- /dev/null
+++ b/gfx/2d/FontVariation.h
@@ -0,0 +1,28 @@
+/* -*- 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_FONTVARIATION_H_
+#define MOZILLA_GFX_FONTVARIATION_H_
+
+#include <stdint.h>
+#include "mozilla/FloatingPoint.h"
+
+namespace mozilla::gfx {
+
+// An OpenType variation tag and value pair
+struct FontVariation {
+ uint32_t mTag;
+ float mValue;
+
+ bool operator==(const FontVariation& aOther) const {
+ return mTag == aOther.mTag &&
+ NumbersAreBitwiseIdentical(mValue, aOther.mValue);
+ }
+};
+
+} // namespace mozilla::gfx
+
+#endif /* MOZILLA_GFX_FONTVARIATION_H_ */
diff --git a/gfx/2d/GenericRefCounted.h b/gfx/2d/GenericRefCounted.h
new file mode 100644
index 0000000000..4462c94230
--- /dev/null
+++ b/gfx/2d/GenericRefCounted.h
@@ -0,0 +1,119 @@
+/* -*- 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/. */
+
+// 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 <type_traits>
+
+#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() = default;
+
+ 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);
+ MozRefCountType cnt = ++refCnt;
+ detail::RefCountLogger::logAddRef(this, cnt);
+ }
+
+ virtual void Release() override {
+ // Note: this method must be thread safe for GenericAtomicRefCounted.
+ MOZ_ASSERT(int32_t(refCnt) > 0);
+ detail::RefCountLogger::ReleaseLogger logger{this};
+ MozRefCountType cnt = --refCnt;
+ // Note: it's not safe to touch |this| after decrementing the refcount,
+ // except for below.
+ logger.logRelease(cnt);
+ 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:
+ std::conditional_t<Atomicity == AtomicRefCount, Atomic<MozRefCountType>,
+ MozRefCountType>
+ 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
diff --git a/gfx/2d/GradientStopsD2D.h b/gfx/2d/GradientStopsD2D.h
new file mode 100644
index 0000000000..20e60eb1d0
--- /dev/null
+++ b/gfx/2d/GradientStopsD2D.h
@@ -0,0 +1,42 @@
+/* -*- 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_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, override)
+
+ GradientStopsD2D(ID2D1GradientStopCollection* aStopCollection,
+ ID3D11Device* aDevice)
+ : mStopCollection(aStopCollection), mDevice(aDevice) {}
+
+ virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; }
+
+ bool IsValid() const final {
+ return mDevice == Factory::GetDirect3D11Device();
+ }
+
+ private:
+ friend class DrawTargetD2D;
+ friend class DrawTargetD2D1;
+
+ mutable RefPtr<ID2D1GradientStopCollection> mStopCollection;
+ RefPtr<ID3D11Device> mDevice;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_GRADIENTSTOPSD2D_H_ */
diff --git a/gfx/2d/Helpers.h b/gfx/2d/Helpers.h
new file mode 100644
index 0000000000..349e99973a
--- /dev/null
+++ b/gfx/2d/Helpers.h
@@ -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_GFX_2D_HELPERS_H_
+#define MOZILLA_GFX_2D_HELPERS_H_
+
+#include "2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class AutoRestoreTransform final {
+ public:
+ AutoRestoreTransform() = default;
+
+ 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 final {
+ 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_
diff --git a/gfx/2d/HelpersCairo.h b/gfx/2d/HelpersCairo.h
new file mode 100644
index 0000000000..e3c707944b
--- /dev/null
+++ b/gfx/2d/HelpersCairo.h
@@ -0,0 +1,333 @@
+/* -*- 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_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_CLEAR:
+ return CAIRO_OPERATOR_CLEAR;
+ 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_format_t CairoContentToCairoFormat(
+ cairo_content_t content) {
+ switch (content) {
+ case CAIRO_CONTENT_COLOR:
+ return CAIRO_FORMAT_RGB24;
+ case CAIRO_CONTENT_ALPHA:
+ return CAIRO_FORMAT_A8;
+ case CAIRO_CONTENT_COLOR_ALPHA:
+ return CAIRO_FORMAT_ARGB32;
+ default:
+ gfxCriticalError() << "Unknown cairo content type " << (int)content;
+ return CAIRO_FORMAT_A8; // least likely to cause OOB reads
+ }
+}
+
+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_ */
diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h
new file mode 100644
index 0000000000..440202cec4
--- /dev/null
+++ b/gfx/2d/HelpersD2D.h
@@ -0,0 +1,983 @@
+/* -*- 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_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 "ImageScaling.h"
+
+#include "ScaledFontDWrite.h"
+
+#undef min
+#undef max
+
+namespace mozilla {
+namespace gfx {
+
+RefPtr<ID2D1Factory1> D2DFactory();
+
+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_ROUNDED_RECT D2DRoundedRect(const RoundedRect& aRect) {
+ return D2D1::RoundedRect(D2DRect(aRect.rect),
+ aRect.corners.BottomLeft().width,
+ aRect.corners.BottomLeft().height);
+}
+
+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 DeviceColor& 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 DXGI_FORMAT& aFormat) {
+ switch (aFormat) {
+ case DXGI_FORMAT_A8_UNORM:
+ case DXGI_FORMAT_R8_UNORM:
+ return SurfaceFormat::A8;
+ default:
+ return SurfaceFormat::B8G8R8A8;
+ }
+}
+
+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:
+ case CompositionOp::OP_CLEAR:
+ 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;
+ case CompositionOp::OP_CLEAR:
+ return D2D1_COMPOSITE_MODE_DESTINATION_OUT;
+ 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, CompositionOp aOp = CompositionOp::OP_OVER) {
+ if (aOp == CompositionOp::OP_CLEAR) {
+ return true;
+ }
+
+ if (aPattern.GetType() == PatternType::CONIC_GRADIENT) {
+ return false;
+ }
+
+ 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.value * diff.x.value + diff.y.value * diff.y.value) >=
+ 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
+ //
+ // clang-format off
+ // region we will paint from
+ // uploadRect
+ // .---------------. .---------------. resulting uploadRect
+ // | |rect | |
+ // | .---------. .----. .----. .---------------.
+ // | | | ----> | | | | ----> | |
+ // | '---------' '----' '----' '---------------'
+ // '---------------' '---------------'
+ // clang-format on
+ //
+ //
+
+ int Bpp = BytesPerPixel(aSurface->GetFormat());
+
+ 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.MoveToX(rect.X());
+ uploadRect.SetWidth(rect.Width());
+ } else if (rect.Y() >= 0 && rect.YMost() < size.height) {
+ uploadRect.MoveToY(rect.Y());
+ uploadRect.SetHeight(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()) * Bpp +
+ 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 {
+ 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:
+ explicit 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;
+};
+
+class MOZ_STACK_CLASS AutoRestoreFP final {
+ 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:
+ explicit 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;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_HELPERSD2D_H_ */
diff --git a/gfx/2d/HelpersSkia.h b/gfx/2d/HelpersSkia.h
new file mode 100644
index 0000000000..7f7bf6fbc1
--- /dev/null
+++ b/gfx/2d/HelpersSkia.h
@@ -0,0 +1,360 @@
+/* -*- 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_HELPERSSKIA_H_
+#define MOZILLA_GFX_HELPERSSKIA_H_
+
+#include "2D.h"
+#include "skia/include/core/SkCanvas.h"
+#include "skia/include/core/SkPathEffect.h"
+#include "skia/include/core/SkPathTypes.h"
+#include "skia/include/core/SkShader.h"
+#include "skia/include/effects/SkDashPathEffect.h"
+#include "mozilla/Assertions.h"
+#include <cmath>
+#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;
+ case SurfaceFormat::R8G8B8A8:
+ return kRGBA_8888_SkColorType;
+ case SurfaceFormat::A8R8G8B8:
+ MOZ_DIAGNOSTIC_ASSERT(false, "A8R8G8B8 unsupported by Skia");
+ return kRGBA_8888_SkColorType;
+ default:
+ MOZ_DIAGNOSTIC_ASSERT(false, "Unknown surface format");
+ 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));
+}
+
+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,
+ bool aUsePathEffects = true) {
+ // 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 || !std::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 && aUsePathEffects) {
+ // 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]);
+ }
+
+ auto 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_CLEAR:
+ return SkBlendMode::kClear;
+ 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;
+ case CompositionOp::OP_COUNT:
+ break;
+ }
+
+ 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 DeviceColor& 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 IntRect SkIRectToIntRect(const SkIRect& aRect) {
+ return IntRect(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 SkTileMode ExtendModeToTileMode(ExtendMode aMode, Axis aAxis) {
+ switch (aMode) {
+ case ExtendMode::CLAMP:
+ return SkTileMode::kClamp;
+ case ExtendMode::REPEAT:
+ return SkTileMode::kRepeat;
+ case ExtendMode::REFLECT:
+ return SkTileMode::kMirror;
+ case ExtendMode::REPEAT_X: {
+ return aAxis == Axis::X_AXIS ? SkTileMode::kRepeat : SkTileMode::kClamp;
+ }
+ case ExtendMode::REPEAT_Y: {
+ return aAxis == Axis::Y_AXIS ? SkTileMode::kRepeat : SkTileMode::kClamp;
+ }
+ }
+ return SkTileMode::kClamp;
+}
+
+static inline SkFontHinting GfxHintingToSkiaHinting(FontHinting aHinting) {
+ switch (aHinting) {
+ case FontHinting::NONE:
+ return SkFontHinting::kNone;
+ case FontHinting::LIGHT:
+ return SkFontHinting::kSlight;
+ case FontHinting::NORMAL:
+ return SkFontHinting::kNormal;
+ case FontHinting::FULL:
+ return SkFontHinting::kFull;
+ }
+ return SkFontHinting::kNormal;
+}
+
+static inline FillRule GetFillRule(SkPathFillType aFillType) {
+ switch (aFillType) {
+ case SkPathFillType::kWinding:
+ return FillRule::FILL_WINDING;
+ case SkPathFillType::kEvenOdd:
+ return FillRule::FILL_EVEN_ODD;
+ case SkPathFillType::kInverseWinding:
+ case SkPathFillType::kInverseEvenOdd:
+ 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;
+}
+
+/**
+ * Computes appropriate resolution scale to be used with SkPath::getFillPath
+ * based on the scaling of the supplied transform.
+ */
+float ComputeResScaleForStroking(const Matrix& aTransform);
+
+/**
+ * This is a wrapper around SkGeometry's SkConic that can be used to convert
+ * conic sections in an SkPath to a sequence of quadratic curves. The quads
+ * vector is organized such that for the Nth quad, it's control points are
+ * 2*N, 2*N+1, 2*N+2. This function returns the resulting number of quads.
+ */
+int ConvertConicToQuads(const Point& aP0, const Point& aP1, const Point& aP2,
+ float aWeight, std::vector<Point>& aQuads);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_HELPERSSKIA_H_ */
diff --git a/gfx/2d/HelpersWinFonts.h b/gfx/2d/HelpersWinFonts.h
new file mode 100644
index 0000000000..9bfa69c9b7
--- /dev/null
+++ b/gfx/2d/HelpersWinFonts.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+namespace mozilla {
+namespace gfx {
+
+extern BYTE sSystemTextQuality;
+
+static BYTE GetSystemTextQuality() { return sSystemTextQuality; }
+
+static AntialiasMode GetSystemDefaultAAMode() {
+ AntialiasMode defaultMode = AntialiasMode::SUBPIXEL;
+
+ 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
diff --git a/gfx/2d/ImageScaling.cpp b/gfx/2d/ImageScaling.cpp
new file mode 100644
index 0000000000..25b1e88283
--- /dev/null
+++ b/gfx/2d/ImageScaling.cpp
@@ -0,0 +1,245 @@
+/* -*- 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 "ImageScaling.h"
+#include "2D.h"
+#include "DataSurfaceHelpers.h"
+
+#include <math.h>
+#include <algorithm>
+
+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 = std::max(scaleSize.width, mOrigSize.width / 2);
+ internalSurfSize.height = std::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
diff --git a/gfx/2d/ImageScaling.h b/gfx/2d/ImageScaling.h
new file mode 100644
index 0000000000..446b5287c6
--- /dev/null
+++ b/gfx/2d/ImageScaling.h
@@ -0,0 +1,84 @@
+/* -*- 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_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),
+ mData(nullptr),
+ mStride(0) {}
+
+ ~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
diff --git a/gfx/2d/ImageScalingSSE2.cpp b/gfx/2d/ImageScalingSSE2.cpp
new file mode 100644
index 0000000000..f901641eaf
--- /dev/null
+++ b/gfx/2d/ImageScalingSSE2.cpp
@@ -0,0 +1,333 @@
+/* -*- 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 "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::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 mozilla::gfx
diff --git a/gfx/2d/InlineTranslator.cpp b/gfx/2d/InlineTranslator.cpp
new file mode 100644
index 0000000000..8b264c2146
--- /dev/null
+++ b/gfx/2d/InlineTranslator.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "InlineTranslator.h"
+#include "RecordedEventImpl.h"
+
+#include "mozilla/gfx/RecordingTypes.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla::gfx {
+
+InlineTranslator::InlineTranslator() : mFontContext(nullptr) {}
+
+InlineTranslator::InlineTranslator(DrawTarget* aDT, void* aFontContext)
+ : mBaseDT(aDT), mFontContext(aFontContext) {}
+
+bool InlineTranslator::TranslateRecording(char* aData, size_t aLen) {
+ // an istream like class for reading from memory
+ struct MemReader {
+ MemReader(char* aData, size_t aLen) : mData(aData), mEnd(aData + aLen) {}
+ void read(char* s, std::streamsize n) {
+ if (n <= (mEnd - mData)) {
+ memcpy(s, mData, n);
+ mData += n;
+ } else {
+ // We've requested more data than is available
+ // set the Reader into an eof state
+ SetIsBad();
+ }
+ }
+ bool eof() { return mData > mEnd; }
+ bool good() { return !eof(); }
+ void SetIsBad() { mData = mEnd + 1; }
+
+ char* mData;
+ char* mEnd;
+ };
+ MemReader reader(aData, aLen);
+
+ uint32_t magicInt;
+ ReadElement(reader, magicInt);
+ if (magicInt != mozilla::gfx::kMagicInt) {
+ mError = "Magic";
+ return false;
+ }
+
+ uint16_t majorRevision;
+ ReadElement(reader, majorRevision);
+ if (majorRevision != kMajorRevision) {
+ mError = "Major";
+ return false;
+ }
+
+ uint16_t minorRevision;
+ ReadElement(reader, minorRevision);
+ if (minorRevision > kMinorRevision) {
+ mError = "Minor";
+ return false;
+ }
+
+ int32_t eventType;
+ ReadElement(reader, eventType);
+ while (reader.good()) {
+ bool success = RecordedEvent::DoWithEvent(
+ reader, static_cast<RecordedEvent::EventType>(eventType),
+ [&](RecordedEvent* recordedEvent) -> bool {
+ // Make sure that the whole event was read from the stream
+ // successfully.
+ if (!reader.good()) {
+ mError = " READ";
+ return false;
+ }
+
+ if (!recordedEvent->PlayEvent(this)) {
+ mError = " PLAY";
+ return false;
+ }
+
+ return true;
+ });
+ if (!success) {
+ mError = RecordedEvent::GetEventName(
+ static_cast<RecordedEvent::EventType>(eventType)) +
+ mError;
+ return false;
+ }
+
+ ReadElement(reader, eventType);
+ }
+
+ return true;
+}
+
+already_AddRefed<DrawTarget> InlineTranslator::CreateDrawTarget(
+ ReferencePtr aRefPtr, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) {
+ MOZ_ASSERT(mBaseDT, "mBaseDT has not been initialized.");
+
+ RefPtr<DrawTarget> drawTarget = mBaseDT;
+ AddDrawTarget(aRefPtr, drawTarget);
+ return drawTarget.forget();
+}
+
+already_AddRefed<SourceSurface> InlineTranslator::LookupExternalSurface(
+ uint64_t aKey) {
+ if (!mExternalSurfaces) {
+ return nullptr;
+ }
+ RefPtr<SourceSurface> surface = mExternalSurfaces->Get(aKey);
+ return surface.forget();
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/InlineTranslator.h b/gfx/2d/InlineTranslator.h
new file mode 100644
index 0000000000..5c35bfacbb
--- /dev/null
+++ b/gfx/2d/InlineTranslator.h
@@ -0,0 +1,191 @@
+/* -*- 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_layout_InlineTranslator_h
+#define mozilla_layout_InlineTranslator_h
+
+#include <string>
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Filters.h"
+#include "mozilla/gfx/RecordedEvent.h"
+
+namespace mozilla {
+namespace gfx {
+
+using gfx::DrawTarget;
+using gfx::FilterNode;
+using gfx::GradientStops;
+using gfx::NativeFontResource;
+using gfx::Path;
+using gfx::ReferencePtr;
+using gfx::ScaledFont;
+using gfx::SourceSurface;
+using gfx::Translator;
+
+class InlineTranslator : public Translator {
+ public:
+ InlineTranslator();
+
+ explicit InlineTranslator(DrawTarget* aDT, void* aFontContext = nullptr);
+
+ bool TranslateRecording(char*, size_t len);
+
+ void SetExternalSurfaces(
+ nsRefPtrHashtable<nsUint64HashKey, SourceSurface>* aExternalSurfaces) {
+ mExternalSurfaces = aExternalSurfaces;
+ }
+ void SetReferenceDrawTargetTransform(const Matrix& aTransform) {
+ mBaseDTTransform = aTransform;
+ }
+
+ DrawTarget* LookupDrawTarget(ReferencePtr aRefPtr) final {
+ DrawTarget* result = mDrawTargets.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ Path* LookupPath(ReferencePtr aRefPtr) final {
+ Path* result = mPaths.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ SourceSurface* LookupSourceSurface(ReferencePtr aRefPtr) final {
+ SourceSurface* result = mSourceSurfaces.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ FilterNode* LookupFilterNode(ReferencePtr aRefPtr) final {
+ FilterNode* result = mFilterNodes.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ already_AddRefed<GradientStops> LookupGradientStops(
+ ReferencePtr aRefPtr) final {
+ return mGradientStops.Get(aRefPtr);
+ }
+
+ ScaledFont* LookupScaledFont(ReferencePtr aRefPtr) final {
+ ScaledFont* result = mScaledFonts.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) final {
+ UnscaledFont* result = mUnscaledFonts.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ NativeFontResource* LookupNativeFontResource(uint64_t aKey) final {
+ NativeFontResource* result = mNativeFontResources.GetWeak(aKey);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ already_AddRefed<SourceSurface> LookupExternalSurface(uint64_t aKey) override;
+
+ void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget* aDT) final {
+ mDrawTargets.InsertOrUpdate(aRefPtr, RefPtr{aDT});
+ }
+
+ void AddPath(ReferencePtr aRefPtr, Path* aPath) final {
+ mPaths.InsertOrUpdate(aRefPtr, RefPtr{aPath});
+ }
+
+ void AddSourceSurface(ReferencePtr aRefPtr,
+ SourceSurface* aSurface) override {
+ mSourceSurfaces.InsertOrUpdate(aRefPtr, RefPtr{aSurface});
+ }
+
+ void AddFilterNode(ReferencePtr aRefPtr, FilterNode* aFilter) final {
+ mFilterNodes.InsertOrUpdate(aRefPtr, RefPtr{aFilter});
+ }
+
+ void AddGradientStops(ReferencePtr aRefPtr, GradientStops* aStops) final {
+ mGradientStops.InsertOrUpdate(aRefPtr, RefPtr{aStops});
+ }
+
+ void AddScaledFont(ReferencePtr aRefPtr, ScaledFont* aScaledFont) final {
+ mScaledFonts.InsertOrUpdate(aRefPtr, RefPtr{aScaledFont});
+ }
+
+ void AddUnscaledFont(ReferencePtr aRefPtr,
+ UnscaledFont* aUnscaledFont) final {
+ mUnscaledFonts.InsertOrUpdate(aRefPtr, RefPtr{aUnscaledFont});
+ }
+
+ void AddNativeFontResource(uint64_t aKey,
+ NativeFontResource* aScaledFontResouce) final {
+ mNativeFontResources.InsertOrUpdate(aKey, RefPtr{aScaledFontResouce});
+ }
+
+ void RemoveDrawTarget(ReferencePtr aRefPtr) override {
+ mDrawTargets.Remove(aRefPtr);
+ }
+
+ void RemovePath(ReferencePtr aRefPtr) final { mPaths.Remove(aRefPtr); }
+
+ void RemoveSourceSurface(ReferencePtr aRefPtr) override {
+ mSourceSurfaces.Remove(aRefPtr);
+ }
+
+ void RemoveFilterNode(ReferencePtr aRefPtr) final {
+ mFilterNodes.Remove(aRefPtr);
+ }
+
+ void RemoveGradientStops(ReferencePtr aRefPtr) final {
+ mGradientStops.Remove(aRefPtr);
+ }
+
+ void RemoveScaledFont(ReferencePtr aRefPtr) final {
+ mScaledFonts.Remove(aRefPtr);
+ }
+
+ void RemoveUnscaledFont(ReferencePtr aRefPtr) final {
+ mUnscaledFonts.Remove(aRefPtr);
+ }
+
+ already_AddRefed<DrawTarget> CreateDrawTarget(
+ ReferencePtr aRefPtr, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) override;
+
+ mozilla::gfx::DrawTarget* GetReferenceDrawTarget() final {
+ MOZ_ASSERT(mBaseDT, "mBaseDT has not been initialized.");
+ return mBaseDT;
+ }
+ Matrix GetReferenceDrawTargetTransform() final { return mBaseDTTransform; }
+
+ void* GetFontContext() final { return mFontContext; }
+ std::string GetError() { return mError; }
+
+ protected:
+ RefPtr<DrawTarget> mBaseDT;
+ Matrix mBaseDTTransform;
+ nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets;
+
+ private:
+ void* mFontContext;
+ std::string mError;
+
+ nsRefPtrHashtable<nsPtrHashKey<void>, Path> mPaths;
+ nsRefPtrHashtable<nsPtrHashKey<void>, SourceSurface> mSourceSurfaces;
+ nsRefPtrHashtable<nsPtrHashKey<void>, FilterNode> mFilterNodes;
+ nsRefPtrHashtable<nsPtrHashKey<void>, GradientStops> mGradientStops;
+ nsRefPtrHashtable<nsPtrHashKey<void>, ScaledFont> mScaledFonts;
+ nsRefPtrHashtable<nsPtrHashKey<void>, UnscaledFont> mUnscaledFonts;
+ nsRefPtrHashtable<nsUint64HashKey, NativeFontResource> mNativeFontResources;
+ nsRefPtrHashtable<nsUint64HashKey, SourceSurface>* mExternalSurfaces =
+ nullptr;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_layout_InlineTranslator_h
diff --git a/gfx/2d/IterableArena.h b/gfx/2d/IterableArena.h
new file mode 100644
index 0000000000..6f90e7d603
--- /dev/null
+++ b/gfx/2d/IterableArena.h
@@ -0,0 +1,175 @@
+/* -*- 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_ITERABLEARENA_H_
+#define MOZILLA_GFX_ITERABLEARENA_H_
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <utility>
+#include <vector>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/gfx/Logging.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(std::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 gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h
new file mode 100644
index 0000000000..5aa0a954dc
--- /dev/null
+++ b/gfx/2d/Logging.h
@@ -0,0 +1,965 @@
+/* -*- 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_LOGGING_H_
+#define MOZILLA_GFX_LOGGING_H_
+
+#include <string>
+#include <sstream>
+#include <stdio.h>
+#include <vector>
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+#endif
+
+#if defined(MOZ_WIDGET_ANDROID)
+# include "nsDebug.h"
+#endif
+#include "2D.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#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
+
+/// 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,
+ InvalidDrawTarget,
+ NativeFontResourceNotFound,
+ UnscaledFontNotFound,
+ ScaledFontNotFound,
+ InvalidLayerType, // 40
+ // 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 (StaticPrefs::gfx_logging_level() >= 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 ((StaticPrefs::gfx_logging_level() >= 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.
+ //
+ // If making any logic changes to this method, you should probably
+ // make the corresponding change in the ShouldOutputMessage method
+ // above.
+ if (StaticPrefs::gfx_logging_level() >= 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))) {
+ MOZ_LOG(GetGFX2DLog(), PRLogLevelForLevel(aLevel),
+ ("%s%s", aString.c_str(), aNoNewline ? "" : "\n"));
+ } else
+# endif
+ if ((StaticPrefs::gfx_logging_level() >= 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 std::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() = default;
+ 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() = default;
+ ~NoLog() = default;
+
+ // No-op
+ MOZ_IMPLICIT NoLog(const NoLog&) = default;
+
+ 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);
+}
+
+#ifdef WIN32
+void LogWStr(const wchar_t* aStr, std::stringstream& aOut);
+#endif
+
+template <int L, typename Logger = BasicLogger>
+class Log final {
+ 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;
+ }
+#ifdef WIN32
+ Log& operator<<(const wchar_t aWStr[]) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ LogWStr(aWStr, mMessage);
+ }
+ return *this;
+ }
+#endif
+ 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;
+ }
+ Log& operator<<(const sRGBColor& aColor) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "sRGBColor(" << aColor.r << ", " << aColor.g << ", "
+ << aColor.b << ", " << aColor.a << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const DeviceColor& aColor) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "DeviceColor(" << aColor.r << ", " << aColor.g << ", "
+ << aColor.b << ", " << aColor.a << ")";
+ }
+ 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<<(const SourceSurface* aSurface) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "SourceSurface(" << (void*)(aSurface) << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const Path* aPath) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "Path(" << (void*)(aPath) << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const Pattern* aPattern) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "Pattern(" << (void*)(aPattern) << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const ScaledFont* aFont) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "ScaledFont(" << (void*)(aFont) << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const FilterNode* aFilter) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "FilterNode(" << (void*)(aFilter) << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const DrawOptions& aOptions) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "DrawOptions(" << aOptions.mAlpha << ", ";
+ (*this) << aOptions.mCompositionOp;
+ mMessage << ", ";
+ (*this) << aOptions.mAntialiasMode;
+ mMessage << ")";
+ }
+ return *this;
+ }
+ Log& operator<<(const DrawSurfaceOptions& aOptions) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ mMessage << "DrawSurfaceOptions(";
+ (*this) << aOptions.mSamplingFilter;
+ mMessage << ", ";
+ (*this) << aOptions.mSamplingBounds;
+ mMessage << ")";
+ }
+ return *this;
+ }
+
+ Log& operator<<(SamplingBounds aBounds) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ switch (aBounds) {
+ case SamplingBounds::UNBOUNDED:
+ mMessage << "SamplingBounds::UNBOUNDED";
+ break;
+ case SamplingBounds::BOUNDED:
+ mMessage << "SamplingBounds::BOUNDED";
+ break;
+ default:
+ mMessage << "Invalid SamplingBounds (" << (int)aBounds << ")";
+ break;
+ }
+ }
+ return *this;
+ }
+ Log& operator<<(SamplingFilter aFilter) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ switch (aFilter) {
+ case SamplingFilter::GOOD:
+ mMessage << "SamplingFilter::GOOD";
+ break;
+ case SamplingFilter::LINEAR:
+ mMessage << "SamplingFilter::LINEAR";
+ break;
+ case SamplingFilter::POINT:
+ mMessage << "SamplingFilter::POINT";
+ break;
+ default:
+ mMessage << "Invalid SamplingFilter (" << (int)aFilter << ")";
+ break;
+ }
+ }
+ return *this;
+ }
+ Log& operator<<(AntialiasMode aMode) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ switch (aMode) {
+ case AntialiasMode::NONE:
+ mMessage << "AntialiasMode::NONE";
+ break;
+ case AntialiasMode::GRAY:
+ mMessage << "AntialiasMode::GRAY";
+ break;
+ case AntialiasMode::SUBPIXEL:
+ mMessage << "AntialiasMode::SUBPIXEL";
+ break;
+ case AntialiasMode::DEFAULT:
+ mMessage << "AntialiasMode::DEFAULT";
+ break;
+ default:
+ mMessage << "Invalid AntialiasMode (" << (int)aMode << ")";
+ break;
+ }
+ }
+ return *this;
+ }
+ Log& operator<<(CompositionOp aOp) {
+ if (MOZ_UNLIKELY(LogIt())) {
+ switch (aOp) {
+ case CompositionOp::OP_CLEAR:
+ mMessage << "CompositionOp::OP_CLEAR";
+ break;
+ case CompositionOp::OP_OVER:
+ mMessage << "CompositionOp::OP_OVER";
+ break;
+ case CompositionOp::OP_ADD:
+ mMessage << "CompositionOp::OP_ADD";
+ break;
+ case CompositionOp::OP_ATOP:
+ mMessage << "CompositionOp::OP_ATOP";
+ break;
+ case CompositionOp::OP_OUT:
+ mMessage << "CompositionOp::OP_OUT";
+ break;
+ case CompositionOp::OP_IN:
+ mMessage << "CompositionOp::OP_IN";
+ break;
+ case CompositionOp::OP_SOURCE:
+ mMessage << "CompositionOp::OP_SOURCE";
+ break;
+ case CompositionOp::OP_DEST_IN:
+ mMessage << "CompositionOp::OP_DEST_IN";
+ break;
+ case CompositionOp::OP_DEST_OUT:
+ mMessage << "CompositionOp::OP_DEST_OUT";
+ break;
+ case CompositionOp::OP_DEST_OVER:
+ mMessage << "CompositionOp::OP_DEST_OVER";
+ break;
+ case CompositionOp::OP_DEST_ATOP:
+ mMessage << "CompositionOp::OP_DEST_ATOP";
+ break;
+ case CompositionOp::OP_XOR:
+ mMessage << "CompositionOp::OP_XOR";
+ break;
+ case CompositionOp::OP_MULTIPLY:
+ mMessage << "CompositionOp::OP_MULTIPLY";
+ break;
+ case CompositionOp::OP_SCREEN:
+ mMessage << "CompositionOp::OP_SCREEN";
+ break;
+ case CompositionOp::OP_OVERLAY:
+ mMessage << "CompositionOp::OP_OVERLAY";
+ break;
+ case CompositionOp::OP_DARKEN:
+ mMessage << "CompositionOp::OP_DARKEN";
+ break;
+ case CompositionOp::OP_LIGHTEN:
+ mMessage << "CompositionOp::OP_LIGHTEN";
+ break;
+ case CompositionOp::OP_COLOR_DODGE:
+ mMessage << "CompositionOp::OP_COLOR_DODGE";
+ break;
+ case CompositionOp::OP_COLOR_BURN:
+ mMessage << "CompositionOp::OP_COLOR_BURN";
+ break;
+ case CompositionOp::OP_HARD_LIGHT:
+ mMessage << "CompositionOp::OP_HARD_LIGHT";
+ break;
+ case CompositionOp::OP_SOFT_LIGHT:
+ mMessage << "CompositionOp::OP_SOFT_LIGHT";
+ break;
+ case CompositionOp::OP_DIFFERENCE:
+ mMessage << "CompositionOp::OP_DIFFERENCE";
+ break;
+ case CompositionOp::OP_EXCLUSION:
+ mMessage << "CompositionOp::OP_EXCLUSION";
+ break;
+ case CompositionOp::OP_HUE:
+ mMessage << "CompositionOp::OP_HUE";
+ break;
+ case CompositionOp::OP_SATURATION:
+ mMessage << "CompositionOp::OP_SATURATION";
+ break;
+ case CompositionOp::OP_COLOR:
+ mMessage << "CompositionOp::OP_COLOR";
+ break;
+ case CompositionOp::OP_LUMINOSITY:
+ mMessage << "CompositionOp::OP_LUMINOSITY";
+ break;
+ case CompositionOp::OP_COUNT:
+ mMessage << "CompositionOp::OP_COUNT";
+ break;
+ default:
+ mMessage << "Invalid CompositionOp (" << (int)aOp << ")";
+ break;
+ }
+ }
+ 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::D2D1_1_IMAGE:
+ mMessage << "SurfaceType::D2D1_1_IMAGE";
+ break;
+ case SurfaceType::RECORDING:
+ mMessage << "SurfaceType::RECORDING";
+ break;
+ case SurfaceType::DATA_SHARED:
+ mMessage << "SurfaceType::DATA_SHARED";
+ break;
+ case SurfaceType::DATA_RECYCLING_SHARED:
+ mMessage << "SurfaceType::DATA_RECYCLING_SHARED";
+ break;
+ case SurfaceType::DATA_ALIGNED:
+ mMessage << "SurfaceType::DATA_ALIGNED";
+ break;
+ case SurfaceType::DATA_SHARED_WRAPPER:
+ mMessage << "SurfaceType::DATA_SHARED_WRAPPER";
+ break;
+ case SurfaceType::DATA_MAPPED:
+ mMessage << "SurfaceType::DATA_MAPPED";
+ break;
+ case SurfaceType::WEBGL:
+ mMessage << "SurfaceType::WEBGL";
+ 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;
+
+template <int Level = LOG_DEBUG>
+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) {
+ if (!mPrefix.empty()) {
+ mLog << '[' << mPrefix << "] ";
+ }
+ mLog << 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<Level> 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));
+ }
+};
+
+template <int Level = LOG_DEBUG>
+class TreeAutoIndent final {
+ public:
+ explicit TreeAutoIndent(TreeLog<Level>& aTreeLog) : mTreeLog(aTreeLog) {
+ mTreeLog.IncreaseIndent();
+ }
+
+ TreeAutoIndent(const TreeAutoIndent& aTreeAutoIndent)
+ : mTreeLog(aTreeAutoIndent.mTreeLog) {
+ mTreeLog.IncreaseIndent();
+ }
+
+ TreeAutoIndent& operator=(const TreeAutoIndent& aTreeAutoIndent) = delete;
+
+ ~TreeAutoIndent() { mTreeLog.DecreaseIndent(); }
+
+ private:
+ TreeLog<Level>& mTreeLog;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_LOGGING_H_ */
diff --git a/gfx/2d/LoggingConstants.h b/gfx/2d/LoggingConstants.h
new file mode 100644
index 0000000000..cede5d3e87
--- /dev/null
+++ b/gfx/2d/LoggingConstants.h
@@ -0,0 +1,31 @@
+/* -*- 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_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_ */
diff --git a/gfx/2d/LuminanceNEON.cpp b/gfx/2d/LuminanceNEON.cpp
new file mode 100644
index 0000000000..cb84336268
--- /dev/null
+++ b/gfx/2d/LuminanceNEON.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 <arm_neon.h>
+#include "LuminanceNEON.h"
+
+using namespace mozilla::gfx;
+
+/**
+ * Byte offsets of channels in a native packed gfxColor or cairo image surface.
+ */
+#ifdef IS_BIG_ENDIAN
+# define GFX_ARGB32_OFFSET_A 0
+# define GFX_ARGB32_OFFSET_R 1
+# define GFX_ARGB32_OFFSET_G 2
+# define GFX_ARGB32_OFFSET_B 3
+#else
+# define GFX_ARGB32_OFFSET_A 3
+# define GFX_ARGB32_OFFSET_R 2
+# define GFX_ARGB32_OFFSET_G 1
+# define GFX_ARGB32_OFFSET_B 0
+#endif
+
+void ComputesRGBLuminanceMask_NEON(const uint8_t* aSourceData,
+ int32_t aSourceStride, uint8_t* aDestData,
+ int32_t aDestStride, const IntSize& aSize,
+ float aOpacity) {
+ int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+ int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+ int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+ const uint8_t* sourcePixel = aSourceData;
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ uint8_t* destPixel = aDestData;
+ int32_t destOffset = aDestStride - aSize.width;
+
+ sourcePixel = aSourceData;
+ int32_t remainderWidth = aSize.width % 8;
+ int32_t roundedWidth = aSize.width - remainderWidth;
+ uint16x8_t temp;
+ uint8x8_t gray;
+ uint8x8_t redVector = vdup_n_u8(redFactor);
+ uint8x8_t greenVector = vdup_n_u8(greenFactor);
+ uint8x8_t blueVector = vdup_n_u8(blueFactor);
+ uint8x8_t fullBitVector = vdup_n_u8(255);
+ uint8x8_t oneVector = vdup_n_u8(1);
+ for (int32_t y = 0; y < aSize.height; y++) {
+ // Calculate luminance by neon with 8 pixels per loop
+ for (int32_t x = 0; x < roundedWidth; x += 8) {
+ uint8x8x4_t argb = vld4_u8(sourcePixel);
+ temp = vmull_u8(argb.val[GFX_ARGB32_OFFSET_R],
+ redVector); // temp = red * redFactor
+ temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_G],
+ greenVector); // temp += green * greenFactor
+ temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_B],
+ blueVector); // temp += blue * blueFactor
+ gray = vshrn_n_u16(temp, 8); // gray = temp >> 8
+
+ // Check alpha value
+ uint8x8_t alphaVector =
+ vtst_u8(argb.val[GFX_ARGB32_OFFSET_A], fullBitVector);
+ gray = vmul_u8(gray, vand_u8(alphaVector, oneVector));
+
+ // Put the result to the 8 pixels
+ vst1_u8(destPixel, gray);
+ sourcePixel += 8 * 4;
+ destPixel += 8;
+ }
+
+ // Calculate the rest pixels of the line by cpu
+ for (int32_t x = 0; x < remainderWidth; x++) {
+ if (sourcePixel[GFX_ARGB32_OFFSET_A] > 0) {
+ *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
+ greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+ blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >>
+ 8;
+ } else {
+ *destPixel = 0;
+ }
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
diff --git a/gfx/2d/LuminanceNEON.h b/gfx/2d/LuminanceNEON.h
new file mode 100644
index 0000000000..1af0d039dd
--- /dev/null
+++ b/gfx/2d/LuminanceNEON.h
@@ -0,0 +1,18 @@
+/* -*- 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 __LUMINANCENEON_H__
+#define __LUMINANCENEON_H__
+
+#include "mozilla/gfx/Point.h"
+
+void ComputesRGBLuminanceMask_NEON(const uint8_t* aSourceData,
+ int32_t aSourceStride, uint8_t* aDestData,
+ int32_t aDestStride,
+ const mozilla::gfx::IntSize& aSize,
+ float aOpacity);
+
+#endif /* __LUMINANCENEON_H__ */
diff --git a/gfx/2d/MMIHelpers.h b/gfx/2d/MMIHelpers.h
new file mode 100644
index 0000000000..3b0a287230
--- /dev/null
+++ b/gfx/2d/MMIHelpers.h
@@ -0,0 +1,232 @@
+/*
+ ============================================================================
+ 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__ */
diff --git a/gfx/2d/MacIOSurface.cpp b/gfx/2d/MacIOSurface.cpp
new file mode 100644
index 0000000000..a90d7dfd50
--- /dev/null
+++ b/gfx/2d/MacIOSurface.cpp
@@ -0,0 +1,621 @@
+/* -*- 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 "MacIOSurface.h"
+#include <OpenGL/gl.h>
+#include <OpenGL/CGLIOSurface.h>
+#include <QuartzCore/QuartzCore.h>
+#include "GLConsts.h"
+#include "GLContextCGL.h"
+#include "gfxMacUtils.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/StaticPrefs_gfx.h"
+
+using namespace mozilla;
+
+MacIOSurface::MacIOSurface(CFTypeRefPtr<IOSurfaceRef> aIOSurfaceRef,
+ bool aHasAlpha, gfx::YUVColorSpace aColorSpace)
+ : mIOSurfaceRef(std::move(aIOSurfaceRef)),
+ mHasAlpha(aHasAlpha),
+ mColorSpace(aColorSpace) {
+ IncrementUseCount();
+}
+
+MacIOSurface::~MacIOSurface() {
+ MOZ_RELEASE_ASSERT(!IsLocked(), "Destroying locked surface");
+ DecrementUseCount();
+}
+
+void AddDictionaryInt(const CFTypeRefPtr<CFMutableDictionaryRef>& aDict,
+ const void* aType, uint32_t aValue) {
+ auto cfValue = CFTypeRefPtr<CFNumberRef>::WrapUnderCreateRule(
+ ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aValue));
+ ::CFDictionaryAddValue(aDict.get(), aType, cfValue.get());
+}
+
+void SetSizeProperties(const CFTypeRefPtr<CFMutableDictionaryRef>& aDict,
+ int aWidth, int aHeight, int aBytesPerPixel) {
+ AddDictionaryInt(aDict, kIOSurfaceWidth, aWidth);
+ AddDictionaryInt(aDict, kIOSurfaceHeight, aHeight);
+ ::CFDictionaryAddValue(aDict.get(), kIOSurfaceIsGlobal, kCFBooleanTrue);
+ AddDictionaryInt(aDict, kIOSurfaceBytesPerElement, aBytesPerPixel);
+
+ size_t bytesPerRow =
+ IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, aWidth * aBytesPerPixel);
+ AddDictionaryInt(aDict, kIOSurfaceBytesPerRow, bytesPerRow);
+
+ // Add a SIMD register worth of extra bytes to the end of the allocation for
+ // SWGL.
+ size_t totalBytes =
+ IOSurfaceAlignProperty(kIOSurfaceAllocSize, aHeight * bytesPerRow + 16);
+ AddDictionaryInt(aDict, kIOSurfaceAllocSize, totalBytes);
+}
+
+/* static */
+already_AddRefed<MacIOSurface> MacIOSurface::CreateIOSurface(int aWidth,
+ int aHeight,
+ bool aHasAlpha) {
+ auto props = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule(
+ ::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;
+ SetSizeProperties(props, aWidth, aHeight, bytesPerElem);
+
+ AddDictionaryInt(props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_32BGRA);
+
+ CFTypeRefPtr<IOSurfaceRef> surfaceRef =
+ CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
+ ::IOSurfaceCreate(props.get()));
+
+ if (StaticPrefs::gfx_color_management_native_srgb()) {
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorSpace"),
+ kCGColorSpaceSRGB);
+ }
+
+ if (!surfaceRef) {
+ return nullptr;
+ }
+
+ RefPtr<MacIOSurface> ioSurface =
+ new MacIOSurface(std::move(surfaceRef), aHasAlpha);
+
+ return ioSurface.forget();
+}
+
+size_t CreatePlaneDictionary(CFTypeRefPtr<CFMutableDictionaryRef>& aDict,
+ const gfx::IntSize& aSize, size_t aOffset,
+ size_t aBytesPerPixel) {
+ size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfacePlaneBytesPerRow,
+ aSize.width * aBytesPerPixel);
+ // Add a SIMD register worth of extra bytes to the end of the allocation for
+ // SWGL.
+ size_t totalBytes = IOSurfaceAlignProperty(kIOSurfacePlaneSize,
+ aSize.height * bytesPerRow + 16);
+
+ aDict = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule(
+ ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ AddDictionaryInt(aDict, kIOSurfacePlaneWidth, aSize.width);
+ AddDictionaryInt(aDict, kIOSurfacePlaneHeight, aSize.height);
+ AddDictionaryInt(aDict, kIOSurfacePlaneBytesPerRow, bytesPerRow);
+ AddDictionaryInt(aDict, kIOSurfacePlaneOffset, aOffset);
+ AddDictionaryInt(aDict, kIOSurfacePlaneSize, totalBytes);
+ AddDictionaryInt(aDict, kIOSurfacePlaneBytesPerElement, aBytesPerPixel);
+
+ return totalBytes;
+}
+
+/* static */
+already_AddRefed<MacIOSurface> MacIOSurface::CreateNV12OrP010Surface(
+ const IntSize& aYSize, const IntSize& aCbCrSize, YUVColorSpace aColorSpace,
+ TransferFunction aTransferFunction, ColorRange aColorRange,
+ ColorDepth aColorDepth) {
+ MOZ_ASSERT(aColorSpace == YUVColorSpace::BT601 ||
+ aColorSpace == YUVColorSpace::BT709 ||
+ aColorSpace == YUVColorSpace::BT2020);
+ MOZ_ASSERT(aColorRange == ColorRange::LIMITED ||
+ aColorRange == ColorRange::FULL);
+ MOZ_ASSERT(aColorDepth == ColorDepth::COLOR_8 ||
+ aColorDepth == ColorDepth::COLOR_10);
+
+ auto props = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule(
+ ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ if (!props) return nullptr;
+
+ MOZ_ASSERT((size_t)aYSize.width <= GetMaxWidth());
+ MOZ_ASSERT((size_t)aYSize.height <= GetMaxHeight());
+
+ AddDictionaryInt(props, kIOSurfaceWidth, aYSize.width);
+ AddDictionaryInt(props, kIOSurfaceHeight, aYSize.height);
+ ::CFDictionaryAddValue(props.get(), kIOSurfaceIsGlobal, kCFBooleanTrue);
+
+ if (aColorRange == ColorRange::LIMITED) {
+ if (aColorDepth == ColorDepth::COLOR_8) {
+ AddDictionaryInt(
+ props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
+ } else {
+ AddDictionaryInt(
+ props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange);
+ }
+ } else {
+ if (aColorDepth == ColorDepth::COLOR_8) {
+ AddDictionaryInt(
+ props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_420YpCbCr8BiPlanarFullRange);
+ } else {
+ AddDictionaryInt(
+ props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
+ }
+ }
+
+ size_t bytesPerPixel = (aColorDepth == ColorDepth::COLOR_8) ? 1 : 2;
+
+ CFTypeRefPtr<CFMutableDictionaryRef> planeProps[2];
+ size_t yPlaneBytes =
+ CreatePlaneDictionary(planeProps[0], aYSize, 0, bytesPerPixel);
+ size_t cbCrOffset =
+ IOSurfaceAlignProperty(kIOSurfacePlaneOffset, yPlaneBytes);
+ size_t cbCrPlaneBytes = CreatePlaneDictionary(planeProps[1], aCbCrSize,
+ cbCrOffset, bytesPerPixel * 2);
+ size_t totalBytes =
+ IOSurfaceAlignProperty(kIOSurfaceAllocSize, cbCrOffset + cbCrPlaneBytes);
+
+ AddDictionaryInt(props, kIOSurfaceAllocSize, totalBytes);
+
+ auto array = CFTypeRefPtr<CFArrayRef>::WrapUnderCreateRule(
+ CFArrayCreate(kCFAllocatorDefault, (const void**)planeProps, 2,
+ &kCFTypeArrayCallBacks));
+ ::CFDictionaryAddValue(props.get(), kIOSurfacePlaneInfo, array.get());
+
+ CFTypeRefPtr<IOSurfaceRef> surfaceRef =
+ CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
+ ::IOSurfaceCreate(props.get()));
+
+ if (!surfaceRef) {
+ return nullptr;
+ }
+
+ // Setup the correct YCbCr conversion matrix, color primaries, and transfer
+ // functions on the IOSurface, in case we pass this directly to CoreAnimation.
+ // For keys and values, we'd like to use values specified by the API, but
+ // those are only defined for CVImageBuffers. Luckily, when an image buffer is
+ // converted into an IOSurface, the keys are transformed but the values are
+ // the same. Since we are creating the IOSurface directly, we use hard-coded
+ // keys derived from inspecting the extracted IOSurfaces in the copying case,
+ // but we use the API-defined values from CVImageBuffer.
+ if (aColorSpace == YUVColorSpace::BT601) {
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"),
+ kCVImageBufferYCbCrMatrix_ITU_R_601_4);
+ } else if (aColorSpace == YUVColorSpace::BT709) {
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"),
+ kCVImageBufferYCbCrMatrix_ITU_R_709_2);
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorPrimaries"),
+ kCVImageBufferColorPrimaries_ITU_R_709_2);
+ } else {
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"),
+ kCVImageBufferYCbCrMatrix_ITU_R_2020);
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorPrimaries"),
+ kCVImageBufferColorPrimaries_ITU_R_2020);
+ }
+
+ // Transfer function is applied independently from the colorSpace.
+ IOSurfaceSetValue(
+ surfaceRef.get(), CFSTR("IOSurfaceTransferFunction"),
+ gfxMacUtils::CFStringForTransferFunction(aTransferFunction));
+
+ // Override the color space to be the same as the main display, so that
+ // CoreAnimation won't try to do any color correction (from the IOSurface
+ // space, to the display). In the future we may want to try specifying this
+ // correctly, but probably only once we do the same for videos drawn through
+ // our gfx code.
+ auto colorSpace = CFTypeRefPtr<CGColorSpaceRef>::WrapUnderCreateRule(
+ CGDisplayCopyColorSpace(CGMainDisplayID()));
+ auto colorData = CFTypeRefPtr<CFDataRef>::WrapUnderCreateRule(
+ CGColorSpaceCopyICCProfile(colorSpace.get()));
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorSpace"),
+ colorData.get());
+
+ RefPtr<MacIOSurface> ioSurface =
+ new MacIOSurface(std::move(surfaceRef), false, aColorSpace);
+
+ return ioSurface.forget();
+}
+
+/* static */
+already_AddRefed<MacIOSurface> MacIOSurface::CreateYUV422Surface(
+ const IntSize& aSize, YUVColorSpace aColorSpace, ColorRange aColorRange) {
+ MOZ_ASSERT(aColorSpace == YUVColorSpace::BT601 ||
+ aColorSpace == YUVColorSpace::BT709);
+ MOZ_ASSERT(aColorRange == ColorRange::LIMITED ||
+ aColorRange == ColorRange::FULL);
+
+ auto props = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule(
+ ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ if (!props) return nullptr;
+
+ MOZ_ASSERT((size_t)aSize.width <= GetMaxWidth());
+ MOZ_ASSERT((size_t)aSize.height <= GetMaxHeight());
+
+ SetSizeProperties(props, aSize.width, aSize.height, 2);
+
+ if (aColorRange == ColorRange::LIMITED) {
+ AddDictionaryInt(props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_422YpCbCr8_yuvs);
+ } else {
+ AddDictionaryInt(props, kIOSurfacePixelFormat,
+ (uint32_t)kCVPixelFormatType_422YpCbCr8FullRange);
+ }
+
+ CFTypeRefPtr<IOSurfaceRef> surfaceRef =
+ CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
+ ::IOSurfaceCreate(props.get()));
+
+ if (!surfaceRef) {
+ return nullptr;
+ }
+
+ // Setup the correct YCbCr conversion matrix on the IOSurface, in case we pass
+ // this directly to CoreAnimation.
+ if (aColorSpace == YUVColorSpace::BT601) {
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"),
+ CFSTR("ITU_R_601_4"));
+ } else {
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"),
+ CFSTR("ITU_R_709_2"));
+ }
+ // Override the color space to be the same as the main display, so that
+ // CoreAnimation won't try to do any color correction (from the IOSurface
+ // space, to the display). In the future we may want to try specifying this
+ // correctly, but probably only once we do the same for videos drawn through
+ // our gfx code.
+ auto colorSpace = CFTypeRefPtr<CGColorSpaceRef>::WrapUnderCreateRule(
+ CGDisplayCopyColorSpace(CGMainDisplayID()));
+ auto colorData = CFTypeRefPtr<CFDataRef>::WrapUnderCreateRule(
+ CGColorSpaceCopyICCProfile(colorSpace.get()));
+ IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorSpace"),
+ colorData.get());
+
+ RefPtr<MacIOSurface> ioSurface =
+ new MacIOSurface(std::move(surfaceRef), false, aColorSpace);
+
+ return ioSurface.forget();
+}
+
+/* static */
+already_AddRefed<MacIOSurface> MacIOSurface::LookupSurface(
+ IOSurfaceID aIOSurfaceID, bool aHasAlpha, gfx::YUVColorSpace aColorSpace) {
+ CFTypeRefPtr<IOSurfaceRef> surfaceRef =
+ CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
+ ::IOSurfaceLookup(aIOSurfaceID));
+ if (!surfaceRef) return nullptr;
+
+ RefPtr<MacIOSurface> ioSurface =
+ new MacIOSurface(std::move(surfaceRef), aHasAlpha, aColorSpace);
+
+ return ioSurface.forget();
+}
+
+IOSurfaceID MacIOSurface::GetIOSurfaceID() const {
+ return ::IOSurfaceGetID(mIOSurfaceRef.get());
+}
+
+void* MacIOSurface::GetBaseAddress() const {
+ return ::IOSurfaceGetBaseAddress(mIOSurfaceRef.get());
+}
+
+void* MacIOSurface::GetBaseAddressOfPlane(size_t aPlaneIndex) const {
+ return ::IOSurfaceGetBaseAddressOfPlane(mIOSurfaceRef.get(), aPlaneIndex);
+}
+
+size_t MacIOSurface::GetWidth(size_t plane) const {
+ return GetDevicePixelWidth(plane);
+}
+
+size_t MacIOSurface::GetHeight(size_t plane) const {
+ return GetDevicePixelHeight(plane);
+}
+
+size_t MacIOSurface::GetPlaneCount() const {
+ return ::IOSurfaceGetPlaneCount(mIOSurfaceRef.get());
+}
+
+/*static*/
+size_t MacIOSurface::GetMaxWidth() {
+ return ::IOSurfaceGetPropertyMaximum(kIOSurfaceWidth);
+}
+
+/*static*/
+size_t MacIOSurface::GetMaxHeight() {
+ return ::IOSurfaceGetPropertyMaximum(kIOSurfaceHeight);
+}
+
+size_t MacIOSurface::GetDevicePixelWidth(size_t plane) const {
+ return ::IOSurfaceGetWidthOfPlane(mIOSurfaceRef.get(), plane);
+}
+
+size_t MacIOSurface::GetDevicePixelHeight(size_t plane) const {
+ return ::IOSurfaceGetHeightOfPlane(mIOSurfaceRef.get(), plane);
+}
+
+size_t MacIOSurface::GetBytesPerRow(size_t plane) const {
+ return ::IOSurfaceGetBytesPerRowOfPlane(mIOSurfaceRef.get(), plane);
+}
+
+size_t MacIOSurface::GetAllocSize() const {
+ return ::IOSurfaceGetAllocSize(mIOSurfaceRef.get());
+}
+
+OSType MacIOSurface::GetPixelFormat() const {
+ return ::IOSurfaceGetPixelFormat(mIOSurfaceRef.get());
+}
+
+void MacIOSurface::IncrementUseCount() {
+ ::IOSurfaceIncrementUseCount(mIOSurfaceRef.get());
+}
+
+void MacIOSurface::DecrementUseCount() {
+ ::IOSurfaceDecrementUseCount(mIOSurfaceRef.get());
+}
+
+void MacIOSurface::Lock(bool aReadOnly) {
+ MOZ_RELEASE_ASSERT(!mIsLocked, "double MacIOSurface lock");
+ ::IOSurfaceLock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0,
+ nullptr);
+ mIsLocked = true;
+}
+
+void MacIOSurface::Unlock(bool aReadOnly) {
+ MOZ_RELEASE_ASSERT(mIsLocked, "MacIOSurface unlock without being locked");
+ ::IOSurfaceUnlock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0,
+ nullptr);
+ mIsLocked = false;
+}
+
+using mozilla::gfx::ColorDepth;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SourceSurface;
+using mozilla::gfx::SurfaceFormat;
+
+static 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();
+ auto* 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();
+}
+
+already_AddRefed<mozilla::gfx::DrawTarget> MacIOSurface::GetAsDrawTargetLocked(
+ mozilla::gfx::BackendType aBackendType) {
+ MOZ_RELEASE_ASSERT(
+ IsLocked(),
+ "Only call GetAsDrawTargetLocked while the surface is locked.");
+
+ size_t bytesPerRow = GetBytesPerRow();
+ size_t ioWidth = GetDevicePixelWidth();
+ size_t ioHeight = GetDevicePixelHeight();
+ unsigned char* ioData = (unsigned char*)GetBaseAddress();
+ SurfaceFormat format = GetFormat();
+ return mozilla::gfx::Factory::CreateDrawTargetForData(
+ aBackendType, ioData, IntSize(ioWidth, ioHeight), bytesPerRow, format);
+}
+
+SurfaceFormat MacIOSurface::GetFormat() const {
+ switch (GetPixelFormat()) {
+ case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
+ case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
+ return SurfaceFormat::NV12;
+ case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange:
+ case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange:
+ return SurfaceFormat::P010;
+ case kCVPixelFormatType_422YpCbCr8_yuvs:
+ case kCVPixelFormatType_422YpCbCr8FullRange:
+ return SurfaceFormat::YUV422;
+ case kCVPixelFormatType_32BGRA:
+ return HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown format");
+ return SurfaceFormat::B8G8R8A8;
+ }
+}
+
+SurfaceFormat MacIOSurface::GetReadFormat() const {
+ SurfaceFormat format = GetFormat();
+ if (format == SurfaceFormat::YUV422) {
+ return SurfaceFormat::R8G8B8X8;
+ }
+ return format;
+}
+
+ColorDepth MacIOSurface::GetColorDepth() const {
+ switch (GetPixelFormat()) {
+ case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange:
+ case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange:
+ return ColorDepth::COLOR_10;
+ default:
+ return ColorDepth::COLOR_8;
+ }
+}
+
+CGLError MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, GLenum target,
+ GLenum internalFormat,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type,
+ GLuint plane) const {
+ return ::CGLTexImageIOSurface2D(ctx, target, internalFormat, width, height,
+ format, type, mIOSurfaceRef.get(), plane);
+}
+
+CGLError MacIOSurface::CGLTexImageIOSurface2D(
+ mozilla::gl::GLContext* aGL, CGLContextObj ctx, size_t plane,
+ mozilla::gfx::SurfaceFormat* aOutReadFormat) {
+ MOZ_ASSERT(plane >= 0);
+ bool isCompatibilityProfile = aGL->IsCompatibilityProfile();
+ OSType pixelFormat = GetPixelFormat();
+
+ GLenum internalFormat;
+ GLenum format;
+ GLenum type;
+ if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
+ pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
+ MOZ_ASSERT(GetPlaneCount() == 2);
+ MOZ_ASSERT(plane < 2);
+
+ // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated
+ // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile.
+ // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats
+ if (plane == 0) {
+ internalFormat = format =
+ (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE) : (LOCAL_GL_RED);
+ } else {
+ internalFormat = format =
+ (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA) : (LOCAL_GL_RG);
+ }
+ type = LOCAL_GL_UNSIGNED_BYTE;
+ if (aOutReadFormat) {
+ *aOutReadFormat = mozilla::gfx::SurfaceFormat::NV12;
+ }
+ } else if (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
+ pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) {
+ MOZ_ASSERT(GetPlaneCount() == 2);
+ MOZ_ASSERT(plane < 2);
+
+ // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated
+ // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile.
+ // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats
+ if (plane == 0) {
+ internalFormat = format =
+ (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE) : (LOCAL_GL_RED);
+ } else {
+ internalFormat = format =
+ (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA) : (LOCAL_GL_RG);
+ }
+ type = LOCAL_GL_UNSIGNED_SHORT;
+ if (aOutReadFormat) {
+ *aOutReadFormat = mozilla::gfx::SurfaceFormat::P010;
+ }
+ } else if (pixelFormat == kCVPixelFormatType_422YpCbCr8_yuvs ||
+ pixelFormat == kCVPixelFormatType_422YpCbCr8FullRange) {
+ MOZ_ASSERT(plane == 0);
+ // The YCBCR_422_APPLE ext is only available in compatibility profile. So,
+ // we should use RGB_422_APPLE for core profile. The difference between
+ // YCBCR_422_APPLE and RGB_422_APPLE is that the YCBCR_422_APPLE converts
+ // the YCbCr value to RGB with REC 601 conversion. But the RGB_422_APPLE
+ // doesn't contain color conversion. You should do the color conversion by
+ // yourself for RGB_422_APPLE.
+ //
+ // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_ycbcr_422.txt
+ // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+ if (isCompatibilityProfile) {
+ format = LOCAL_GL_YCBCR_422_APPLE;
+ if (aOutReadFormat) {
+ *aOutReadFormat = mozilla::gfx::SurfaceFormat::R8G8B8X8;
+ }
+ } else {
+ format = LOCAL_GL_RGB_422_APPLE;
+ if (aOutReadFormat) {
+ *aOutReadFormat = mozilla::gfx::SurfaceFormat::YUV422;
+ }
+ }
+ internalFormat = LOCAL_GL_RGB;
+ type = LOCAL_GL_UNSIGNED_SHORT_8_8_REV_APPLE;
+ } else {
+ MOZ_ASSERT(plane == 0);
+
+ internalFormat = HasAlpha() ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
+ format = LOCAL_GL_BGRA;
+ type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
+ if (aOutReadFormat) {
+ *aOutReadFormat = HasAlpha() ? mozilla::gfx::SurfaceFormat::R8G8B8A8
+ : mozilla::gfx::SurfaceFormat::R8G8B8X8;
+ }
+ }
+
+ auto err =
+ CGLTexImageIOSurface2D(ctx, LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ internalFormat, GetDevicePixelWidth(plane),
+ GetDevicePixelHeight(plane), format, type, plane);
+ if (err) {
+ const auto formatChars = (const char*)&pixelFormat;
+ const char formatStr[] = {formatChars[3], formatChars[2], formatChars[1],
+ formatChars[0], 0};
+ const nsPrintfCString errStr(
+ "CGLTexImageIOSurface2D(context, target, 0x%04x,"
+ " %u, %u, 0x%04x, 0x%04x, iosurfPtr, %u) -> %i",
+ internalFormat, uint32_t(GetDevicePixelWidth(plane)),
+ uint32_t(GetDevicePixelHeight(plane)), format, type,
+ (unsigned int)plane, err);
+ gfxCriticalError() << errStr.get() << " (iosurf format: " << formatStr
+ << ")";
+ }
+ return err;
+}
+
+void MacIOSurface::SetColorSpace(const mozilla::gfx::ColorSpace2 cs) const {
+ Maybe<CFStringRef> str;
+ switch (cs) {
+ case gfx::ColorSpace2::UNKNOWN:
+ break;
+ case gfx::ColorSpace2::SRGB:
+ str = Some(kCGColorSpaceSRGB);
+ break;
+ case gfx::ColorSpace2::DISPLAY_P3:
+ str = Some(kCGColorSpaceDisplayP3);
+ break;
+ case gfx::ColorSpace2::BT601_525: // Doesn't really have a better option.
+ case gfx::ColorSpace2::BT709:
+ str = Some(kCGColorSpaceITUR_709);
+ break;
+ case gfx::ColorSpace2::BT2020:
+ str = Some(kCGColorSpaceITUR_2020);
+ break;
+ }
+ if (str) {
+ IOSurfaceSetValue(mIOSurfaceRef.get(), CFSTR("IOSurfaceColorSpace"), *str);
+ }
+}
diff --git a/gfx/2d/MacIOSurface.h b/gfx/2d/MacIOSurface.h
new file mode 100644
index 0000000000..ef176430d1
--- /dev/null
+++ b/gfx/2d/MacIOSurface.h
@@ -0,0 +1,162 @@
+/* -*- 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 MacIOSurface_h__
+#define MacIOSurface_h__
+#ifdef XP_DARWIN
+# include <CoreVideo/CoreVideo.h>
+# include <IOSurface/IOSurface.h>
+# include <QuartzCore/QuartzCore.h>
+# include <dlfcn.h>
+
+# include "mozilla/gfx/Types.h"
+# include "CFTypeRefPtr.h"
+
+namespace mozilla {
+namespace gl {
+class GLContext;
+}
+} // namespace mozilla
+
+struct _CGLContextObject;
+
+typedef _CGLContextObject* CGLContextObj;
+typedef uint32_t IOSurfaceID;
+
+# ifdef XP_IOS
+typedef kern_return_t IOReturn;
+typedef int CGLError;
+# endif
+
+# ifdef XP_MACOSX
+# import <OpenGL/OpenGL.h>
+# else
+# import <OpenGLES/ES2/gl.h>
+# endif
+
+# include "2D.h"
+# include "mozilla/RefCounted.h"
+# include "mozilla/RefPtr.h"
+
+class MacIOSurface final
+ : public mozilla::external::AtomicRefCounted<MacIOSurface> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(MacIOSurface)
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::BackendType BackendType;
+ typedef mozilla::gfx::IntSize IntSize;
+ typedef mozilla::gfx::YUVColorSpace YUVColorSpace;
+ typedef mozilla::gfx::ColorSpace2 ColorSpace2;
+ typedef mozilla::gfx::TransferFunction TransferFunction;
+ typedef mozilla::gfx::ColorRange ColorRange;
+ typedef mozilla::gfx::ColorDepth ColorDepth;
+
+ // 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,
+ bool aHasAlpha = true);
+ static already_AddRefed<MacIOSurface> CreateNV12OrP010Surface(
+ const IntSize& aYSize, const IntSize& aCbCrSize,
+ YUVColorSpace aColorSpace, TransferFunction aTransferFunction,
+ ColorRange aColorRange, ColorDepth aColorDepth);
+ static already_AddRefed<MacIOSurface> CreateYUV422Surface(
+ const IntSize& aSize, YUVColorSpace aColorSpace, ColorRange aColorRange);
+ static void ReleaseIOSurface(MacIOSurface* aIOSurface);
+ static already_AddRefed<MacIOSurface> LookupSurface(
+ IOSurfaceID aSurfaceID, bool aHasAlpha = true,
+ mozilla::gfx::YUVColorSpace aColorSpace =
+ mozilla::gfx::YUVColorSpace::Identity);
+
+ explicit MacIOSurface(CFTypeRefPtr<IOSurfaceRef> aIOSurfaceRef,
+ bool aHasAlpha = true,
+ mozilla::gfx::YUVColorSpace aColorSpace =
+ mozilla::gfx::YUVColorSpace::Identity);
+
+ ~MacIOSurface();
+ IOSurfaceID GetIOSurfaceID() const;
+ void* GetBaseAddress() const;
+ void* GetBaseAddressOfPlane(size_t planeIndex) const;
+ size_t GetPlaneCount() const;
+ OSType GetPixelFormat() const;
+ // 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) const;
+ size_t GetHeight(size_t plane = 0) const;
+ IntSize GetSize(size_t plane = 0) const {
+ return IntSize(GetWidth(plane), GetHeight(plane));
+ }
+ size_t GetDevicePixelWidth(size_t plane = 0) const;
+ size_t GetDevicePixelHeight(size_t plane = 0) const;
+ size_t GetBytesPerRow(size_t plane = 0) const;
+ size_t GetAllocSize() const;
+ void Lock(bool aReadOnly = true);
+ void Unlock(bool aReadOnly = true);
+ bool IsLocked() const { return mIsLocked; }
+ void IncrementUseCount();
+ void DecrementUseCount();
+ bool HasAlpha() const { return mHasAlpha; }
+ mozilla::gfx::SurfaceFormat GetFormat() const;
+ mozilla::gfx::SurfaceFormat GetReadFormat() const;
+ mozilla::gfx::ColorDepth GetColorDepth() const;
+ // This would be better suited on MacIOSurfaceImage type, however due to the
+ // current data structure, this is not possible as only the IOSurfaceRef is
+ // being used across.
+ void SetYUVColorSpace(YUVColorSpace aColorSpace) {
+ mColorSpace = aColorSpace;
+ }
+ YUVColorSpace GetYUVColorSpace() const { return mColorSpace; }
+ bool IsFullRange() const {
+ OSType format = GetPixelFormat();
+ return (format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
+ format == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
+ }
+ mozilla::gfx::ColorRange GetColorRange() const {
+ if (IsFullRange()) return mozilla::gfx::ColorRange::FULL;
+ return mozilla::gfx::ColorRange::LIMITED;
+ }
+
+ // 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(
+ mozilla::gl::GLContext* aGL, CGLContextObj ctxt, size_t plane,
+ mozilla::gfx::SurfaceFormat* aOutReadFormat = nullptr);
+ CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, GLenum target,
+ GLenum internalFormat, GLsizei width,
+ GLsizei height, GLenum format, GLenum type,
+ GLuint plane) const;
+ already_AddRefed<SourceSurface> GetAsSurface();
+
+ // Creates a DrawTarget that wraps the data in the IOSurface. Rendering to
+ // this DrawTarget directly manipulates the contents of the IOSurface.
+ // Only call when the surface is already locked for writing!
+ // The returned DrawTarget must only be used while the surface is still
+ // locked.
+ // Also, only call this if you're reasonably sure that the DrawTarget of the
+ // selected backend supports the IOSurface's SurfaceFormat.
+ already_AddRefed<DrawTarget> GetAsDrawTargetLocked(BackendType aBackendType);
+
+ static size_t GetMaxWidth();
+ static size_t GetMaxHeight();
+ CFTypeRefPtr<IOSurfaceRef> GetIOSurfaceRef() { return mIOSurfaceRef; }
+
+ void SetColorSpace(mozilla::gfx::ColorSpace2) const;
+
+ ColorSpace2 mColorPrimaries = ColorSpace2::UNKNOWN;
+
+ private:
+ CFTypeRefPtr<IOSurfaceRef> mIOSurfaceRef;
+ const bool mHasAlpha;
+ YUVColorSpace mColorSpace = YUVColorSpace::Identity;
+ bool mIsLocked = false;
+};
+
+#endif
+#endif
diff --git a/gfx/2d/Matrix.cpp b/gfx/2d/Matrix.cpp
new file mode 100644
index 0000000000..cb8830c168
--- /dev/null
+++ b/gfx/2d/Matrix.cpp
@@ -0,0 +1,179 @@
+/* -*- 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 "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
+
+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);
+}
+
+template <>
+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;
+}
+
+template <>
+MatrixDouble MatrixDouble::Rotation(Double aAngle) {
+ MatrixDouble newMatrix;
+
+ Double s = sin(aAngle);
+ Double c = cos(aAngle);
+
+ newMatrix._11 = c;
+ newMatrix._12 = s;
+ newMatrix._21 = -s;
+ newMatrix._22 = c;
+
+ return newMatrix;
+}
+
+template <>
+Matrix4x4 MatrixDouble::operator*(const Matrix4x4& aMatrix) const {
+ Matrix4x4 resultMatrix;
+
+ resultMatrix._11 = this->_11 * aMatrix._11 + this->_12 * aMatrix._21;
+ resultMatrix._12 = this->_11 * aMatrix._12 + this->_12 * aMatrix._22;
+ resultMatrix._13 = this->_11 * aMatrix._13 + this->_12 * aMatrix._23;
+ resultMatrix._14 = this->_11 * aMatrix._14 + this->_12 * aMatrix._24;
+
+ resultMatrix._21 = this->_21 * aMatrix._11 + this->_22 * aMatrix._21;
+ resultMatrix._22 = this->_21 * aMatrix._12 + this->_22 * aMatrix._22;
+ resultMatrix._23 = this->_21 * aMatrix._13 + this->_22 * aMatrix._23;
+ resultMatrix._24 = this->_21 * aMatrix._14 + this->_22 * aMatrix._24;
+
+ resultMatrix._31 = aMatrix._31;
+ resultMatrix._32 = aMatrix._32;
+ resultMatrix._33 = aMatrix._33;
+ resultMatrix._34 = aMatrix._34;
+
+ resultMatrix._41 =
+ this->_31 * aMatrix._11 + this->_32 * aMatrix._21 + aMatrix._41;
+ resultMatrix._42 =
+ this->_31 * aMatrix._12 + this->_32 * aMatrix._22 + aMatrix._42;
+ resultMatrix._43 =
+ this->_31 * aMatrix._13 + this->_32 * aMatrix._23 + aMatrix._43;
+ resultMatrix._44 =
+ this->_31 * aMatrix._14 + this->_32 * aMatrix._24 + aMatrix._44;
+
+ return resultMatrix;
+}
+
+// Intersect the polygon given by aPoints with the half space induced by
+// aPlaneNormal and return the resulting polygon. The returned points are
+// stored in aDestBuffer, and its meaningful subspan is returned.
+template <typename F>
+Span<Point4DTyped<UnknownUnits, F>> IntersectPolygon(
+ Span<Point4DTyped<UnknownUnits, F>> aPoints,
+ const Point4DTyped<UnknownUnits, F>& aPlaneNormal,
+ Span<Point4DTyped<UnknownUnits, F>> aDestBuffer) {
+ if (aPoints.Length() < 1 || aDestBuffer.Length() < 1) {
+ return {};
+ }
+
+ size_t nextIndex = 0; // aDestBuffer[nextIndex] is the next emitted point.
+
+ // Iterate over the polygon edges. In each iteration the current edge
+ // is the edge from *prevPoint to point. If the two end points lie on
+ // different sides of the plane, we have an intersection. Otherwise,
+ // the edge is either completely "inside" the half-space created by
+ // the clipping plane, and we add curPoint, or it is completely
+ // "outside", and we discard curPoint. This loop can create duplicated
+ // points in the polygon.
+ const auto* prevPoint = &aPoints[aPoints.Length() - 1];
+ F prevDot = aPlaneNormal.DotProduct(*prevPoint);
+ for (const auto& curPoint : aPoints) {
+ F curDot = aPlaneNormal.DotProduct(curPoint);
+
+ if ((curDot >= 0.0) != (prevDot >= 0.0)) {
+ // An intersection with the clipping plane has been detected.
+ // Interpolate to find the intersecting curPoint and emit it.
+ F t = -prevDot / (curDot - prevDot);
+ aDestBuffer[nextIndex++] = curPoint * t + *prevPoint * (1.0 - t);
+ if (nextIndex >= aDestBuffer.Length()) {
+ break;
+ }
+ }
+
+ if (curDot >= 0.0) {
+ // Emit any source points that are on the positive side of the
+ // clipping plane.
+ aDestBuffer[nextIndex++] = curPoint;
+ if (nextIndex >= aDestBuffer.Length()) {
+ break;
+ }
+ }
+
+ prevPoint = &curPoint;
+ prevDot = curDot;
+ }
+
+ return aDestBuffer.To(nextIndex);
+}
+
+template Span<Point4DTyped<UnknownUnits, Float>> IntersectPolygon(
+ Span<Point4DTyped<UnknownUnits, Float>> aPoints,
+ const Point4DTyped<UnknownUnits, Float>& aPlaneNormal,
+ Span<Point4DTyped<UnknownUnits, Float>> aDestBuffer);
+template Span<Point4DTyped<UnknownUnits, Double>> IntersectPolygon(
+ Span<Point4DTyped<UnknownUnits, Double>> aPoints,
+ const Point4DTyped<UnknownUnits, Double>& aPlaneNormal,
+ Span<Point4DTyped<UnknownUnits, Double>> aDestBuffer);
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h
new file mode 100644
index 0000000000..d59ed65d5b
--- /dev/null
+++ b/gfx/2d/Matrix.h
@@ -0,0 +1,2330 @@
+/* -*- 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_MATRIX_H_
+#define MOZILLA_GFX_MATRIX_H_
+
+#include "Types.h"
+#include "Triangle.h"
+#include "Rect.h"
+#include "Point.h"
+#include "Quaternion.h"
+#include <iosfwd>
+#include <math.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/gfx/ScaleFactors2D.h"
+#include "mozilla/Span.h"
+
+namespace mozilla {
+namespace gfx {
+
+static inline bool FuzzyEqual(Float aV1, Float aV2) {
+ // XXX - Check if fabs does the smart thing and just negates the sign bit.
+ return fabs(aV2 - aV1) < 1e-6;
+}
+
+template <typename F>
+Span<Point4DTyped<UnknownUnits, F>> IntersectPolygon(
+ Span<Point4DTyped<UnknownUnits, F>> aPoints,
+ const Point4DTyped<UnknownUnits, F>& aPlaneNormal,
+ Span<Point4DTyped<UnknownUnits, F>> aDestBuffer);
+
+template <class T>
+using BaseMatrixScales = BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>;
+
+using MatrixScales = BaseMatrixScales<float>;
+using MatrixScalesDouble = BaseMatrixScales<double>;
+
+template <class T>
+class BaseMatrix {
+ // Alias that maps to either Point or PointDouble depending on whether T is a
+ // float or a double.
+ typedef PointTyped<UnknownUnits, T> MatrixPoint;
+ // Same for size and rect
+ typedef SizeTyped<UnknownUnits, T> MatrixSize;
+ typedef RectTyped<UnknownUnits, T> MatrixRect;
+
+ public:
+ BaseMatrix() : _11(1.0f), _12(0), _21(0), _22(1.0f), _31(0), _32(0) {}
+ BaseMatrix(T a11, T a12, T a21, T a22, T a31, T a32)
+ : _11(a11), _12(a12), _21(a21), _22(a22), _31(a31), _32(a32) {}
+ union {
+ struct {
+ T _11, _12;
+ T _21, _22;
+ T _31, _32;
+ };
+ T components[6];
+ };
+
+ template <class T2>
+ explicit BaseMatrix(const BaseMatrix<T2>& aOther)
+ : _11(aOther._11),
+ _12(aOther._12),
+ _21(aOther._21),
+ _22(aOther._22),
+ _31(aOther._31),
+ _32(aOther._32) {}
+
+ MOZ_ALWAYS_INLINE BaseMatrix Copy() const { return BaseMatrix<T>(*this); }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseMatrix& aMatrix) {
+ if (aMatrix.IsIdentity()) {
+ return aStream << "[ I ]";
+ }
+ return aStream << "[ " << aMatrix._11 << " " << aMatrix._12 << "; "
+ << aMatrix._21 << " " << aMatrix._22 << "; " << aMatrix._31
+ << " " << aMatrix._32 << "; ]";
+ }
+
+ MatrixPoint TransformPoint(const MatrixPoint& aPoint) const {
+ MatrixPoint retPoint;
+
+ retPoint.x = aPoint.x * _11 + aPoint.y * _21 + _31;
+ retPoint.y = aPoint.x * _12 + aPoint.y * _22 + _32;
+
+ return retPoint;
+ }
+
+ MatrixSize TransformSize(const MatrixSize& aSize) const {
+ MatrixSize retSize;
+
+ retSize.width = aSize.width * _11 + aSize.height * _21;
+ retSize.height = aSize.width * _12 + aSize.height * _22;
+
+ return retSize;
+ }
+
+ /**
+ * In most cases you probably want to use TransformBounds. This function
+ * just transforms the top-left and size separately and constructs a rect
+ * from those results.
+ */
+ MatrixRect TransformRect(const MatrixRect& aRect) const {
+ return MatrixRect(TransformPoint(aRect.TopLeft()),
+ TransformSize(aRect.Size()));
+ }
+
+ GFX2D_API MatrixRect TransformBounds(const MatrixRect& aRect) const {
+ int i;
+ MatrixPoint quad[4];
+ T min_x, max_x;
+ T 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 MatrixRect(min_x, min_y, max_x - min_x, max_y - min_y);
+ }
+
+ static BaseMatrix<T> Translation(T aX, T aY) {
+ return BaseMatrix<T>(1.0f, 0.0f, 0.0f, 1.0f, aX, aY);
+ }
+
+ static BaseMatrix<T> Translation(MatrixPoint aPoint) {
+ return Translation(aPoint.x, aPoint.y);
+ }
+
+ /**
+ * Apply a translation to this matrix.
+ *
+ * The "Pre" in this method's name means that the translation is applied
+ * -before- this matrix's existing transformation. That is, any vector that
+ * is multiplied by the resulting matrix will first be translated, then be
+ * transformed by the original transform.
+ *
+ * Calling this method will result in this matrix having the same value as
+ * the result of:
+ *
+ * BaseMatrix<T>::Translation(x, y) * this
+ *
+ * (Note that in performance critical code multiplying by the result of a
+ * Translation()/Scaling() call is not recommended since that results in a
+ * full matrix multiply involving 12 floating-point multiplications. Calling
+ * this method would be preferred since it only involves four floating-point
+ * multiplications.)
+ */
+ BaseMatrix<T>& PreTranslate(T aX, T aY) {
+ _31 += _11 * aX + _21 * aY;
+ _32 += _12 * aX + _22 * aY;
+
+ return *this;
+ }
+
+ BaseMatrix<T>& PreTranslate(const MatrixPoint& aPoint) {
+ return PreTranslate(aPoint.x, aPoint.y);
+ }
+
+ /**
+ * Similar to PreTranslate, but the translation is applied -after- this
+ * matrix's existing transformation instead of before it.
+ *
+ * This method is generally less used than PreTranslate since typically code
+ * want to adjust an existing user space to device space matrix to create a
+ * transform to device space from a -new- user space (translated from the
+ * previous user space). In that case consumers will need to use the Pre*
+ * variants of the matrix methods rather than using the Post* methods, since
+ * the Post* methods add a transform to the device space end of the
+ * transformation.
+ */
+ BaseMatrix<T>& PostTranslate(T aX, T aY) {
+ _31 += aX;
+ _32 += aY;
+ return *this;
+ }
+
+ BaseMatrix<T>& PostTranslate(const MatrixPoint& aPoint) {
+ return PostTranslate(aPoint.x, aPoint.y);
+ }
+
+ static BaseMatrix<T> Scaling(T aScaleX, T aScaleY) {
+ return BaseMatrix<T>(aScaleX, 0.0f, 0.0f, aScaleY, 0.0f, 0.0f);
+ }
+
+ static BaseMatrix<T> Scaling(const BaseMatrixScales<T>& scale) {
+ return Scaling(scale.xScale, scale.yScale);
+ }
+
+ /**
+ * Similar to PreTranslate, but applies a scale instead of a translation.
+ */
+ BaseMatrix<T>& PreScale(T aX, T aY) {
+ _11 *= aX;
+ _12 *= aX;
+ _21 *= aY;
+ _22 *= aY;
+
+ return *this;
+ }
+
+ BaseMatrix<T>& PreScale(const BaseMatrixScales<T>& scale) {
+ return PreScale(scale.xScale, scale.yScale);
+ }
+
+ /**
+ * Similar to PostTranslate, but applies a scale instead of a translation.
+ */
+ BaseMatrix<T>& PostScale(T aScaleX, T aScaleY) {
+ _11 *= aScaleX;
+ _12 *= aScaleY;
+ _21 *= aScaleX;
+ _22 *= aScaleY;
+ _31 *= aScaleX;
+ _32 *= aScaleY;
+
+ return *this;
+ }
+
+ GFX2D_API static BaseMatrix<T> Rotation(T aAngle);
+
+ /**
+ * Similar to PreTranslate, but applies a rotation instead of a translation.
+ */
+ BaseMatrix<T>& PreRotate(T aAngle) {
+ return *this = BaseMatrix<T>::Rotation(aAngle) * *this;
+ }
+
+ bool Invert() {
+ // Compute co-factors.
+ T A = _22;
+ T B = -_21;
+ T C = _21 * _32 - _22 * _31;
+ T D = -_12;
+ T E = _11;
+ T F = _31 * _12 - _11 * _32;
+
+ T det = Determinant();
+
+ if (!det) {
+ return false;
+ }
+
+ T inv_det = 1 / det;
+
+ _11 = inv_det * A;
+ _12 = inv_det * D;
+ _21 = inv_det * B;
+ _22 = inv_det * E;
+ _31 = inv_det * C;
+ _32 = inv_det * F;
+
+ return true;
+ }
+
+ BaseMatrix<T> Inverse() const {
+ BaseMatrix<T> clone = *this;
+ DebugOnly<bool> inverted = clone.Invert();
+ MOZ_ASSERT(inverted,
+ "Attempted to get the inverse of a non-invertible matrix");
+ return clone;
+ }
+
+ T Determinant() const { return _11 * _22 - _12 * _21; }
+
+ BaseMatrix<T> operator*(const BaseMatrix<T>& aMatrix) const {
+ BaseMatrix<T> resultMatrix;
+
+ resultMatrix._11 = this->_11 * aMatrix._11 + this->_12 * aMatrix._21;
+ resultMatrix._12 = this->_11 * aMatrix._12 + this->_12 * aMatrix._22;
+ resultMatrix._21 = this->_21 * aMatrix._11 + this->_22 * aMatrix._21;
+ resultMatrix._22 = this->_21 * aMatrix._12 + this->_22 * aMatrix._22;
+ resultMatrix._31 =
+ this->_31 * aMatrix._11 + this->_32 * aMatrix._21 + aMatrix._31;
+ resultMatrix._32 =
+ this->_31 * aMatrix._12 + this->_32 * aMatrix._22 + aMatrix._32;
+
+ return resultMatrix;
+ }
+
+ BaseMatrix<T>& operator*=(const BaseMatrix<T>& aMatrix) {
+ *this = *this * aMatrix;
+ return *this;
+ }
+
+ /**
+ * Multiplies *this with aMatrix and returns the result.
+ */
+ Matrix4x4 operator*(const Matrix4x4& aMatrix) const;
+
+ /**
+ * Multiplies in the opposite order to operator=*.
+ */
+ BaseMatrix<T>& PreMultiply(const BaseMatrix<T>& aMatrix) {
+ *this = aMatrix * *this;
+ return *this;
+ }
+
+ /**
+ * Please explicitly use either FuzzyEquals or ExactlyEquals.
+ */
+ bool operator==(const BaseMatrix<T>& other) const = delete;
+ bool operator!=(const BaseMatrix<T>& other) const = delete;
+
+ /* Returns true if the other matrix is fuzzy-equal to this matrix.
+ * Note that this isn't a cheap comparison!
+ */
+ bool FuzzyEquals(const BaseMatrix<T>& o) const {
+ return FuzzyEqual(_11, o._11) && FuzzyEqual(_12, o._12) &&
+ FuzzyEqual(_21, o._21) && FuzzyEqual(_22, o._22) &&
+ FuzzyEqual(_31, o._31) && FuzzyEqual(_32, o._32);
+ }
+
+ bool ExactlyEquals(const BaseMatrix<T>& o) const {
+ return _11 == o._11 && _12 == o._12 && _21 == o._21 && _22 == o._22 &&
+ _31 == o._31 && _32 == o._32;
+ }
+
+ /* Verifies that the matrix contains no Infs or NaNs. */
+ bool IsFinite() const {
+ return std::isfinite(_11) && std::isfinite(_12) && std::isfinite(_21) &&
+ std::isfinite(_22) && std::isfinite(_31) && std::isfinite(_32);
+ }
+
+ /* Returns true if the matrix is a rectilinear transformation (i.e.
+ * grid-aligned rectangles are transformed to grid-aligned rectangles)
+ */
+ bool IsRectilinear() const {
+ if (FuzzyEqual(_12, 0) && FuzzyEqual(_21, 0)) {
+ return true;
+ } else if (FuzzyEqual(_22, 0) && FuzzyEqual(_11, 0)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the matrix is anything other than a straight
+ * translation by integers.
+ */
+ bool HasNonIntegerTranslation() const {
+ return HasNonTranslation() || !FuzzyEqual(_31, floor(_31 + 0.5f)) ||
+ !FuzzyEqual(_32, floor(_32 + 0.5f));
+ }
+
+ /**
+ * Returns true if the matrix only has an integer translation.
+ */
+ bool HasOnlyIntegerTranslation() const { return !HasNonIntegerTranslation(); }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a straight translation.
+ */
+ bool HasNonTranslation() const {
+ return !FuzzyEqual(_11, 1.0) || !FuzzyEqual(_22, 1.0) ||
+ !FuzzyEqual(_12, 0.0) || !FuzzyEqual(_21, 0.0);
+ }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a translation or a -1 y scale (y axis flip)
+ */
+ bool HasNonTranslationOrFlip() const {
+ return !FuzzyEqual(_11, 1.0) ||
+ (!FuzzyEqual(_22, 1.0) && !FuzzyEqual(_22, -1.0)) ||
+ !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0);
+ }
+
+ /* Returns true if the matrix is an identity matrix.
+ */
+ bool IsIdentity() const {
+ return _11 == 1.0f && _12 == 0.0f && _21 == 0.0f && _22 == 1.0f &&
+ _31 == 0.0f && _32 == 0.0f;
+ }
+
+ /* Returns true if the matrix is singular.
+ */
+ bool IsSingular() const {
+ T det = Determinant();
+ return !std::isfinite(det) || det == 0;
+ }
+
+ GFX2D_API BaseMatrix<T>& NudgeToIntegers() {
+ NudgeToInteger(&_11);
+ NudgeToInteger(&_12);
+ NudgeToInteger(&_21);
+ NudgeToInteger(&_22);
+ NudgeToInteger(&_31);
+ NudgeToInteger(&_32);
+ return *this;
+ }
+
+ bool IsTranslation() const {
+ return FuzzyEqual(_11, 1.0f) && FuzzyEqual(_12, 0.0f) &&
+ FuzzyEqual(_21, 0.0f) && FuzzyEqual(_22, 1.0f);
+ }
+
+ static bool FuzzyIsInteger(T aValue) {
+ return FuzzyEqual(aValue, floorf(aValue + 0.5f));
+ }
+
+ bool IsIntegerTranslation() const {
+ return IsTranslation() && FuzzyIsInteger(_31) && FuzzyIsInteger(_32);
+ }
+
+ bool IsAllIntegers() const {
+ return FuzzyIsInteger(_11) && FuzzyIsInteger(_12) && FuzzyIsInteger(_21) &&
+ FuzzyIsInteger(_22) && FuzzyIsInteger(_31) && FuzzyIsInteger(_32);
+ }
+
+ MatrixPoint GetTranslation() const { return MatrixPoint(_31, _32); }
+
+ /**
+ * Returns true if matrix is multiple of 90 degrees rotation with flipping,
+ * scaling and translation.
+ */
+ bool PreservesAxisAlignedRectangles() const {
+ return ((FuzzyEqual(_11, 0.0) && FuzzyEqual(_22, 0.0)) ||
+ (FuzzyEqual(_12, 0.0) && FuzzyEqual(_21, 0.0)));
+ }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a translation or scale; this is, if there is
+ * rotation.
+ */
+ bool HasNonAxisAlignedTransform() const {
+ return !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0);
+ }
+
+ /**
+ * Returns true if the matrix has negative scaling (i.e. flip).
+ */
+ bool HasNegativeScaling() const { return (_11 < 0.0) || (_22 < 0.0); }
+
+ /**
+ * Computes the scale factors of this matrix; that is,
+ * the amounts each basis vector is scaled by.
+ * The xMajor parameter indicates if the larger scale is
+ * to be assumed to be in the X direction or not.
+ */
+ BaseMatrixScales<T> ScaleFactors() const {
+ T det = Determinant();
+
+ if (det == 0.0) {
+ return BaseMatrixScales<T>(0.0, 0.0);
+ }
+
+ MatrixSize sz = MatrixSize(1.0, 0.0);
+ sz = TransformSize(sz);
+
+ T major = sqrt(sz.width * sz.width + sz.height * sz.height);
+ T minor = 0.0;
+
+ // ignore mirroring
+ if (det < 0.0) {
+ det = -det;
+ }
+
+ if (major) {
+ minor = det / major;
+ }
+
+ return BaseMatrixScales<T>(major, minor);
+ }
+
+ /**
+ * Returns true if the matrix preserves distances, i.e. a rigid transformation
+ * that doesn't change size or shape). Such a matrix has uniform unit scaling
+ * and an orthogonal basis.
+ */
+ bool PreservesDistance() const {
+ return FuzzyEqual(_11 * _11 + _12 * _12, 1.0) &&
+ FuzzyEqual(_21 * _21 + _22 * _22, 1.0) &&
+ FuzzyEqual(_11 * _21 + _12 * _22, 0.0);
+ }
+};
+
+typedef BaseMatrix<Float> Matrix;
+typedef BaseMatrix<Double> MatrixDouble;
+
+// Helper functions used by Matrix4x4Typed defined in Matrix.cpp
+double SafeTangent(double aTheta);
+double FlushToZero(double aVal);
+
+template <class Units, class F>
+Point4DTyped<Units, F> ComputePerspectivePlaneIntercept(
+ const Point4DTyped<Units, F>& aFirst,
+ const Point4DTyped<Units, F>& aSecond) {
+ // This function will always return a point with a w value of 0.
+ // The X, Y, and Z components will point towards an infinite vanishing
+ // point.
+
+ // We want to interpolate aFirst and aSecond to find the point intersecting
+ // with the w=0 plane.
+
+ // Since we know what we want the w component to be, we can rearrange the
+ // interpolation equation and solve for t.
+ float t = -aFirst.w / (aSecond.w - aFirst.w);
+
+ // Use t to find the remainder of the components
+ return aFirst + (aSecond - aFirst) * t;
+}
+
+template <class SourceUnits, class TargetUnits, class T>
+class Matrix4x4Typed {
+ public:
+ typedef PointTyped<SourceUnits, T> SourcePoint;
+ typedef PointTyped<TargetUnits, T> TargetPoint;
+ typedef Point3DTyped<SourceUnits, T> SourcePoint3D;
+ typedef Point3DTyped<TargetUnits, T> TargetPoint3D;
+ typedef Point4DTyped<SourceUnits, T> SourcePoint4D;
+ typedef Point4DTyped<TargetUnits, T> TargetPoint4D;
+ typedef RectTyped<SourceUnits, T> SourceRect;
+ typedef RectTyped<TargetUnits, T> TargetRect;
+
+ Matrix4x4Typed()
+ : _11(1.0f),
+ _12(0.0f),
+ _13(0.0f),
+ _14(0.0f),
+ _21(0.0f),
+ _22(1.0f),
+ _23(0.0f),
+ _24(0.0f),
+ _31(0.0f),
+ _32(0.0f),
+ _33(1.0f),
+ _34(0.0f),
+ _41(0.0f),
+ _42(0.0f),
+ _43(0.0f),
+ _44(1.0f) {}
+
+ Matrix4x4Typed(T a11, T a12, T a13, T a14, T a21, T a22, T a23, T a24, T a31,
+ T a32, T a33, T a34, T a41, T a42, T a43, T a44)
+ : _11(a11),
+ _12(a12),
+ _13(a13),
+ _14(a14),
+ _21(a21),
+ _22(a22),
+ _23(a23),
+ _24(a24),
+ _31(a31),
+ _32(a32),
+ _33(a33),
+ _34(a34),
+ _41(a41),
+ _42(a42),
+ _43(a43),
+ _44(a44) {}
+
+ explicit Matrix4x4Typed(const T aArray[16]) {
+ memcpy(components, aArray, sizeof(components));
+ }
+
+ Matrix4x4Typed(const Matrix4x4Typed& aOther) {
+ memcpy(components, aOther.components, sizeof(components));
+ }
+
+ template <class T2>
+ explicit Matrix4x4Typed(
+ const Matrix4x4Typed<SourceUnits, TargetUnits, T2>& aOther)
+ : _11(aOther._11),
+ _12(aOther._12),
+ _13(aOther._13),
+ _14(aOther._14),
+ _21(aOther._21),
+ _22(aOther._22),
+ _23(aOther._23),
+ _24(aOther._24),
+ _31(aOther._31),
+ _32(aOther._32),
+ _33(aOther._33),
+ _34(aOther._34),
+ _41(aOther._41),
+ _42(aOther._42),
+ _43(aOther._43),
+ _44(aOther._44) {}
+
+ union {
+ struct {
+ T _11, _12, _13, _14;
+ T _21, _22, _23, _24;
+ T _31, _32, _33, _34;
+ T _41, _42, _43, _44;
+ };
+ T components[16];
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Matrix4x4Typed& aMatrix) {
+ if (aMatrix.Is2D()) {
+ BaseMatrix<T> matrix = aMatrix.As2D();
+ return aStream << matrix;
+ }
+ const T* f = &aMatrix._11;
+ aStream << "[ " << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3]
+ << "; ]";
+ return aStream;
+ }
+
+ Point4DTyped<UnknownUnits, T>& operator[](int aIndex) {
+ MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index");
+ return *reinterpret_cast<Point4DTyped<UnknownUnits, T>*>((&_11) +
+ 4 * aIndex);
+ }
+ const Point4DTyped<UnknownUnits, T>& operator[](int aIndex) const {
+ MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index");
+ return *reinterpret_cast<const Point4DTyped<UnknownUnits, T>*>((&_11) +
+ 4 * aIndex);
+ }
+
+ /**
+ * Returns true if the matrix is isomorphic to a 2D affine transformation.
+ */
+ bool Is2D() const {
+ if (_13 != 0.0f || _14 != 0.0f || _23 != 0.0f || _24 != 0.0f ||
+ _31 != 0.0f || _32 != 0.0f || _33 != 1.0f || _34 != 0.0f ||
+ _43 != 0.0f || _44 != 1.0f) {
+ return false;
+ }
+ return true;
+ }
+
+ bool Is2D(BaseMatrix<T>* aMatrix) const {
+ if (!Is2D()) {
+ return false;
+ }
+ if (aMatrix) {
+ aMatrix->_11 = _11;
+ aMatrix->_12 = _12;
+ aMatrix->_21 = _21;
+ aMatrix->_22 = _22;
+ aMatrix->_31 = _41;
+ aMatrix->_32 = _42;
+ }
+ return true;
+ }
+
+ BaseMatrix<T> As2D() const {
+ MOZ_ASSERT(Is2D(), "Matrix is not a 2D affine transform");
+
+ return BaseMatrix<T>(_11, _12, _21, _22, _41, _42);
+ }
+
+ bool CanDraw2D(BaseMatrix<T>* aMatrix = nullptr) const {
+ if (_14 != 0.0f || _24 != 0.0f || _44 != 1.0f) {
+ return false;
+ }
+ if (aMatrix) {
+ aMatrix->_11 = _11;
+ aMatrix->_12 = _12;
+ aMatrix->_21 = _21;
+ aMatrix->_22 = _22;
+ aMatrix->_31 = _41;
+ aMatrix->_32 = _42;
+ }
+ return true;
+ }
+
+ Matrix4x4Typed& ProjectTo2D() {
+ _31 = 0.0f;
+ _32 = 0.0f;
+ _13 = 0.0f;
+ _23 = 0.0f;
+ _33 = 1.0f;
+ _43 = 0.0f;
+ _34 = 0.0f;
+ // Some matrices, such as those derived from perspective transforms,
+ // can modify _44 from 1, while leaving the rest of the fourth column
+ // (_14, _24) at 0. In this case, after resetting the third row and
+ // third column above, the value of _44 functions only to scale the
+ // coordinate transform divide by W. The matrix can be converted to
+ // a true 2D matrix by normalizing out the scaling effect of _44 on
+ // the remaining components ahead of time.
+ if (_14 == 0.0f && _24 == 0.0f && _44 != 1.0f && _44 != 0.0f) {
+ T scale = 1.0f / _44;
+ _11 *= scale;
+ _12 *= scale;
+ _21 *= scale;
+ _22 *= scale;
+ _41 *= scale;
+ _42 *= scale;
+ _44 = 1.0f;
+ }
+ return *this;
+ }
+
+ template <class F>
+ Point4DTyped<TargetUnits, F> ProjectPoint(
+ const PointTyped<SourceUnits, F>& aPoint) const {
+ // Find a value for z that will transform to 0.
+
+ // The transformed value of z is computed as:
+ // z' = aPoint.x * _13 + aPoint.y * _23 + z * _33 + _43;
+
+ // Solving for z when z' = 0 gives us:
+ F z = -(aPoint.x * _13 + aPoint.y * _23 + _43) / _33;
+
+ // Compute the transformed point
+ return this->TransformPoint(
+ Point4DTyped<SourceUnits, F>(aPoint.x, aPoint.y, z, 1));
+ }
+
+ template <class F>
+ RectTyped<TargetUnits, F> ProjectRectBounds(
+ const RectTyped<SourceUnits, F>& aRect,
+ const RectTyped<TargetUnits, F>& aClip) const {
+ // This function must never return std::numeric_limits<Float>::max() or any
+ // other arbitrary large value in place of inifinity. This often occurs
+ // when aRect is an inversed projection matrix or when aRect is transformed
+ // to be partly behind and in front of the camera (w=0 plane in homogenous
+ // coordinates) - See Bug 1035611
+
+ // Some call-sites will call RoundGfxRectToAppRect which clips both the
+ // extents and dimensions of the rect to be bounded by nscoord_MAX.
+ // If we return a Rect that, when converted to nscoords, has a width or
+ // height greater than nscoord_MAX, RoundGfxRectToAppRect will clip the
+ // overflow off both the min and max end of the rect after clipping the
+ // extents of the rect, resulting in a translation of the rect towards the
+ // infinite end.
+
+ // The bounds returned by ProjectRectBounds are expected to be clipped only
+ // on the edges beyond the bounds of the coordinate system; otherwise, the
+ // clipped bounding box would be smaller than the correct one and result
+ // bugs such as incorrect culling (eg. Bug 1073056)
+
+ // To address this without requiring all code to work in homogenous
+ // coordinates or interpret infinite values correctly, a specialized
+ // clipping function is integrated into ProjectRectBounds.
+
+ // Callers should pass an aClip value that represents the extents to clip
+ // the result to, in the same coordinate system as aRect.
+ Point4DTyped<TargetUnits, F> points[4];
+
+ points[0] = ProjectPoint(aRect.TopLeft());
+ points[1] = ProjectPoint(aRect.TopRight());
+ points[2] = ProjectPoint(aRect.BottomRight());
+ points[3] = ProjectPoint(aRect.BottomLeft());
+
+ F min_x = std::numeric_limits<F>::max();
+ F min_y = std::numeric_limits<F>::max();
+ F max_x = -std::numeric_limits<F>::max();
+ F max_y = -std::numeric_limits<F>::max();
+
+ for (int i = 0; i < 4; i++) {
+ // Only use points that exist above the w=0 plane
+ if (points[i].HasPositiveWCoord()) {
+ PointTyped<TargetUnits, F> point2d =
+ aClip.ClampPoint(points[i].As2DPoint());
+ min_x = std::min<F>(point2d.x, min_x);
+ max_x = std::max<F>(point2d.x, max_x);
+ min_y = std::min<F>(point2d.y, min_y);
+ max_y = std::max<F>(point2d.y, max_y);
+ }
+
+ int next = (i == 3) ? 0 : i + 1;
+ if (points[i].HasPositiveWCoord() != points[next].HasPositiveWCoord()) {
+ // If the line between two points crosses the w=0 plane, then
+ // interpolate to find the point of intersection with the w=0 plane and
+ // use that instead.
+ Point4DTyped<TargetUnits, F> intercept =
+ ComputePerspectivePlaneIntercept(points[i], points[next]);
+ // Since intercept.w will always be 0 here, we interpret x,y,z as a
+ // direction towards an infinite vanishing point.
+ if (intercept.x < 0.0f) {
+ min_x = aClip.X();
+ } else if (intercept.x > 0.0f) {
+ max_x = aClip.XMost();
+ }
+ if (intercept.y < 0.0f) {
+ min_y = aClip.Y();
+ } else if (intercept.y > 0.0f) {
+ max_y = aClip.YMost();
+ }
+ }
+ }
+
+ if (max_x < min_x || max_y < min_y) {
+ return RectTyped<TargetUnits, F>(0, 0, 0, 0);
+ }
+
+ return RectTyped<TargetUnits, F>(min_x, min_y, max_x - min_x,
+ max_y - min_y);
+ }
+
+ /**
+ * TransformAndClipBounds transforms aRect as a bounding box, while clipping
+ * the transformed bounds to the extents of aClip.
+ */
+ template <class F>
+ RectTyped<TargetUnits, F> TransformAndClipBounds(
+ const RectTyped<SourceUnits, F>& aRect,
+ const RectTyped<TargetUnits, F>& aClip) const {
+ PointTyped<UnknownUnits, F> verts[kTransformAndClipRectMaxVerts];
+ size_t vertCount = TransformAndClipRect(aRect, aClip, verts);
+
+ F min_x = std::numeric_limits<F>::max();
+ F min_y = std::numeric_limits<F>::max();
+ F max_x = -std::numeric_limits<F>::max();
+ F max_y = -std::numeric_limits<F>::max();
+ for (size_t i = 0; i < vertCount; i++) {
+ min_x = std::min(min_x, verts[i].x.value);
+ max_x = std::max(max_x, verts[i].x.value);
+ min_y = std::min(min_y, verts[i].y.value);
+ max_y = std::max(max_y, verts[i].y.value);
+ }
+
+ if (max_x < min_x || max_y < min_y) {
+ return RectTyped<TargetUnits, F>(0, 0, 0, 0);
+ }
+
+ return RectTyped<TargetUnits, F>(min_x, min_y, max_x - min_x,
+ max_y - min_y);
+ }
+
+ template <class F>
+ RectTyped<TargetUnits, F> TransformAndClipBounds(
+ const TriangleTyped<SourceUnits, F>& aTriangle,
+ const RectTyped<TargetUnits, F>& aClip) const {
+ return TransformAndClipBounds(aTriangle.BoundingBox(), aClip);
+ }
+
+ /**
+ * TransformAndClipRect projects a rectangle and clips against view frustum
+ * clipping planes in homogenous space so that its projected vertices are
+ * constrained within the 2d rectangle passed in aClip.
+ * The resulting vertices are populated in aVerts. aVerts must be
+ * pre-allocated to hold at least kTransformAndClipRectMaxVerts Points.
+ * The vertex count is returned by TransformAndClipRect. It is possible to
+ * emit fewer than 3 vertices, indicating that aRect will not be visible
+ * within aClip.
+ */
+ template <class F>
+ size_t TransformAndClipRect(const RectTyped<SourceUnits, F>& aRect,
+ const RectTyped<TargetUnits, F>& aClip,
+ PointTyped<TargetUnits, F>* aVerts) const {
+ typedef Point4DTyped<UnknownUnits, F> P4D;
+
+ // The initial polygon is made up by the corners of aRect in homogenous
+ // space, mapped into the destination space of this transform.
+ P4D rectCorners[] = {
+ TransformPoint(P4D(aRect.X(), aRect.Y(), 0, 1)),
+ TransformPoint(P4D(aRect.XMost(), aRect.Y(), 0, 1)),
+ TransformPoint(P4D(aRect.XMost(), aRect.YMost(), 0, 1)),
+ TransformPoint(P4D(aRect.X(), aRect.YMost(), 0, 1)),
+ };
+
+ // Cut off pieces of the polygon that are outside of aClip (the "view
+ // frustrum"), by consecutively intersecting the polygon with the half space
+ // induced by the clipping plane for each side of aClip.
+ // View frustum clipping planes are described as normals originating from
+ // the 0,0,0,0 origin.
+ // Each pass can increase or decrease the number of points that make up the
+ // current clipped polygon. We double buffer the set of points, alternating
+ // between polygonBufA and polygonBufB. Duplicated points in the polygons
+ // are kept around until all clipping is done. The loop at the end filters
+ // out any consecutive duplicates.
+ P4D polygonBufA[kTransformAndClipRectMaxVerts];
+ P4D polygonBufB[kTransformAndClipRectMaxVerts];
+
+ Span<P4D> polygon(rectCorners);
+ polygon = IntersectPolygon<F>(polygon, P4D(1.0, 0.0, 0.0, -aClip.X()),
+ polygonBufA);
+ polygon = IntersectPolygon<F>(polygon, P4D(-1.0, 0.0, 0.0, aClip.XMost()),
+ polygonBufB);
+ polygon = IntersectPolygon<F>(polygon, P4D(0.0, 1.0, 0.0, -aClip.Y()),
+ polygonBufA);
+ polygon = IntersectPolygon<F>(polygon, P4D(0.0, -1.0, 0.0, aClip.YMost()),
+ polygonBufB);
+
+ size_t vertCount = 0;
+ for (const auto& srcPoint : polygon) {
+ PointTyped<TargetUnits, F> p;
+ if (srcPoint.w == 0.0) {
+ // If a point lies on the intersection of the clipping planes at
+ // (0,0,0,0), we must avoid a division by zero w component.
+ p = PointTyped<TargetUnits, F>(0.0, 0.0);
+ } else {
+ p = srcPoint.As2DPoint();
+ }
+ // Emit only unique points
+ if (vertCount == 0 || p != aVerts[vertCount - 1]) {
+ aVerts[vertCount++] = p;
+ }
+ }
+
+ return vertCount;
+ }
+
+ static const int kTransformAndClipRectMaxVerts = 32;
+
+ static Matrix4x4Typed From2D(const BaseMatrix<T>& aMatrix) {
+ Matrix4x4Typed matrix;
+ matrix._11 = aMatrix._11;
+ matrix._12 = aMatrix._12;
+ matrix._21 = aMatrix._21;
+ matrix._22 = aMatrix._22;
+ matrix._41 = aMatrix._31;
+ matrix._42 = aMatrix._32;
+ return matrix;
+ }
+
+ bool Is2DIntegerTranslation() const {
+ return Is2D() && As2D().IsIntegerTranslation();
+ }
+
+ TargetPoint4D TransposeTransform4D(const SourcePoint4D& aPoint) const {
+ Float x = aPoint.x * _11 + aPoint.y * _12 + aPoint.z * _13 + aPoint.w * _14;
+ Float y = aPoint.x * _21 + aPoint.y * _22 + aPoint.z * _23 + aPoint.w * _24;
+ Float z = aPoint.x * _31 + aPoint.y * _32 + aPoint.z * _33 + aPoint.w * _34;
+ Float w = aPoint.x * _41 + aPoint.y * _42 + aPoint.z * _43 + aPoint.w * _44;
+
+ return TargetPoint4D(x, y, z, w);
+ }
+
+ template <class F>
+ Point4DTyped<TargetUnits, F> TransformPoint(
+ const Point4DTyped<SourceUnits, F>& aPoint) const {
+ Point4DTyped<TargetUnits, F> retPoint;
+
+ retPoint.x =
+ aPoint.x * _11 + aPoint.y * _21 + aPoint.z * _31 + aPoint.w * _41;
+ retPoint.y =
+ aPoint.x * _12 + aPoint.y * _22 + aPoint.z * _32 + aPoint.w * _42;
+ retPoint.z =
+ aPoint.x * _13 + aPoint.y * _23 + aPoint.z * _33 + aPoint.w * _43;
+ retPoint.w =
+ aPoint.x * _14 + aPoint.y * _24 + aPoint.z * _34 + aPoint.w * _44;
+
+ return retPoint;
+ }
+
+ template <class F>
+ Point3DTyped<TargetUnits, F> TransformPoint(
+ const Point3DTyped<SourceUnits, F>& aPoint) const {
+ Point3DTyped<TargetUnits, F> result;
+ result.x = aPoint.x * _11 + aPoint.y * _21 + aPoint.z * _31 + _41;
+ result.y = aPoint.x * _12 + aPoint.y * _22 + aPoint.z * _32 + _42;
+ result.z = aPoint.x * _13 + aPoint.y * _23 + aPoint.z * _33 + _43;
+
+ result /= (aPoint.x * _14 + aPoint.y * _24 + aPoint.z * _34 + _44);
+
+ return result;
+ }
+
+ template <class F>
+ PointTyped<TargetUnits, F> TransformPoint(
+ const PointTyped<SourceUnits, F>& aPoint) const {
+ Point4DTyped<SourceUnits, F> temp(aPoint.x, aPoint.y, 0, 1);
+ return TransformPoint(temp).As2DPoint();
+ }
+
+ template <class F>
+ GFX2D_API RectTyped<TargetUnits, F> TransformBounds(
+ const RectTyped<SourceUnits, F>& aRect) const {
+ PointTyped<TargetUnits, F> quad[4];
+ F min_x, max_x;
+ F 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 (int 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 RectTyped<TargetUnits, F>(min_x, min_y, max_x - min_x,
+ max_y - min_y);
+ }
+
+ static Matrix4x4Typed Translation(T aX, T aY, T aZ) {
+ return Matrix4x4Typed(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, aX, aY, aZ, 1.0f);
+ }
+
+ static Matrix4x4Typed Translation(const TargetPoint3D& aP) {
+ return Translation(aP.x, aP.y, aP.z);
+ }
+
+ static Matrix4x4Typed Translation(const TargetPoint& aP) {
+ return Translation(aP.x, aP.y, 0);
+ }
+
+ /**
+ * Apply a translation to this matrix.
+ *
+ * The "Pre" in this method's name means that the translation is applied
+ * -before- this matrix's existing transformation. That is, any vector that
+ * is multiplied by the resulting matrix will first be translated, then be
+ * transformed by the original transform.
+ *
+ * Calling this method will result in this matrix having the same value as
+ * the result of:
+ *
+ * Matrix4x4::Translation(x, y) * this
+ *
+ * (Note that in performance critical code multiplying by the result of a
+ * Translation()/Scaling() call is not recommended since that results in a
+ * full matrix multiply involving 64 floating-point multiplications. Calling
+ * this method would be preferred since it only involves 12 floating-point
+ * multiplications.)
+ */
+ Matrix4x4Typed& PreTranslate(T aX, T aY, T aZ) {
+ _41 += aX * _11 + aY * _21 + aZ * _31;
+ _42 += aX * _12 + aY * _22 + aZ * _32;
+ _43 += aX * _13 + aY * _23 + aZ * _33;
+ _44 += aX * _14 + aY * _24 + aZ * _34;
+
+ return *this;
+ }
+
+ Matrix4x4Typed& PreTranslate(const Point3DTyped<UnknownUnits, T>& aPoint) {
+ return PreTranslate(aPoint.x, aPoint.y, aPoint.z);
+ }
+
+ /**
+ * Similar to PreTranslate, but the translation is applied -after- this
+ * matrix's existing transformation instead of before it.
+ *
+ * This method is generally less used than PreTranslate since typically code
+ * wants to adjust an existing user space to device space matrix to create a
+ * transform to device space from a -new- user space (translated from the
+ * previous user space). In that case consumers will need to use the Pre*
+ * variants of the matrix methods rather than using the Post* methods, since
+ * the Post* methods add a transform to the device space end of the
+ * transformation.
+ */
+ Matrix4x4Typed& PostTranslate(T aX, T aY, T aZ) {
+ _11 += _14 * aX;
+ _21 += _24 * aX;
+ _31 += _34 * aX;
+ _41 += _44 * aX;
+ _12 += _14 * aY;
+ _22 += _24 * aY;
+ _32 += _34 * aY;
+ _42 += _44 * aY;
+ _13 += _14 * aZ;
+ _23 += _24 * aZ;
+ _33 += _34 * aZ;
+ _43 += _44 * aZ;
+
+ return *this;
+ }
+
+ Matrix4x4Typed& PostTranslate(const TargetPoint3D& aPoint) {
+ return PostTranslate(aPoint.x, aPoint.y, aPoint.z);
+ }
+
+ Matrix4x4Typed& PostTranslate(const TargetPoint& aPoint) {
+ return PostTranslate(aPoint.x, aPoint.y, 0);
+ }
+
+ static Matrix4x4Typed Scaling(T aScaleX, T aScaleY, T aScaleZ) {
+ return Matrix4x4Typed(aScaleX, 0.0f, 0.0f, 0.0f, 0.0f, aScaleY, 0.0f, 0.0f,
+ 0.0f, 0.0f, aScaleZ, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+
+ /**
+ * Similar to PreTranslate, but applies a scale instead of a translation.
+ */
+ Matrix4x4Typed& PreScale(T aX, T aY, T aZ) {
+ _11 *= aX;
+ _12 *= aX;
+ _13 *= aX;
+ _14 *= aX;
+ _21 *= aY;
+ _22 *= aY;
+ _23 *= aY;
+ _24 *= aY;
+ _31 *= aZ;
+ _32 *= aZ;
+ _33 *= aZ;
+ _34 *= aZ;
+
+ return *this;
+ }
+
+ /**
+ * Similar to PostTranslate, but applies a scale instead of a translation.
+ */
+ Matrix4x4Typed& PostScale(T aScaleX, T aScaleY, T aScaleZ) {
+ _11 *= aScaleX;
+ _21 *= aScaleX;
+ _31 *= aScaleX;
+ _41 *= aScaleX;
+ _12 *= aScaleY;
+ _22 *= aScaleY;
+ _32 *= aScaleY;
+ _42 *= aScaleY;
+ _13 *= aScaleZ;
+ _23 *= aScaleZ;
+ _33 *= aScaleZ;
+ _43 *= aScaleZ;
+
+ return *this;
+ }
+
+ void SkewXY(T aSkew) { (*this)[1] += (*this)[0] * aSkew; }
+
+ void SkewXZ(T aSkew) { (*this)[2] += (*this)[0] * aSkew; }
+
+ void SkewYZ(T aSkew) { (*this)[2] += (*this)[1] * aSkew; }
+
+ Matrix4x4Typed& ChangeBasis(const Point3DTyped<UnknownUnits, T>& aOrigin) {
+ return ChangeBasis(aOrigin.x, aOrigin.y, aOrigin.z);
+ }
+
+ Matrix4x4Typed& ChangeBasis(T aX, T aY, T aZ) {
+ // Translate to the origin before applying this matrix
+ PreTranslate(-aX, -aY, -aZ);
+
+ // Translate back into position after applying this matrix
+ PostTranslate(aX, aY, aZ);
+
+ return *this;
+ }
+
+ Matrix4x4Typed& Transpose() {
+ std::swap(_12, _21);
+ std::swap(_13, _31);
+ std::swap(_14, _41);
+
+ std::swap(_23, _32);
+ std::swap(_24, _42);
+
+ std::swap(_34, _43);
+
+ return *this;
+ }
+
+ bool operator==(const Matrix4x4Typed& o) const {
+ // XXX would be nice to memcmp here, but that breaks IEEE 754 semantics
+ return _11 == o._11 && _12 == o._12 && _13 == o._13 && _14 == o._14 &&
+ _21 == o._21 && _22 == o._22 && _23 == o._23 && _24 == o._24 &&
+ _31 == o._31 && _32 == o._32 && _33 == o._33 && _34 == o._34 &&
+ _41 == o._41 && _42 == o._42 && _43 == o._43 && _44 == o._44;
+ }
+
+ bool operator!=(const Matrix4x4Typed& o) const { return !((*this) == o); }
+
+ Matrix4x4Typed& operator=(const Matrix4x4Typed& aOther) = default;
+
+ template <typename NewTargetUnits>
+ Matrix4x4Typed<SourceUnits, NewTargetUnits, T> operator*(
+ const Matrix4x4Typed<TargetUnits, NewTargetUnits, T>& aMatrix) const {
+ Matrix4x4Typed<SourceUnits, NewTargetUnits, T> matrix;
+
+ matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21 + _13 * aMatrix._31 +
+ _14 * aMatrix._41;
+ matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21 + _23 * aMatrix._31 +
+ _24 * aMatrix._41;
+ matrix._31 = _31 * aMatrix._11 + _32 * aMatrix._21 + _33 * aMatrix._31 +
+ _34 * aMatrix._41;
+ matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + _43 * aMatrix._31 +
+ _44 * aMatrix._41;
+ matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22 + _13 * aMatrix._32 +
+ _14 * aMatrix._42;
+ matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22 + _23 * aMatrix._32 +
+ _24 * aMatrix._42;
+ matrix._32 = _31 * aMatrix._12 + _32 * aMatrix._22 + _33 * aMatrix._32 +
+ _34 * aMatrix._42;
+ matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + _43 * aMatrix._32 +
+ _44 * aMatrix._42;
+ matrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23 + _13 * aMatrix._33 +
+ _14 * aMatrix._43;
+ matrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23 + _23 * aMatrix._33 +
+ _24 * aMatrix._43;
+ matrix._33 = _31 * aMatrix._13 + _32 * aMatrix._23 + _33 * aMatrix._33 +
+ _34 * aMatrix._43;
+ matrix._43 = _41 * aMatrix._13 + _42 * aMatrix._23 + _43 * aMatrix._33 +
+ _44 * aMatrix._43;
+ matrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24 + _13 * aMatrix._34 +
+ _14 * aMatrix._44;
+ matrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24 + _23 * aMatrix._34 +
+ _24 * aMatrix._44;
+ matrix._34 = _31 * aMatrix._14 + _32 * aMatrix._24 + _33 * aMatrix._34 +
+ _34 * aMatrix._44;
+ matrix._44 = _41 * aMatrix._14 + _42 * aMatrix._24 + _43 * aMatrix._34 +
+ _44 * aMatrix._44;
+
+ return matrix;
+ }
+
+ Matrix4x4Typed& operator*=(
+ const Matrix4x4Typed<TargetUnits, TargetUnits, T>& aMatrix) {
+ *this = *this * aMatrix;
+ return *this;
+ }
+
+ /* Returns true if the matrix is an identity matrix.
+ */
+ bool IsIdentity() const {
+ return _11 == 1.0f && _12 == 0.0f && _13 == 0.0f && _14 == 0.0f &&
+ _21 == 0.0f && _22 == 1.0f && _23 == 0.0f && _24 == 0.0f &&
+ _31 == 0.0f && _32 == 0.0f && _33 == 1.0f && _34 == 0.0f &&
+ _41 == 0.0f && _42 == 0.0f && _43 == 0.0f && _44 == 1.0f;
+ }
+
+ bool IsSingular() const { return Determinant() == 0.0; }
+
+ T Determinant() const {
+ return _14 * _23 * _32 * _41 - _13 * _24 * _32 * _41 -
+ _14 * _22 * _33 * _41 + _12 * _24 * _33 * _41 +
+ _13 * _22 * _34 * _41 - _12 * _23 * _34 * _41 -
+ _14 * _23 * _31 * _42 + _13 * _24 * _31 * _42 +
+ _14 * _21 * _33 * _42 - _11 * _24 * _33 * _42 -
+ _13 * _21 * _34 * _42 + _11 * _23 * _34 * _42 +
+ _14 * _22 * _31 * _43 - _12 * _24 * _31 * _43 -
+ _14 * _21 * _32 * _43 + _11 * _24 * _32 * _43 +
+ _12 * _21 * _34 * _43 - _11 * _22 * _34 * _43 -
+ _13 * _22 * _31 * _44 + _12 * _23 * _31 * _44 +
+ _13 * _21 * _32 * _44 - _11 * _23 * _32 * _44 -
+ _12 * _21 * _33 * _44 + _11 * _22 * _33 * _44;
+ }
+
+ // Invert() is not unit-correct. Prefer Inverse() where possible.
+ bool Invert() {
+ T det = Determinant();
+ if (!det) {
+ return false;
+ }
+
+ Matrix4x4Typed<SourceUnits, TargetUnits, T> result;
+ result._11 = _23 * _34 * _42 - _24 * _33 * _42 + _24 * _32 * _43 -
+ _22 * _34 * _43 - _23 * _32 * _44 + _22 * _33 * _44;
+ result._12 = _14 * _33 * _42 - _13 * _34 * _42 - _14 * _32 * _43 +
+ _12 * _34 * _43 + _13 * _32 * _44 - _12 * _33 * _44;
+ result._13 = _13 * _24 * _42 - _14 * _23 * _42 + _14 * _22 * _43 -
+ _12 * _24 * _43 - _13 * _22 * _44 + _12 * _23 * _44;
+ result._14 = _14 * _23 * _32 - _13 * _24 * _32 - _14 * _22 * _33 +
+ _12 * _24 * _33 + _13 * _22 * _34 - _12 * _23 * _34;
+ result._21 = _24 * _33 * _41 - _23 * _34 * _41 - _24 * _31 * _43 +
+ _21 * _34 * _43 + _23 * _31 * _44 - _21 * _33 * _44;
+ result._22 = _13 * _34 * _41 - _14 * _33 * _41 + _14 * _31 * _43 -
+ _11 * _34 * _43 - _13 * _31 * _44 + _11 * _33 * _44;
+ result._23 = _14 * _23 * _41 - _13 * _24 * _41 - _14 * _21 * _43 +
+ _11 * _24 * _43 + _13 * _21 * _44 - _11 * _23 * _44;
+ result._24 = _13 * _24 * _31 - _14 * _23 * _31 + _14 * _21 * _33 -
+ _11 * _24 * _33 - _13 * _21 * _34 + _11 * _23 * _34;
+ result._31 = _22 * _34 * _41 - _24 * _32 * _41 + _24 * _31 * _42 -
+ _21 * _34 * _42 - _22 * _31 * _44 + _21 * _32 * _44;
+ result._32 = _14 * _32 * _41 - _12 * _34 * _41 - _14 * _31 * _42 +
+ _11 * _34 * _42 + _12 * _31 * _44 - _11 * _32 * _44;
+ result._33 = _12 * _24 * _41 - _14 * _22 * _41 + _14 * _21 * _42 -
+ _11 * _24 * _42 - _12 * _21 * _44 + _11 * _22 * _44;
+ result._34 = _14 * _22 * _31 - _12 * _24 * _31 - _14 * _21 * _32 +
+ _11 * _24 * _32 + _12 * _21 * _34 - _11 * _22 * _34;
+ result._41 = _23 * _32 * _41 - _22 * _33 * _41 - _23 * _31 * _42 +
+ _21 * _33 * _42 + _22 * _31 * _43 - _21 * _32 * _43;
+ result._42 = _12 * _33 * _41 - _13 * _32 * _41 + _13 * _31 * _42 -
+ _11 * _33 * _42 - _12 * _31 * _43 + _11 * _32 * _43;
+ result._43 = _13 * _22 * _41 - _12 * _23 * _41 - _13 * _21 * _42 +
+ _11 * _23 * _42 + _12 * _21 * _43 - _11 * _22 * _43;
+ result._44 = _12 * _23 * _31 - _13 * _22 * _31 + _13 * _21 * _32 -
+ _11 * _23 * _32 - _12 * _21 * _33 + _11 * _22 * _33;
+
+ result._11 /= det;
+ result._12 /= det;
+ result._13 /= det;
+ result._14 /= det;
+ result._21 /= det;
+ result._22 /= det;
+ result._23 /= det;
+ result._24 /= det;
+ result._31 /= det;
+ result._32 /= det;
+ result._33 /= det;
+ result._34 /= det;
+ result._41 /= det;
+ result._42 /= det;
+ result._43 /= det;
+ result._44 /= det;
+ *this = result;
+
+ return true;
+ }
+
+ Matrix4x4Typed<TargetUnits, SourceUnits, T> Inverse() const {
+ typedef Matrix4x4Typed<TargetUnits, SourceUnits, T> InvertedMatrix;
+ InvertedMatrix clone = InvertedMatrix::FromUnknownMatrix(ToUnknownMatrix());
+ DebugOnly<bool> inverted = clone.Invert();
+ MOZ_ASSERT(inverted,
+ "Attempted to get the inverse of a non-invertible matrix");
+ return clone;
+ }
+
+ Maybe<Matrix4x4Typed<TargetUnits, SourceUnits, T>> MaybeInverse() const {
+ typedef Matrix4x4Typed<TargetUnits, SourceUnits, T> InvertedMatrix;
+ InvertedMatrix clone = InvertedMatrix::FromUnknownMatrix(ToUnknownMatrix());
+ if (clone.Invert()) {
+ return Some(clone);
+ }
+ return Nothing();
+ }
+
+ void Normalize() {
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ (*this)[i][j] /= (*this)[3][3];
+ }
+ }
+ }
+
+ bool FuzzyEqual(const Matrix4x4Typed& o) const {
+ return gfx::FuzzyEqual(_11, o._11) && gfx::FuzzyEqual(_12, o._12) &&
+ gfx::FuzzyEqual(_13, o._13) && gfx::FuzzyEqual(_14, o._14) &&
+ gfx::FuzzyEqual(_21, o._21) && gfx::FuzzyEqual(_22, o._22) &&
+ gfx::FuzzyEqual(_23, o._23) && gfx::FuzzyEqual(_24, o._24) &&
+ gfx::FuzzyEqual(_31, o._31) && gfx::FuzzyEqual(_32, o._32) &&
+ gfx::FuzzyEqual(_33, o._33) && gfx::FuzzyEqual(_34, o._34) &&
+ gfx::FuzzyEqual(_41, o._41) && gfx::FuzzyEqual(_42, o._42) &&
+ gfx::FuzzyEqual(_43, o._43) && gfx::FuzzyEqual(_44, o._44);
+ }
+
+ bool FuzzyEqualsMultiplicative(const Matrix4x4Typed& o) const {
+ return ::mozilla::FuzzyEqualsMultiplicative(_11, o._11) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_12, o._12) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_13, o._13) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_14, o._14) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_21, o._21) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_22, o._22) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_23, o._23) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_24, o._24) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_31, o._31) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_32, o._32) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_33, o._33) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_34, o._34) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_41, o._41) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_42, o._42) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_43, o._43) &&
+ ::mozilla::FuzzyEqualsMultiplicative(_44, o._44);
+ }
+
+ bool IsBackfaceVisible() const {
+ // Inverse()._33 < 0;
+ T det = Determinant();
+ T __33 = _12 * _24 * _41 - _14 * _22 * _41 + _14 * _21 * _42 -
+ _11 * _24 * _42 - _12 * _21 * _44 + _11 * _22 * _44;
+ return (__33 * det) < 0;
+ }
+
+ Matrix4x4Typed& NudgeToIntegersFixedEpsilon() {
+ NudgeToInteger(&_11);
+ NudgeToInteger(&_12);
+ NudgeToInteger(&_13);
+ NudgeToInteger(&_14);
+ NudgeToInteger(&_21);
+ NudgeToInteger(&_22);
+ NudgeToInteger(&_23);
+ NudgeToInteger(&_24);
+ NudgeToInteger(&_31);
+ NudgeToInteger(&_32);
+ NudgeToInteger(&_33);
+ NudgeToInteger(&_34);
+ static const float error = 1e-5f;
+ NudgeToInteger(&_41, error);
+ NudgeToInteger(&_42, error);
+ NudgeToInteger(&_43, error);
+ NudgeToInteger(&_44, error);
+ return *this;
+ }
+
+ Point4D TransposedVector(int aIndex) const {
+ MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index");
+ return Point4DTyped<UnknownUnits, T>(*((&_11) + aIndex), *((&_21) + aIndex),
+ *((&_31) + aIndex),
+ *((&_41) + aIndex));
+ }
+
+ void SetTransposedVector(int aIndex, Point4DTyped<UnknownUnits, T>& aVector) {
+ MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index");
+ *((&_11) + aIndex) = aVector.x;
+ *((&_21) + aIndex) = aVector.y;
+ *((&_31) + aIndex) = aVector.z;
+ *((&_41) + aIndex) = aVector.w;
+ }
+
+ bool Decompose(Point3DTyped<UnknownUnits, T>& translation,
+ BaseQuaternion<T>& rotation,
+ Point3DTyped<UnknownUnits, T>& scale) const {
+ // Ensure matrix can be normalized
+ if (gfx::FuzzyEqual(_44, 0.0f)) {
+ return false;
+ }
+ Matrix4x4Typed mat = *this;
+ mat.Normalize();
+ if (HasPerspectiveComponent()) {
+ // We do not support projection matrices
+ return false;
+ }
+
+ // Extract translation
+ translation.x = mat._41;
+ translation.y = mat._42;
+ translation.z = mat._43;
+
+ // Remove translation
+ mat._41 = 0.0f;
+ mat._42 = 0.0f;
+ mat._43 = 0.0f;
+
+ // Extract scale
+ scale.x = sqrtf(_11 * _11 + _21 * _21 + _31 * _31);
+ scale.y = sqrtf(_12 * _12 + _22 * _22 + _32 * _32);
+ scale.z = sqrtf(_13 * _13 + _23 * _23 + _33 * _33);
+
+ // Remove scale
+ if (gfx::FuzzyEqual(scale.x, 0.0f) || gfx::FuzzyEqual(scale.y, 0.0f) ||
+ gfx::FuzzyEqual(scale.z, 0.0f)) {
+ // We do not support matrices with a zero scale component
+ return false;
+ }
+
+ // Extract rotation
+ rotation.SetFromRotationMatrix(this->ToUnknownMatrix());
+ return true;
+ }
+
+ // Sets this matrix to a rotation matrix given by aQuat.
+ // This quaternion *MUST* be normalized!
+ // Implemented in Quaternion.cpp
+ void SetRotationFromQuaternion(const BaseQuaternion<T>& q) {
+ const T x2 = q.x + q.x, y2 = q.y + q.y, z2 = q.z + q.z;
+ const T xx = q.x * x2, xy = q.x * y2, xz = q.x * z2;
+ const T yy = q.y * y2, yz = q.y * z2, zz = q.z * z2;
+ const T wx = q.w * x2, wy = q.w * y2, wz = q.w * z2;
+
+ _11 = 1.0f - (yy + zz);
+ _21 = xy - wz;
+ _31 = xz + wy;
+ _41 = 0.0f;
+
+ _12 = xy + wz;
+ _22 = 1.0f - (xx + zz);
+ _32 = yz - wx;
+ _42 = 0.0f;
+
+ _13 = xz - wy;
+ _23 = yz + wx;
+ _33 = 1.0f - (xx + yy);
+ _43 = 0.0f;
+
+ _14 = _42 = _43 = 0.0f;
+ _44 = 1.0f;
+ }
+
+ // Set all the members of the matrix to NaN
+ void SetNAN() {
+ _11 = UnspecifiedNaN<T>();
+ _21 = UnspecifiedNaN<T>();
+ _31 = UnspecifiedNaN<T>();
+ _41 = UnspecifiedNaN<T>();
+ _12 = UnspecifiedNaN<T>();
+ _22 = UnspecifiedNaN<T>();
+ _32 = UnspecifiedNaN<T>();
+ _42 = UnspecifiedNaN<T>();
+ _13 = UnspecifiedNaN<T>();
+ _23 = UnspecifiedNaN<T>();
+ _33 = UnspecifiedNaN<T>();
+ _43 = UnspecifiedNaN<T>();
+ _14 = UnspecifiedNaN<T>();
+ _24 = UnspecifiedNaN<T>();
+ _34 = UnspecifiedNaN<T>();
+ _44 = UnspecifiedNaN<T>();
+ }
+
+ // Verifies that the matrix contains no Infs or NaNs
+ bool IsFinite() const {
+ return std::isfinite(_11) && std::isfinite(_12) && std::isfinite(_13) &&
+ std::isfinite(_14) && std::isfinite(_21) && std::isfinite(_22) &&
+ std::isfinite(_23) && std::isfinite(_24) && std::isfinite(_31) &&
+ std::isfinite(_32) && std::isfinite(_33) && std::isfinite(_34) &&
+ std::isfinite(_41) && std::isfinite(_42) && std::isfinite(_43) &&
+ std::isfinite(_44);
+ }
+
+ void SkewXY(double aXSkew, double aYSkew) {
+ // XXX Is double precision really necessary here
+ T tanX = SafeTangent(aXSkew);
+ T tanY = SafeTangent(aYSkew);
+ T temp;
+
+ temp = _11;
+ _11 += tanY * _21;
+ _21 += tanX * temp;
+
+ temp = _12;
+ _12 += tanY * _22;
+ _22 += tanX * temp;
+
+ temp = _13;
+ _13 += tanY * _23;
+ _23 += tanX * temp;
+
+ temp = _14;
+ _14 += tanY * _24;
+ _24 += tanX * temp;
+ }
+
+ void RotateX(double aTheta) {
+ // XXX Is double precision really necessary here
+ double cosTheta = FlushToZero(cos(aTheta));
+ double sinTheta = FlushToZero(sin(aTheta));
+
+ T temp;
+
+ temp = _21;
+ _21 = cosTheta * _21 + sinTheta * _31;
+ _31 = -sinTheta * temp + cosTheta * _31;
+
+ temp = _22;
+ _22 = cosTheta * _22 + sinTheta * _32;
+ _32 = -sinTheta * temp + cosTheta * _32;
+
+ temp = _23;
+ _23 = cosTheta * _23 + sinTheta * _33;
+ _33 = -sinTheta * temp + cosTheta * _33;
+
+ temp = _24;
+ _24 = cosTheta * _24 + sinTheta * _34;
+ _34 = -sinTheta * temp + cosTheta * _34;
+ }
+
+ void RotateY(double aTheta) {
+ // XXX Is double precision really necessary here
+ double cosTheta = FlushToZero(cos(aTheta));
+ double sinTheta = FlushToZero(sin(aTheta));
+
+ T temp;
+
+ temp = _11;
+ _11 = cosTheta * _11 + -sinTheta * _31;
+ _31 = sinTheta * temp + cosTheta * _31;
+
+ temp = _12;
+ _12 = cosTheta * _12 + -sinTheta * _32;
+ _32 = sinTheta * temp + cosTheta * _32;
+
+ temp = _13;
+ _13 = cosTheta * _13 + -sinTheta * _33;
+ _33 = sinTheta * temp + cosTheta * _33;
+
+ temp = _14;
+ _14 = cosTheta * _14 + -sinTheta * _34;
+ _34 = sinTheta * temp + cosTheta * _34;
+ }
+
+ void RotateZ(double aTheta) {
+ // XXX Is double precision really necessary here
+ double cosTheta = FlushToZero(cos(aTheta));
+ double sinTheta = FlushToZero(sin(aTheta));
+
+ T temp;
+
+ temp = _11;
+ _11 = cosTheta * _11 + sinTheta * _21;
+ _21 = -sinTheta * temp + cosTheta * _21;
+
+ temp = _12;
+ _12 = cosTheta * _12 + sinTheta * _22;
+ _22 = -sinTheta * temp + cosTheta * _22;
+
+ temp = _13;
+ _13 = cosTheta * _13 + sinTheta * _23;
+ _23 = -sinTheta * temp + cosTheta * _23;
+
+ temp = _14;
+ _14 = cosTheta * _14 + sinTheta * _24;
+ _24 = -sinTheta * temp + cosTheta * _24;
+ }
+
+ // Sets this matrix to a rotation matrix about a
+ // vector [x,y,z] by angle theta. The vector is normalized
+ // to a unit vector.
+ // https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined
+ void SetRotateAxisAngle(double aX, double aY, double aZ, double aTheta) {
+ Point3DTyped<UnknownUnits, T> vector(aX, aY, aZ);
+ if (!vector.Length()) {
+ return;
+ }
+ vector.RobustNormalize();
+
+ double x = vector.x;
+ double y = vector.y;
+ double z = vector.z;
+
+ double cosTheta = FlushToZero(cos(aTheta));
+ double sinTheta = FlushToZero(sin(aTheta));
+
+ // sin(aTheta / 2) * cos(aTheta / 2)
+ double sc = sinTheta / 2;
+ // pow(sin(aTheta / 2), 2)
+ double sq = (1 - cosTheta) / 2;
+
+ _11 = 1 - 2 * (y * y + z * z) * sq;
+ _12 = 2 * (x * y * sq + z * sc);
+ _13 = 2 * (x * z * sq - y * sc);
+ _14 = 0.0f;
+ _21 = 2 * (x * y * sq - z * sc);
+ _22 = 1 - 2 * (x * x + z * z) * sq;
+ _23 = 2 * (y * z * sq + x * sc);
+ _24 = 0.0f;
+ _31 = 2 * (x * z * sq + y * sc);
+ _32 = 2 * (y * z * sq - x * sc);
+ _33 = 1 - 2 * (x * x + y * y) * sq;
+ _34 = 0.0f;
+ _41 = 0.0f;
+ _42 = 0.0f;
+ _43 = 0.0f;
+ _44 = 1.0f;
+ }
+
+ void Perspective(T aDepth) {
+ MOZ_ASSERT(aDepth > 0.0f, "Perspective must be positive!");
+ _31 += -1.0 / aDepth * _41;
+ _32 += -1.0 / aDepth * _42;
+ _33 += -1.0 / aDepth * _43;
+ _34 += -1.0 / aDepth * _44;
+ }
+
+ Point3D GetNormalVector() const {
+ // Define a plane in transformed space as the transformations
+ // of 3 points on the z=0 screen plane.
+ Point3DTyped<UnknownUnits, T> a =
+ TransformPoint(Point3DTyped<UnknownUnits, T>(0, 0, 0));
+ Point3DTyped<UnknownUnits, T> b =
+ TransformPoint(Point3DTyped<UnknownUnits, T>(0, 1, 0));
+ Point3DTyped<UnknownUnits, T> c =
+ TransformPoint(Point3DTyped<UnknownUnits, T>(1, 0, 0));
+
+ // Convert to two vectors on the surface of the plane.
+ Point3DTyped<UnknownUnits, T> ab = b - a;
+ Point3DTyped<UnknownUnits, T> ac = c - a;
+
+ return ac.CrossProduct(ab);
+ }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a straight translation.
+ */
+ bool HasNonTranslation() const {
+ return !gfx::FuzzyEqual(_11, 1.0) || !gfx::FuzzyEqual(_22, 1.0) ||
+ !gfx::FuzzyEqual(_12, 0.0) || !gfx::FuzzyEqual(_21, 0.0) ||
+ !gfx::FuzzyEqual(_13, 0.0) || !gfx::FuzzyEqual(_23, 0.0) ||
+ !gfx::FuzzyEqual(_31, 0.0) || !gfx::FuzzyEqual(_32, 0.0) ||
+ !gfx::FuzzyEqual(_33, 1.0);
+ }
+
+ /**
+ * Returns true if the matrix is anything other than a straight
+ * translation by integers.
+ */
+ bool HasNonIntegerTranslation() const {
+ return HasNonTranslation() || !gfx::FuzzyEqual(_41, floor(_41 + 0.5)) ||
+ !gfx::FuzzyEqual(_42, floor(_42 + 0.5)) ||
+ !gfx::FuzzyEqual(_43, floor(_43 + 0.5));
+ }
+
+ /**
+ * Return true if the matrix is with perspective (w).
+ */
+ bool HasPerspectiveComponent() const {
+ return _14 != 0 || _24 != 0 || _34 != 0 || _44 != 1;
+ }
+
+ /* Returns true if the matrix is a rectilinear transformation (i.e.
+ * grid-aligned rectangles are transformed to grid-aligned rectangles).
+ * This should only be called on 2D matrices.
+ */
+ bool IsRectilinear() const {
+ MOZ_ASSERT(Is2D());
+ if (gfx::FuzzyEqual(_12, 0) && gfx::FuzzyEqual(_21, 0)) {
+ return true;
+ } else if (gfx::FuzzyEqual(_22, 0) && gfx::FuzzyEqual(_11, 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Convert between typed and untyped matrices.
+ */
+ using UnknownMatrix = Matrix4x4Typed<UnknownUnits, UnknownUnits, T>;
+ UnknownMatrix ToUnknownMatrix() const {
+ return UnknownMatrix{_11, _12, _13, _14, _21, _22, _23, _24,
+ _31, _32, _33, _34, _41, _42, _43, _44};
+ }
+ static Matrix4x4Typed FromUnknownMatrix(const UnknownMatrix& aUnknown) {
+ return Matrix4x4Typed{
+ aUnknown._11, aUnknown._12, aUnknown._13, aUnknown._14,
+ aUnknown._21, aUnknown._22, aUnknown._23, aUnknown._24,
+ aUnknown._31, aUnknown._32, aUnknown._33, aUnknown._34,
+ aUnknown._41, aUnknown._42, aUnknown._43, aUnknown._44};
+ }
+ /**
+ * For convenience, overload FromUnknownMatrix() for Maybe<Matrix>.
+ */
+ static Maybe<Matrix4x4Typed> FromUnknownMatrix(
+ const Maybe<UnknownMatrix>& aUnknown) {
+ if (aUnknown.isSome()) {
+ return Some(FromUnknownMatrix(*aUnknown));
+ }
+ return Nothing();
+ }
+};
+
+typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4;
+typedef Matrix4x4Typed<UnknownUnits, UnknownUnits, double> Matrix4x4Double;
+
+class Matrix5x4 {
+ public:
+ Matrix5x4()
+ : _11(1.0f),
+ _12(0),
+ _13(0),
+ _14(0),
+ _21(0),
+ _22(1.0f),
+ _23(0),
+ _24(0),
+ _31(0),
+ _32(0),
+ _33(1.0f),
+ _34(0),
+ _41(0),
+ _42(0),
+ _43(0),
+ _44(1.0f),
+ _51(0),
+ _52(0),
+ _53(0),
+ _54(0) {}
+ Matrix5x4(Float a11, Float a12, Float a13, Float a14, Float a21, Float a22,
+ Float a23, Float a24, Float a31, Float a32, Float a33, Float a34,
+ Float a41, Float a42, Float a43, Float a44, Float a51, Float a52,
+ Float a53, Float a54)
+ : _11(a11),
+ _12(a12),
+ _13(a13),
+ _14(a14),
+ _21(a21),
+ _22(a22),
+ _23(a23),
+ _24(a24),
+ _31(a31),
+ _32(a32),
+ _33(a33),
+ _34(a34),
+ _41(a41),
+ _42(a42),
+ _43(a43),
+ _44(a44),
+ _51(a51),
+ _52(a52),
+ _53(a53),
+ _54(a54) {}
+
+ bool operator==(const Matrix5x4& o) const {
+ return _11 == o._11 && _12 == o._12 && _13 == o._13 && _14 == o._14 &&
+ _21 == o._21 && _22 == o._22 && _23 == o._23 && _24 == o._24 &&
+ _31 == o._31 && _32 == o._32 && _33 == o._33 && _34 == o._34 &&
+ _41 == o._41 && _42 == o._42 && _43 == o._43 && _44 == o._44 &&
+ _51 == o._51 && _52 == o._52 && _53 == o._53 && _54 == o._54;
+ }
+
+ bool operator!=(const Matrix5x4& aMatrix) const {
+ return !(*this == aMatrix);
+ }
+
+ Matrix5x4 operator*(const Matrix5x4& aMatrix) const {
+ Matrix5x4 resultMatrix;
+
+ resultMatrix._11 = this->_11 * aMatrix._11 + this->_12 * aMatrix._21 +
+ this->_13 * aMatrix._31 + this->_14 * aMatrix._41;
+ resultMatrix._12 = this->_11 * aMatrix._12 + this->_12 * aMatrix._22 +
+ this->_13 * aMatrix._32 + this->_14 * aMatrix._42;
+ resultMatrix._13 = this->_11 * aMatrix._13 + this->_12 * aMatrix._23 +
+ this->_13 * aMatrix._33 + this->_14 * aMatrix._43;
+ resultMatrix._14 = this->_11 * aMatrix._14 + this->_12 * aMatrix._24 +
+ this->_13 * aMatrix._34 + this->_14 * aMatrix._44;
+ resultMatrix._21 = this->_21 * aMatrix._11 + this->_22 * aMatrix._21 +
+ this->_23 * aMatrix._31 + this->_24 * aMatrix._41;
+ resultMatrix._22 = this->_21 * aMatrix._12 + this->_22 * aMatrix._22 +
+ this->_23 * aMatrix._32 + this->_24 * aMatrix._42;
+ resultMatrix._23 = this->_21 * aMatrix._13 + this->_22 * aMatrix._23 +
+ this->_23 * aMatrix._33 + this->_24 * aMatrix._43;
+ resultMatrix._24 = this->_21 * aMatrix._14 + this->_22 * aMatrix._24 +
+ this->_23 * aMatrix._34 + this->_24 * aMatrix._44;
+ resultMatrix._31 = this->_31 * aMatrix._11 + this->_32 * aMatrix._21 +
+ this->_33 * aMatrix._31 + this->_34 * aMatrix._41;
+ resultMatrix._32 = this->_31 * aMatrix._12 + this->_32 * aMatrix._22 +
+ this->_33 * aMatrix._32 + this->_34 * aMatrix._42;
+ resultMatrix._33 = this->_31 * aMatrix._13 + this->_32 * aMatrix._23 +
+ this->_33 * aMatrix._33 + this->_34 * aMatrix._43;
+ resultMatrix._34 = this->_31 * aMatrix._14 + this->_32 * aMatrix._24 +
+ this->_33 * aMatrix._34 + this->_34 * aMatrix._44;
+ resultMatrix._41 = this->_41 * aMatrix._11 + this->_42 * aMatrix._21 +
+ this->_43 * aMatrix._31 + this->_44 * aMatrix._41;
+ resultMatrix._42 = this->_41 * aMatrix._12 + this->_42 * aMatrix._22 +
+ this->_43 * aMatrix._32 + this->_44 * aMatrix._42;
+ resultMatrix._43 = this->_41 * aMatrix._13 + this->_42 * aMatrix._23 +
+ this->_43 * aMatrix._33 + this->_44 * aMatrix._43;
+ resultMatrix._44 = this->_41 * aMatrix._14 + this->_42 * aMatrix._24 +
+ this->_43 * aMatrix._34 + this->_44 * aMatrix._44;
+ resultMatrix._51 = this->_51 * aMatrix._11 + this->_52 * aMatrix._21 +
+ this->_53 * aMatrix._31 + this->_54 * aMatrix._41 +
+ aMatrix._51;
+ resultMatrix._52 = this->_51 * aMatrix._12 + this->_52 * aMatrix._22 +
+ this->_53 * aMatrix._32 + this->_54 * aMatrix._42 +
+ aMatrix._52;
+ resultMatrix._53 = this->_51 * aMatrix._13 + this->_52 * aMatrix._23 +
+ this->_53 * aMatrix._33 + this->_54 * aMatrix._43 +
+ aMatrix._53;
+ resultMatrix._54 = this->_51 * aMatrix._14 + this->_52 * aMatrix._24 +
+ this->_53 * aMatrix._34 + this->_54 * aMatrix._44 +
+ aMatrix._54;
+
+ return resultMatrix;
+ }
+
+ Matrix5x4& operator*=(const Matrix5x4& aMatrix) {
+ *this = *this * aMatrix;
+ return *this;
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Matrix5x4& aMatrix) {
+ const Float* f = &aMatrix._11;
+ aStream << "[ " << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';';
+ f += 4;
+ aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3]
+ << "; ]";
+ return aStream;
+ }
+
+ union {
+ struct {
+ Float _11, _12, _13, _14;
+ Float _21, _22, _23, _24;
+ Float _31, _32, _33, _34;
+ Float _41, _42, _43, _44;
+ Float _51, _52, _53, _54;
+ };
+ Float components[20];
+ };
+};
+
+/* This Matrix class will carry one additional type field in order to
+ * track what type of 4x4 matrix we're dealing with, it can then execute
+ * simplified versions of certain operations when applicable.
+ * This does not allow access to the parent class directly, as a caller
+ * could then mutate the parent class without updating the type.
+ */
+template <typename SourceUnits, typename TargetUnits>
+class Matrix4x4TypedFlagged
+ : protected Matrix4x4Typed<SourceUnits, TargetUnits> {
+ public:
+ using Parent = Matrix4x4Typed<SourceUnits, TargetUnits>;
+ using TargetPoint = PointTyped<TargetUnits>;
+ using Parent::_11;
+ using Parent::_12;
+ using Parent::_13;
+ using Parent::_14;
+ using Parent::_21;
+ using Parent::_22;
+ using Parent::_23;
+ using Parent::_24;
+ using Parent::_31;
+ using Parent::_32;
+ using Parent::_33;
+ using Parent::_34;
+ using Parent::_41;
+ using Parent::_42;
+ using Parent::_43;
+ using Parent::_44;
+
+ Matrix4x4TypedFlagged() : mType(MatrixType::Identity) {}
+
+ Matrix4x4TypedFlagged(Float a11, Float a12, Float a13, Float a14, Float a21,
+ Float a22, Float a23, Float a24, Float a31, Float a32,
+ Float a33, Float a34, Float a41, Float a42, Float a43,
+ Float a44)
+ : Parent(a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41,
+ a42, a43, a44) {
+ Analyze();
+ }
+
+ MOZ_IMPLICIT Matrix4x4TypedFlagged(const Parent& aOther) : Parent(aOther) {
+ Analyze();
+ }
+
+ template <class F>
+ PointTyped<TargetUnits, F> TransformPoint(
+ const PointTyped<SourceUnits, F>& aPoint) const {
+ if (mType == MatrixType::Identity) {
+ return aPoint;
+ }
+
+ if (mType == MatrixType::Simple) {
+ return TransformPointSimple(aPoint);
+ }
+
+ return Parent::TransformPoint(aPoint);
+ }
+
+ template <class F>
+ RectTyped<TargetUnits, F> TransformAndClipBounds(
+ const RectTyped<SourceUnits, F>& aRect,
+ const RectTyped<TargetUnits, F>& aClip) const {
+ if (mType == MatrixType::Identity) {
+ const RectTyped<SourceUnits, F>& clipped = aRect.Intersect(aClip);
+ return RectTyped<TargetUnits, F>(clipped.X(), clipped.Y(),
+ clipped.Width(), clipped.Height());
+ }
+
+ if (mType == MatrixType::Simple) {
+ PointTyped<UnknownUnits, F> p1 = TransformPointSimple(aRect.TopLeft());
+ PointTyped<UnknownUnits, F> p2 = TransformPointSimple(aRect.TopRight());
+ PointTyped<UnknownUnits, F> p3 = TransformPointSimple(aRect.BottomLeft());
+ PointTyped<UnknownUnits, F> p4 =
+ TransformPointSimple(aRect.BottomRight());
+
+ F min_x = std::min(std::min(std::min(p1.x, p2.x), p3.x), p4.x);
+ F max_x = std::max(std::max(std::max(p1.x, p2.x), p3.x), p4.x);
+ F min_y = std::min(std::min(std::min(p1.y, p2.y), p3.y), p4.y);
+ F max_y = std::max(std::max(std::max(p1.y, p2.y), p3.y), p4.y);
+
+ TargetPoint topLeft(std::max(min_x, aClip.x), std::max(min_y, aClip.y));
+ F width = std::min(max_x, aClip.XMost()) - topLeft.x;
+ F height = std::min(max_y, aClip.YMost()) - topLeft.y;
+
+ return RectTyped<TargetUnits, F>(topLeft.x, topLeft.y, width, height);
+ }
+ return Parent::TransformAndClipBounds(aRect, aClip);
+ }
+
+ bool FuzzyEqual(const Parent& o) const { return Parent::FuzzyEqual(o); }
+
+ bool FuzzyEqual(const Matrix4x4TypedFlagged& o) const {
+ if (mType == MatrixType::Identity && o.mType == MatrixType::Identity) {
+ return true;
+ }
+ return Parent::FuzzyEqual(o);
+ }
+
+ Matrix4x4TypedFlagged& PreTranslate(Float aX, Float aY, Float aZ) {
+ if (mType == MatrixType::Identity) {
+ _41 = aX;
+ _42 = aY;
+ _43 = aZ;
+
+ if (!aZ) {
+ mType = MatrixType::Simple;
+ return *this;
+ }
+ mType = MatrixType::Full;
+ return *this;
+ }
+
+ Parent::PreTranslate(aX, aY, aZ);
+
+ if (aZ != 0) {
+ mType = MatrixType::Full;
+ }
+
+ return *this;
+ }
+
+ Matrix4x4TypedFlagged& PostTranslate(Float aX, Float aY, Float aZ) {
+ if (mType == MatrixType::Identity) {
+ _41 = aX;
+ _42 = aY;
+ _43 = aZ;
+
+ if (!aZ) {
+ mType = MatrixType::Simple;
+ return *this;
+ }
+ mType = MatrixType::Full;
+ return *this;
+ }
+
+ Parent::PostTranslate(aX, aY, aZ);
+
+ if (aZ != 0) {
+ mType = MatrixType::Full;
+ }
+
+ return *this;
+ }
+
+ Matrix4x4TypedFlagged& ChangeBasis(Float aX, Float aY, Float aZ) {
+ // Translate to the origin before applying this matrix
+ PreTranslate(-aX, -aY, -aZ);
+
+ // Translate back into position after applying this matrix
+ PostTranslate(aX, aY, aZ);
+
+ return *this;
+ }
+
+ bool IsIdentity() const { return mType == MatrixType::Identity; }
+
+ template <class F>
+ Point4DTyped<TargetUnits, F> ProjectPoint(
+ const PointTyped<SourceUnits, F>& aPoint) const {
+ if (mType == MatrixType::Identity) {
+ return Point4DTyped<TargetUnits, F>(aPoint.x, aPoint.y, 0, 1);
+ }
+
+ if (mType == MatrixType::Simple) {
+ TargetPoint point = TransformPointSimple(aPoint);
+ return Point4DTyped<TargetUnits, F>(point.x, point.y, 0, 1);
+ }
+
+ return Parent::ProjectPoint(aPoint);
+ }
+
+ Matrix4x4TypedFlagged& ProjectTo2D() {
+ if (mType == MatrixType::Full) {
+ Parent::ProjectTo2D();
+ }
+ return *this;
+ }
+
+ bool IsSingular() const {
+ if (mType == MatrixType::Identity) {
+ return false;
+ }
+ return Parent::Determinant() == 0.0;
+ }
+
+ bool Invert() {
+ if (mType == MatrixType::Identity) {
+ return true;
+ }
+
+ return Parent::Invert();
+ }
+
+ Matrix4x4TypedFlagged<TargetUnits, SourceUnits> Inverse() const {
+ typedef Matrix4x4TypedFlagged<TargetUnits, SourceUnits> InvertedMatrix;
+ InvertedMatrix clone = InvertedMatrix::FromUnknownMatrix(ToUnknownMatrix());
+ if (mType == MatrixType::Identity) {
+ return clone;
+ }
+ DebugOnly<bool> inverted = clone.Invert();
+ MOZ_ASSERT(inverted,
+ "Attempted to get the inverse of a non-invertible matrix");
+
+ // Inverting a 2D Matrix should result in a 2D matrix, ergo mType doesn't
+ // change.
+ return clone;
+ }
+
+ template <typename NewTargetUnits>
+ bool operator==(
+ const Matrix4x4TypedFlagged<TargetUnits, NewTargetUnits>& aMatrix) const {
+ if (mType == MatrixType::Identity &&
+ aMatrix.mType == MatrixType::Identity) {
+ return true;
+ }
+ // Depending on the usage it may make sense to compare more flags.
+ return Parent::operator==(aMatrix);
+ }
+
+ template <typename NewTargetUnits>
+ bool operator!=(
+ const Matrix4x4TypedFlagged<TargetUnits, NewTargetUnits>& aMatrix) const {
+ if (mType == MatrixType::Identity &&
+ aMatrix.mType == MatrixType::Identity) {
+ return false;
+ }
+ // Depending on the usage it may make sense to compare more flags.
+ return Parent::operator!=(aMatrix);
+ }
+
+ template <typename NewTargetUnits>
+ Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> operator*(
+ const Matrix4x4Typed<TargetUnits, NewTargetUnits>& aMatrix) const {
+ if (mType == MatrixType::Identity) {
+ return aMatrix;
+ }
+
+ if (mType == MatrixType::Simple) {
+ Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix;
+ matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21;
+ matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21;
+ matrix._31 = aMatrix._31;
+ matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + aMatrix._41;
+ matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22;
+ matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22;
+ matrix._32 = aMatrix._32;
+ matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + aMatrix._42;
+ matrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23;
+ matrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23;
+ matrix._33 = aMatrix._33;
+ matrix._43 = _41 * aMatrix._13 + _42 * aMatrix._23 + aMatrix._43;
+ matrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24;
+ matrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24;
+ matrix._34 = aMatrix._34;
+ matrix._44 = _41 * aMatrix._14 + _42 * aMatrix._24 + aMatrix._44;
+ matrix.Analyze();
+ return matrix;
+ }
+
+ return Parent::operator*(aMatrix);
+ }
+
+ template <typename NewTargetUnits>
+ Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> operator*(
+ const Matrix4x4TypedFlagged<TargetUnits, NewTargetUnits>& aMatrix) const {
+ if (mType == MatrixType::Identity) {
+ return aMatrix;
+ }
+
+ if (aMatrix.mType == MatrixType::Identity) {
+ return Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits>::
+ FromUnknownMatrix(this->ToUnknownMatrix());
+ }
+
+ if (mType == MatrixType::Simple && aMatrix.mType == MatrixType::Simple) {
+ Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix;
+ matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21;
+ matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21;
+ matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + aMatrix._41;
+ matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22;
+ matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22;
+ matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + aMatrix._42;
+ matrix.mType = MatrixType::Simple;
+ return matrix;
+ } else if (mType == MatrixType::Simple) {
+ Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix;
+ matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21;
+ matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21;
+ matrix._31 = aMatrix._31;
+ matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + aMatrix._41;
+ matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22;
+ matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22;
+ matrix._32 = aMatrix._32;
+ matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + aMatrix._42;
+ matrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23;
+ matrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23;
+ matrix._33 = aMatrix._33;
+ matrix._43 = _41 * aMatrix._13 + _42 * aMatrix._23 + aMatrix._43;
+ matrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24;
+ matrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24;
+ matrix._34 = aMatrix._34;
+ matrix._44 = _41 * aMatrix._14 + _42 * aMatrix._24 + aMatrix._44;
+ matrix.mType = MatrixType::Full;
+ return matrix;
+ } else if (aMatrix.mType == MatrixType::Simple) {
+ Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix;
+ matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21 + _14 * aMatrix._41;
+ matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21 + _24 * aMatrix._41;
+ matrix._31 = _31 * aMatrix._11 + _32 * aMatrix._21 + _34 * aMatrix._41;
+ matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + _44 * aMatrix._41;
+ matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22 + _14 * aMatrix._42;
+ matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22 + _24 * aMatrix._42;
+ matrix._32 = _31 * aMatrix._12 + _32 * aMatrix._22 + _34 * aMatrix._42;
+ matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + _44 * aMatrix._42;
+ matrix._13 = _13;
+ matrix._23 = _23;
+ matrix._33 = _33;
+ matrix._43 = _43;
+ matrix._14 = _14;
+ matrix._24 = _24;
+ matrix._34 = _34;
+ matrix._44 = _44;
+ matrix.mType = MatrixType::Full;
+ return matrix;
+ }
+
+ return Parent::operator*(aMatrix);
+ }
+
+ bool Is2D() const { return mType != MatrixType::Full; }
+
+ bool CanDraw2D(Matrix* aMatrix = nullptr) const {
+ if (mType != MatrixType::Full) {
+ if (aMatrix) {
+ aMatrix->_11 = _11;
+ aMatrix->_12 = _12;
+ aMatrix->_21 = _21;
+ aMatrix->_22 = _22;
+ aMatrix->_31 = _41;
+ aMatrix->_32 = _42;
+ }
+ return true;
+ }
+ return Parent::CanDraw2D(aMatrix);
+ }
+
+ bool Is2D(Matrix* aMatrix) const {
+ if (!Is2D()) {
+ return false;
+ }
+ if (aMatrix) {
+ aMatrix->_11 = _11;
+ aMatrix->_12 = _12;
+ aMatrix->_21 = _21;
+ aMatrix->_22 = _22;
+ aMatrix->_31 = _41;
+ aMatrix->_32 = _42;
+ }
+ return true;
+ }
+
+ template <class F>
+ RectTyped<TargetUnits, F> ProjectRectBounds(
+ const RectTyped<SourceUnits, F>& aRect,
+ const RectTyped<TargetUnits, F>& aClip) const {
+ return Parent::ProjectRectBounds(aRect, aClip);
+ }
+
+ const Parent& GetMatrix() const { return *this; }
+
+ private:
+ enum class MatrixType : uint8_t {
+ Identity,
+ Simple, // 2x3 Matrix
+ Full // 4x4 Matrix
+ };
+
+ Matrix4x4TypedFlagged(Float a11, Float a12, Float a13, Float a14, Float a21,
+ Float a22, Float a23, Float a24, Float a31, Float a32,
+ Float a33, Float a34, Float a41, Float a42, Float a43,
+ Float a44,
+ typename Matrix4x4TypedFlagged::MatrixType aType)
+ : Parent(a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41,
+ a42, a43, a44) {
+ mType = aType;
+ }
+ static Matrix4x4TypedFlagged FromUnknownMatrix(
+ const Matrix4x4Flagged& aUnknown) {
+ return Matrix4x4TypedFlagged{
+ aUnknown._11, aUnknown._12, aUnknown._13, aUnknown._14, aUnknown._21,
+ aUnknown._22, aUnknown._23, aUnknown._24, aUnknown._31, aUnknown._32,
+ aUnknown._33, aUnknown._34, aUnknown._41, aUnknown._42, aUnknown._43,
+ aUnknown._44, aUnknown.mType};
+ }
+ Matrix4x4Flagged ToUnknownMatrix() const {
+ return Matrix4x4Flagged{_11, _12, _13, _14, _21, _22, _23, _24, _31,
+ _32, _33, _34, _41, _42, _43, _44, mType};
+ }
+
+ template <class F>
+ PointTyped<TargetUnits, F> TransformPointSimple(
+ const PointTyped<SourceUnits, F>& aPoint) const {
+ PointTyped<SourceUnits, F> temp;
+ temp.x = aPoint.x * _11 + aPoint.y * +_21 + _41;
+ temp.y = aPoint.x * _12 + aPoint.y * +_22 + _42;
+ return temp;
+ }
+
+ void Analyze() {
+ if (Parent::IsIdentity()) {
+ mType = MatrixType::Identity;
+ return;
+ }
+
+ if (Parent::Is2D()) {
+ mType = MatrixType::Simple;
+ return;
+ }
+
+ mType = MatrixType::Full;
+ }
+
+ MatrixType mType;
+};
+
+using Matrix4x4Flagged = Matrix4x4TypedFlagged<UnknownUnits, UnknownUnits>;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_MATRIX_H_ */
diff --git a/gfx/2d/MatrixFwd.h b/gfx/2d/MatrixFwd.h
new file mode 100644
index 0000000000..494aadb887
--- /dev/null
+++ b/gfx/2d/MatrixFwd.h
@@ -0,0 +1,39 @@
+/* -*- 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_MATRIX_FWD_H_
+#define MOZILLA_GFX_MATRIX_FWD_H_
+
+// Forward declare enough things to define the typedefs |Matrix| and
+// |Matrix4x4|.
+
+namespace mozilla {
+namespace gfx {
+
+template <class T>
+class BaseMatrix;
+
+typedef float Float;
+typedef BaseMatrix<Float> Matrix;
+
+typedef double Double;
+typedef BaseMatrix<Double> MatrixDouble;
+
+struct UnknownUnits;
+
+template <class SourceUnits, class TargetUnits, class T = Float>
+class Matrix4x4Typed;
+template <class SourceUnits, class TargetUnits>
+class Matrix4x4TypedFlagged;
+
+typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4;
+typedef Matrix4x4Typed<UnknownUnits, UnknownUnits, double> Matrix4x4Double;
+typedef Matrix4x4TypedFlagged<UnknownUnits, UnknownUnits> Matrix4x4Flagged;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/NativeFontResource.cpp b/gfx/2d/NativeFontResource.cpp
new file mode 100644
index 0000000000..1c6a9fe0af
--- /dev/null
+++ b/gfx/2d/NativeFontResource.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "2D.h"
+#include "nsIMemoryReporter.h"
+
+namespace mozilla {
+namespace gfx {
+
+static Atomic<size_t> gTotalNativeFontResourceData;
+
+NativeFontResource::NativeFontResource(size_t aDataLength)
+ : mDataLength(aDataLength) {
+ gTotalNativeFontResourceData += mDataLength;
+}
+
+NativeFontResource::~NativeFontResource() {
+ gTotalNativeFontResourceData -= mDataLength;
+}
+
+// Memory reporter that estimates the amount of memory that is currently being
+// allocated internally by various native font APIs for native font resources.
+// The sanest way to do this, given that NativeFontResources can be created and
+// used in many different threads or processes and given that such memory is
+// implicitly allocated by the native APIs, is just to maintain a global atomic
+// counter and report this value as such.
+class NativeFontResourceDataMemoryReporter final : public nsIMemoryReporter {
+ ~NativeFontResourceDataMemoryReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ MOZ_COLLECT_REPORT("explicit/gfx/native-font-resource-data", KIND_HEAP,
+ UNITS_BYTES, gTotalNativeFontResourceData,
+ "Total memory used by native font API resource data.");
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(NativeFontResourceDataMemoryReporter, nsIMemoryReporter)
+
+void NativeFontResource::RegisterMemoryReporter() {
+ RegisterStrongMemoryReporter(new NativeFontResourceDataMemoryReporter);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/NativeFontResourceDWrite.cpp b/gfx/2d/NativeFontResourceDWrite.cpp
new file mode 100644
index 0000000000..e0b599fa76
--- /dev/null
+++ b/gfx/2d/NativeFontResourceDWrite.cpp
@@ -0,0 +1,269 @@
+/* -*- 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 "UnscaledFontDWrite.h"
+
+#include <unordered_map>
+
+#include "Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticMutex.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gfx {
+
+static StaticMutex sFontFileStreamsMutex MOZ_UNANNOTATED;
+static uint64_t sNextFontFileKey = 0;
+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();
+ Factory::GetDWriteFactory()->RegisterFontFileLoader(mInstance);
+ }
+ return mInstance;
+ }
+
+ private:
+ static IDWriteFontFileLoader* mInstance;
+};
+
+class DWriteFontFileStream final : public IDWriteFontFileStream {
+ public:
+ explicit DWriteFontFileStream(uint64_t aFontFileKey);
+
+ /**
+ * 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
+ */
+ bool Initialize(uint8_t* aData, uint32_t aSize);
+
+ // 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)() { return ++mRefCnt; }
+
+ IFACEMETHOD_(ULONG, Release)() {
+ uint32_t count = --mRefCnt;
+ if (count == 0) {
+ // Avoid locking unless necessary. Verify the refcount hasn't changed
+ // while locked. Delete within the scope of the lock when zero.
+ StaticMutexAutoLock lock(sFontFileStreamsMutex);
+ if (0 != mRefCnt) {
+ return mRefCnt;
+ }
+ delete this;
+ }
+ return count;
+ }
+
+ // 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:
+ nsTArray<uint8_t> mData;
+ Atomic<uint32_t> mRefCnt;
+ uint64_t mFontFileKey;
+
+ ~DWriteFontFileStream();
+};
+
+IDWriteFontFileLoader* DWriteFontFileLoader::mInstance = nullptr;
+
+HRESULT STDMETHODCALLTYPE DWriteFontFileLoader::CreateStreamFromKey(
+ const void* fontFileReferenceKey, UINT32 fontFileReferenceKeySize,
+ IDWriteFontFileStream** fontFileStream) {
+ if (!fontFileReferenceKey || !fontFileStream) {
+ return E_POINTER;
+ }
+
+ StaticMutexAutoLock lock(sFontFileStreamsMutex);
+ 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(uint64_t aFontFileKey)
+ : mRefCnt(0), mFontFileKey(aFontFileKey) {}
+
+DWriteFontFileStream::~DWriteFontFileStream() {
+ sFontFileStreams.erase(mFontFileKey);
+}
+
+bool DWriteFontFileStream::Initialize(uint8_t* aData, uint32_t aSize) {
+ if (!mData.SetLength(aSize, fallible)) {
+ return false;
+ }
+ memcpy(mData.Elements(), aData, aSize);
+ return true;
+}
+
+HRESULT STDMETHODCALLTYPE DWriteFontFileStream::GetFileSize(UINT64* fileSize) {
+ *fileSize = mData.Length();
+ 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.Length()) {
+ 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) {
+ RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
+ if (!factory) {
+ gfxWarning() << "Failed to get DWrite Factory.";
+ return nullptr;
+ }
+
+ sFontFileStreamsMutex.Lock();
+ uint64_t fontFileKey = sNextFontFileKey++;
+ RefPtr<DWriteFontFileStream> ffsRef = new DWriteFontFileStream(fontFileKey);
+ if (!ffsRef->Initialize(aFontData, aDataLength)) {
+ sFontFileStreamsMutex.Unlock();
+ gfxWarning() << "Failed to create DWriteFontFileStream.";
+ return nullptr;
+ }
+ sFontFileStreams[fontFileKey] = ffsRef;
+ sFontFileStreamsMutex.Unlock();
+
+ 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(), ffsRef.forget(),
+ faceType, numberOfFaces, aDataLength);
+ return fontResource.forget();
+}
+
+already_AddRefed<UnscaledFont> NativeFontResourceDWrite::CreateUnscaledFont(
+ uint32_t aIndex, 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<UnscaledFont> unscaledFont = new UnscaledFontDWrite(fontFace, nullptr);
+
+ return unscaledFont.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/NativeFontResourceDWrite.h b/gfx/2d/NativeFontResourceDWrite.h
new file mode 100644
index 0000000000..0f3feccb18
--- /dev/null
+++ b/gfx/2d/NativeFontResourceDWrite.h
@@ -0,0 +1,60 @@
+/* -*- 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, override)
+
+ /**
+ * 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.
+ * @return Referenced NativeFontResourceDWrite or nullptr if invalid.
+ */
+ static already_AddRefed<NativeFontResourceDWrite> Create(
+ uint8_t* aFontData, uint32_t aDataLength);
+
+ already_AddRefed<UnscaledFont> CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) final;
+
+ private:
+ NativeFontResourceDWrite(
+ IDWriteFactory* aFactory, already_AddRefed<IDWriteFontFile> aFontFile,
+ already_AddRefed<IDWriteFontFileStream> aFontFileStream,
+ DWRITE_FONT_FACE_TYPE aFaceType, uint32_t aNumberOfFaces,
+ size_t aDataLength)
+ : NativeFontResource(aDataLength),
+ mFactory(aFactory),
+ mFontFile(aFontFile),
+ mFontFileStream(aFontFileStream),
+ mFaceType(aFaceType),
+ mNumberOfFaces(aNumberOfFaces) {}
+
+ IDWriteFactory* mFactory;
+ RefPtr<IDWriteFontFile> mFontFile;
+ RefPtr<IDWriteFontFileStream> mFontFileStream;
+ DWRITE_FONT_FACE_TYPE mFaceType;
+ uint32_t mNumberOfFaces;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_NativeFontResourceDWrite_h
diff --git a/gfx/2d/NativeFontResourceFreeType.cpp b/gfx/2d/NativeFontResourceFreeType.cpp
new file mode 100644
index 0000000000..e00dfafe21
--- /dev/null
+++ b/gfx/2d/NativeFontResourceFreeType.cpp
@@ -0,0 +1,92 @@
+/* -*- 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 "NativeFontResourceFreeType.h"
+#include "UnscaledFontFreeType.h"
+
+namespace mozilla::gfx {
+
+NativeFontResourceFreeType::NativeFontResourceFreeType(
+ UniquePtr<uint8_t[]>&& aFontData, uint32_t aDataLength,
+ FT_Library aFTLibrary)
+ : NativeFontResource(aDataLength),
+ mFontData(std::move(aFontData)),
+ mDataLength(aDataLength),
+ mFTLibrary(aFTLibrary) {}
+
+NativeFontResourceFreeType::~NativeFontResourceFreeType() = default;
+
+template <class T>
+already_AddRefed<T> NativeFontResourceFreeType::CreateInternal(
+ uint8_t* aFontData, uint32_t aDataLength, FT_Library aFTLibrary) {
+ if (!aFontData || !aDataLength) {
+ return nullptr;
+ }
+ UniquePtr<uint8_t[]> fontData(new (fallible) uint8_t[aDataLength]);
+ if (!fontData) {
+ return nullptr;
+ }
+ memcpy(fontData.get(), aFontData, aDataLength);
+
+ RefPtr<T> resource = new T(std::move(fontData), aDataLength, aFTLibrary);
+ return resource.forget();
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+already_AddRefed<NativeFontResourceFreeType> NativeFontResourceFreeType::Create(
+ uint8_t* aFontData, uint32_t aDataLength, FT_Library aFTLibrary) {
+ return CreateInternal<NativeFontResourceFreeType>(aFontData, aDataLength,
+ aFTLibrary);
+}
+
+already_AddRefed<UnscaledFont> NativeFontResourceFreeType::CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) {
+ if (RefPtr<SharedFTFace> face = CloneFace()) {
+ return MakeAndAddRef<UnscaledFontFreeType>(std::move(face));
+ }
+ return nullptr;
+}
+#endif
+
+already_AddRefed<SharedFTFace> NativeFontResourceFreeType::CloneFace(
+ int aFaceIndex) {
+ RefPtr<SharedFTFace> face = Factory::NewSharedFTFaceFromData(
+ mFTLibrary, mFontData.get(), mDataLength, aFaceIndex, this);
+ if (!face ||
+ (FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) != FT_Err_Ok &&
+ FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL) !=
+ FT_Err_Ok)) {
+ return nullptr;
+ }
+ return face.forget();
+}
+
+#ifdef MOZ_WIDGET_GTK
+NativeFontResourceFontconfig::NativeFontResourceFontconfig(
+ UniquePtr<uint8_t[]>&& aFontData, uint32_t aDataLength,
+ FT_Library aFTLibrary)
+ : NativeFontResourceFreeType(std::move(aFontData), aDataLength,
+ aFTLibrary) {}
+
+already_AddRefed<UnscaledFont> NativeFontResourceFontconfig::CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) {
+ if (RefPtr<SharedFTFace> face = CloneFace()) {
+ return MakeAndAddRef<UnscaledFontFontconfig>(std::move(face));
+ }
+ return nullptr;
+}
+
+already_AddRefed<NativeFontResourceFontconfig>
+NativeFontResourceFontconfig::Create(uint8_t* aFontData, uint32_t aDataLength,
+ FT_Library aFTLibrary) {
+ return CreateInternal<NativeFontResourceFontconfig>(aFontData, aDataLength,
+ aFTLibrary);
+}
+#endif
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/NativeFontResourceFreeType.h b/gfx/2d/NativeFontResourceFreeType.h
new file mode 100644
index 0000000000..381dfe0c0b
--- /dev/null
+++ b/gfx/2d/NativeFontResourceFreeType.h
@@ -0,0 +1,79 @@
+/* -*- 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_NativeFontResourceFreeType_h
+#define mozilla_gfx_NativeFontResourceFreeType_h
+
+#include "2D.h"
+
+#include <cairo-ft.h>
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace gfx {
+
+class NativeFontResourceFreeType
+ : public NativeFontResource,
+ public SharedFTFaceRefCountedData<NativeFontResourceFreeType> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceFreeType, override)
+
+#ifdef MOZ_WIDGET_ANDROID
+ static already_AddRefed<NativeFontResourceFreeType> Create(
+ uint8_t* aFontData, uint32_t aDataLength,
+ FT_Library aFTLibrary = nullptr);
+
+ already_AddRefed<UnscaledFont> CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) override;
+#endif
+
+ ~NativeFontResourceFreeType();
+
+ already_AddRefed<SharedFTFace> CloneFace(int aFaceIndex = 0) override;
+
+ protected:
+ NativeFontResourceFreeType(UniquePtr<uint8_t[]>&& aFontData,
+ uint32_t aDataLength,
+ FT_Library aFTLibrary = nullptr);
+
+ template <class T>
+ static already_AddRefed<T> CreateInternal(uint8_t* aFontData,
+ uint32_t aDataLength,
+ FT_Library aFTLibrary);
+
+ UniquePtr<uint8_t[]> mFontData;
+ uint32_t mDataLength;
+ FT_Library mFTLibrary;
+};
+
+#ifdef MOZ_WIDGET_GTK
+class NativeFontResourceFontconfig final : public NativeFontResourceFreeType {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceFontconfig,
+ override)
+
+ static already_AddRefed<NativeFontResourceFontconfig> Create(
+ uint8_t* aFontData, uint32_t aDataLength,
+ FT_Library aFTLibrary = nullptr);
+
+ already_AddRefed<UnscaledFont> CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) final;
+
+ private:
+ friend class NativeFontResourceFreeType;
+
+ NativeFontResourceFontconfig(UniquePtr<uint8_t[]>&& aFontData,
+ uint32_t aDataLength,
+ FT_Library aFTLibrary = nullptr);
+};
+#endif
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_NativeFontResourceFreeType_h
diff --git a/gfx/2d/NativeFontResourceGDI.cpp b/gfx/2d/NativeFontResourceGDI.cpp
new file mode 100644
index 0000000000..f4a365da7a
--- /dev/null
+++ b/gfx/2d/NativeFontResourceGDI.cpp
@@ -0,0 +1,52 @@
+/* -*- 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"
+#include "UnscaledFontGDI.h"
+
+namespace mozilla {
+namespace gfx {
+
+/* static */
+already_AddRefed<NativeFontResourceGDI> NativeFontResourceGDI::Create(
+ uint8_t* aFontData, uint32_t aDataLength) {
+ 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, aDataLength);
+
+ return fontResouce.forget();
+}
+
+NativeFontResourceGDI::~NativeFontResourceGDI() {
+ ::RemoveFontMemResourceEx(mFontResourceHandle);
+}
+
+already_AddRefed<UnscaledFont> NativeFontResourceGDI::CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) {
+ if (aInstanceDataLength < sizeof(LOGFONT)) {
+ gfxWarning() << "GDI unscaled font instance data is truncated.";
+ return nullptr;
+ }
+
+ const LOGFONT* logFont = reinterpret_cast<const LOGFONT*>(aInstanceData);
+ RefPtr<UnscaledFont> unscaledFont = new UnscaledFontGDI(*logFont);
+ return unscaledFont.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/NativeFontResourceGDI.h b/gfx/2d/NativeFontResourceGDI.h
new file mode 100644
index 0000000000..7f71e8d28d
--- /dev/null
+++ b/gfx/2d/NativeFontResourceGDI.h
@@ -0,0 +1,51 @@
+/* -*- 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, override)
+
+ /**
+ * 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.
+ * @return Referenced NativeFontResourceGDI or nullptr if invalid.
+ */
+ static already_AddRefed<NativeFontResourceGDI> Create(uint8_t* aFontData,
+ uint32_t aDataLength);
+
+ virtual ~NativeFontResourceGDI();
+
+ already_AddRefed<UnscaledFont> CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) final;
+
+ private:
+ explicit NativeFontResourceGDI(HANDLE aFontResourceHandle, size_t aDataLength)
+ : NativeFontResource(aDataLength),
+ mFontResourceHandle(aFontResourceHandle) {}
+
+ HANDLE mFontResourceHandle;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_NativeFontResourceGDI_h
diff --git a/gfx/2d/NativeFontResourceMac.cpp b/gfx/2d/NativeFontResourceMac.cpp
new file mode 100644
index 0000000000..a05310a142
--- /dev/null
+++ b/gfx/2d/NativeFontResourceMac.cpp
@@ -0,0 +1,174 @@
+/* -*- 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 <unordered_map>
+#include <unordered_set>
+#include "NativeFontResourceMac.h"
+#include "UnscaledFontMac.h"
+#include "Types.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/DataMutex.h"
+
+#ifdef MOZ_WIDGET_UIKIT
+# include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#include "nsIMemoryReporter.h"
+#include "nsCocoaFeatures.h"
+
+namespace mozilla {
+namespace gfx {
+
+#define FONT_NAME_MAX 32
+static StaticDataMutex<std::unordered_map<void*, nsAutoCStringN<FONT_NAME_MAX>>>
+ sWeakFontDataMap("WeakFonts");
+
+void FontDataDeallocate(void*, void* info) {
+ auto fontMap = sWeakFontDataMap.Lock();
+ fontMap->erase(info);
+ free(info);
+}
+
+class NativeFontResourceMacReporter final : public nsIMemoryReporter {
+ ~NativeFontResourceMacReporter() = default;
+
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ auto fontMap = sWeakFontDataMap.Lock();
+
+ nsAutoCString path("explicit/gfx/native-font-resource-mac/font(");
+
+ unsigned int unknownFontIndex = 0;
+ for (auto& i : *fontMap) {
+ nsAutoCString subPath(path);
+
+ if (aAnonymize) {
+ subPath.AppendPrintf("<anonymized-%p>", this);
+ } else {
+ if (i.second.Length()) {
+ subPath.AppendLiteral("psname=");
+ subPath.Append(i.second);
+ } else {
+ subPath.AppendPrintf("Unknown(%d)", unknownFontIndex);
+ }
+ }
+
+ size_t bytes = MallocSizeOf(i.first) + FONT_NAME_MAX;
+
+ subPath.Append(")");
+
+ aHandleReport->Callback(""_ns, subPath, KIND_HEAP, UNITS_BYTES, bytes,
+ "Memory used by this native font."_ns, aData);
+
+ unknownFontIndex++;
+ }
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(NativeFontResourceMacReporter, nsIMemoryReporter)
+
+void NativeFontResourceMac::RegisterMemoryReporter() {
+ RegisterStrongMemoryReporter(new NativeFontResourceMacReporter);
+}
+
+/* static */
+already_AddRefed<NativeFontResourceMac> NativeFontResourceMac::Create(
+ uint8_t* aFontData, uint32_t aDataLength) {
+ uint8_t* fontData = (uint8_t*)malloc(aDataLength);
+ if (!fontData) {
+ return nullptr;
+ }
+ memcpy(fontData, aFontData, aDataLength);
+ CFAllocatorContext context = {0, fontData, nullptr, nullptr,
+ nullptr, nullptr, nullptr, FontDataDeallocate,
+ nullptr};
+ CFAllocatorRef allocator = CFAllocatorCreate(kCFAllocatorDefault, &context);
+
+ // We create a CFDataRef here that we'l hold until we've determined that we
+ // have a valid font. If and only if we can create a font from the data,
+ // we'll store the font data in our map. Whether or not the font is valid,
+ // we'll later release this CFDataRef.
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, fontData,
+ aDataLength, allocator);
+ if (!data) {
+ free(fontData);
+ return nullptr;
+ }
+
+ CTFontDescriptorRef ctFontDesc =
+ CTFontManagerCreateFontDescriptorFromData(data);
+ if (!ctFontDesc) {
+ CFRelease(data);
+ return nullptr;
+ }
+
+ // creating the CGFontRef via the CTFont avoids the data being held alive
+ // in a cache.
+ CTFontRef ctFont = CTFontCreateWithFontDescriptor(ctFontDesc, 0, NULL);
+
+ // Creating the CGFont from the CTFont prevents the font data from being
+ // held in the TDescriptorSource cache. This appears to be true even
+ // if we later create a CTFont from the CGFont.
+ CGFontRef fontRef = CTFontCopyGraphicsFont(ctFont, NULL);
+ CFRelease(ctFont);
+
+ if (!fontRef) {
+ // Not a valid font; release the structures we've been holding.
+ CFRelease(data);
+ CFRelease(ctFontDesc);
+ return nullptr;
+ }
+
+ // Determine the font name and store it with the font data in the map.
+ nsAutoCStringN<FONT_NAME_MAX> fontName;
+
+ CFStringRef psname = CGFontCopyPostScriptName(fontRef);
+ if (psname) {
+ const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8);
+ if (cstr) {
+ fontName.Assign(cstr);
+ } else {
+ char buf[FONT_NAME_MAX];
+ if (CFStringGetCString(psname, buf, FONT_NAME_MAX,
+ kCFStringEncodingUTF8)) {
+ fontName.Assign(buf);
+ }
+ }
+ CFRelease(psname);
+ }
+
+ {
+ auto fontMap = sWeakFontDataMap.Lock();
+ void* key = (void*)fontData;
+ fontMap->insert({key, fontName});
+ }
+ // It's now safe to release our CFDataRef.
+ CFRelease(data);
+
+ // passes ownership of fontRef to the NativeFontResourceMac instance
+ RefPtr<NativeFontResourceMac> fontResource =
+ new NativeFontResourceMac(ctFontDesc, fontRef, aDataLength);
+
+ return fontResource.forget();
+}
+
+already_AddRefed<UnscaledFont> NativeFontResourceMac::CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) {
+ RefPtr<UnscaledFont> unscaledFont =
+ new UnscaledFontMac(mFontDescRef, mFontRef, true);
+
+ return unscaledFont.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/NativeFontResourceMac.h b/gfx/2d/NativeFontResourceMac.h
new file mode 100644
index 0000000000..97683a8a4f
--- /dev/null
+++ b/gfx/2d/NativeFontResourceMac.h
@@ -0,0 +1,49 @@
+/* -*- 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, override)
+
+ static already_AddRefed<NativeFontResourceMac> Create(uint8_t* aFontData,
+ uint32_t aDataLength);
+
+ already_AddRefed<UnscaledFont> CreateUnscaledFont(
+ uint32_t aIndex, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength) final;
+
+ ~NativeFontResourceMac() {
+ CFRelease(mFontDescRef);
+ CFRelease(mFontRef);
+ }
+
+ static void RegisterMemoryReporter();
+
+ private:
+ explicit NativeFontResourceMac(CTFontDescriptorRef aFontDescRef,
+ CGFontRef aFontRef, size_t aDataLength)
+ : NativeFontResource(aDataLength),
+ mFontDescRef(aFontDescRef),
+ mFontRef(aFontRef) {}
+
+ CTFontDescriptorRef mFontDescRef;
+ CGFontRef mFontRef;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_NativeFontResourceMac_h
diff --git a/gfx/2d/NumericTools.h b/gfx/2d/NumericTools.h
new file mode 100644
index 0000000000..d60cf1cc8d
--- /dev/null
+++ b/gfx/2d/NumericTools.h
@@ -0,0 +1,46 @@
+/* -*- 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_NUMERICTOOLS_H_
+#define MOZILLA_GFX_NUMERICTOOLS_H_
+
+#include <cstdint>
+
+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;
+}
+
+inline int32_t RoundToMultiple(int32_t x, int32_t aMultiplier) {
+ return RoundDownToMultiple(x + aMultiplier / 2, aMultiplier);
+}
+
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_NUMERICTOOLS_H_ */
diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp
new file mode 100644
index 0000000000..69b5680c24
--- /dev/null
+++ b/gfx/2d/Path.cpp
@@ -0,0 +1,552 @@
+/* -*- 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 "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() = default;
+ 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() = default;
+
+Path::~Path() = default;
+
+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);
+
+ mBeginPoint = 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(mBeginPoint);
+}
+
+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) {
+ if (aLength < mCursor.mLength) {
+ // If cursor is beyond the target length, reset to the beginning.
+ mCursor.Reset();
+ } else {
+ // Adjust aLength to account for the position where we'll start searching.
+ aLength -= mCursor.mLength;
+ }
+
+ while (mCursor.mIndex < mPathOps.size()) {
+ const auto& op = mPathOps[mCursor.mIndex];
+ if (op.mType == FlatPathOp::OP_MOVETO) {
+ if (Distance(mCursor.mCurrentPoint, op.mPoint) > 0.0f) {
+ mCursor.mLastPointSinceMove = mCursor.mCurrentPoint;
+ }
+ mCursor.mCurrentPoint = op.mPoint;
+ } else {
+ Float segmentLength = Distance(mCursor.mCurrentPoint, op.mPoint);
+
+ if (segmentLength) {
+ mCursor.mLastPointSinceMove = mCursor.mCurrentPoint;
+ if (segmentLength > aLength) {
+ Point currentVector = op.mPoint - mCursor.mCurrentPoint;
+ Point tangent = currentVector / segmentLength;
+ if (aTangent) {
+ *aTangent = tangent;
+ }
+ return mCursor.mCurrentPoint + tangent * aLength;
+ }
+ }
+
+ aLength -= segmentLength;
+ mCursor.mLength += segmentLength;
+ mCursor.mCurrentPoint = op.mPoint;
+ }
+ mCursor.mIndex++;
+ }
+
+ if (aTangent) {
+ Point currentVector = mCursor.mCurrentPoint - mCursor.mLastPointSinceMove;
+ if (auto h = hypotf(currentVector.x, currentVector.y)) {
+ *aTangent = currentVector / h;
+ } else {
+ *aTangent = Point();
+ }
+ }
+ return mCursor.mCurrentPoint;
+}
+
+// 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;
+ double currentTolerance = aTolerance;
+ 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(currentTolerance * std::abs(s3inv) / 3.);
+ currentTolerance *= 1 + aTolerance;
+ // Increase tolerance every iteration to prevent this loop from executing
+ // too many times. This approximates the length of large curves more
+ // roughly. In practice, aTolerance is the constant kFlatteningTolerance
+ // which has value 0.0001. With this value, it takes 6,932 splits to double
+ // currentTolerance (to 0.0002) and 23,028 splits to increase
+ // currentTolerance by an order of magnitude (to 0.001).
+ 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.) {
+ cp21 = aControlPoints.mCP3 - 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.
+ double s3 = cp41.x - cp41.y;
+
+ // Use the absolute value so that Min and Max will correspond with the
+ // minimum and maximum of the range.
+ if (s3 == 0) {
+ *aMin = -1.0;
+ *aMax = 2.0;
+ } else {
+ double r = CubicRoot(std::abs(aTolerance / s3));
+ *aMin = aT - r;
+ *aMax = aT + r;
+ }
+ 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;
+ }
+
+ 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;
+ }
+}
+
+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;
+ }
+ }
+}
+
+Rect Path::GetFastBounds(const Matrix& aTransform,
+ const StrokeOptions* aStrokeOptions) const {
+ return aStrokeOptions ? GetStrokedBounds(*aStrokeOptions, aTransform)
+ : GetBounds(aTransform);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/PathAnalysis.h b/gfx/2d/PathAnalysis.h
new file mode 100644
index 0000000000..5821b8839e
--- /dev/null
+++ b/gfx/2d/PathAnalysis.h
@@ -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 "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, override)
+
+ virtual void MoveTo(const Point& aPoint) override;
+ virtual void LineTo(const Point& aPoint) override;
+ virtual void BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) override;
+ virtual void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override;
+ virtual void Close() override;
+ virtual void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise = false) override;
+
+ virtual Point CurrentPoint() const override {
+ return mPathOps.empty() ? Point() : mPathOps[mPathOps.size() - 1].mPoint;
+ }
+
+ Float ComputeLength();
+ Point ComputePointAtLength(Float aLength, Point* aTangent);
+
+ private:
+ Float mCachedLength = 0.0f;
+ bool mCalculatedLength = false;
+
+ std::vector<FlatPathOp> mPathOps;
+
+ // Used to accelerate ComputePointAtLength for the common case of iterating
+ // forward along the path.
+ struct {
+ uint32_t mIndex = 0;
+ Float mLength = 0.0f;
+ Point mCurrentPoint;
+ Point mLastPointSinceMove;
+
+ void Reset() {
+ mIndex = 0;
+ mLength = 0.0f;
+ mCurrentPoint = Point();
+ mLastPointSinceMove = Point();
+ }
+ } mCursor;
+};
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/PathCairo.cpp b/gfx/2d/PathCairo.cpp
new file mode 100644
index 0000000000..6dec373f3c
--- /dev/null
+++ b/gfx/2d/PathCairo.cpp
@@ -0,0 +1,301 @@
+/* -*- 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 "PathCairo.h"
+#include <math.h>
+#include "DrawTargetCairo.h"
+#include "Logging.h"
+#include "PathHelpers.h"
+#include "HelpersCairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+already_AddRefed<PathBuilder> PathBuilderCairo::Create(FillRule aFillRule) {
+ return MakeAndAddRef<PathBuilderCairo>(aFillRule);
+}
+
+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);
+}
+
+already_AddRefed<Path> PathBuilderCairo::Finish() {
+ return MakeAndAddRef<PathCairo>(mFillRule, mPathData, mCurrentPoint,
+ mBeginPoint);
+}
+
+PathCairo::PathCairo(FillRule aFillRule,
+ std::vector<cairo_path_data_t>& aPathData,
+ const Point& aCurrentPoint, const Point& aBeginPoint)
+ : mFillRule(aFillRule),
+ mContainingContext(nullptr),
+ mCurrentPoint(aCurrentPoint),
+ mBeginPoint(aBeginPoint) {
+ 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;
+ builder->mBeginPoint = mBeginPoint;
+
+ 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);
+ builder->mBeginPoint = aTransform.TransformPoint(mBeginPoint);
+
+ 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.empty()) {
+ 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
diff --git a/gfx/2d/PathCairo.h b/gfx/2d/PathCairo.h
new file mode 100644
index 0000000000..540729b475
--- /dev/null
+++ b/gfx/2d/PathCairo.h
@@ -0,0 +1,97 @@
+/* -*- 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_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, override)
+
+ explicit PathBuilderCairo(FillRule aFillRule);
+
+ void MoveTo(const Point& aPoint) override;
+ void LineTo(const Point& aPoint) override;
+ void BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) override;
+ void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override;
+ void Close() override;
+ void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise = false) override;
+ already_AddRefed<Path> Finish() override;
+
+ BackendType GetBackendType() const override { return BackendType::CAIRO; }
+
+ static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
+
+ private: // data
+ friend class PathCairo;
+
+ FillRule mFillRule;
+ std::vector<cairo_path_data_t> mPathData;
+};
+
+class PathCairo : public Path {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCairo, override)
+
+ PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t>& aPathData,
+ const Point& aCurrentPoint, const Point& aBeginPoint);
+ explicit PathCairo(cairo_t* aContext);
+ virtual ~PathCairo();
+
+ BackendType GetBackendType() const override { return BackendType::CAIRO; }
+
+ already_AddRefed<PathBuilder> CopyToBuilder(
+ FillRule aFillRule) const override;
+ already_AddRefed<PathBuilder> TransformedCopyToBuilder(
+ const Matrix& aTransform, FillRule aFillRule) const override;
+
+ bool ContainsPoint(const Point& aPoint,
+ const Matrix& aTransform) const override;
+
+ bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
+ const Point& aPoint,
+ const Matrix& aTransform) const override;
+
+ Rect GetBounds(const Matrix& aTransform = Matrix()) const override;
+
+ Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform = Matrix()) const override;
+
+ void StreamToSink(PathSink* aSink) const override;
+
+ FillRule GetFillRule() const override { 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;
+ Point mBeginPoint;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PATH_CAIRO_H_ */
diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp
new file mode 100644
index 0000000000..d46006f650
--- /dev/null
+++ b/gfx/2d/PathD2D.cpp
@@ -0,0 +1,422 @@
+/* -*- 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 "PathD2D.h"
+#include "HelpersD2D.h"
+#include <math.h>
+#include "DrawTargetD2D1.h"
+#include "Logging.h"
+
+namespace mozilla {
+namespace gfx {
+
+already_AddRefed<PathBuilder> PathBuilderD2D::Create(FillRule aFillRule) {
+ RefPtr<ID2D1PathGeometry> path;
+ HRESULT hr =
+ DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path));
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to create Direct2D Path Geometry. Code: "
+ << hexa(hr);
+ return nullptr;
+ }
+
+ RefPtr<ID2D1GeometrySink> sink;
+ hr = path->Open(getter_AddRefs(sink));
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to access Direct2D Path Geometry. Code: "
+ << hexa(hr);
+ return nullptr;
+ }
+
+ if (aFillRule == FillRule::FILL_WINDING) {
+ sink->SetFillMode(D2D1_FILL_MODE_WINDING);
+ }
+
+ return MakeAndAddRef<PathBuilderD2D>(sink, path, aFillRule,
+ BackendType::DIRECT2D1_1);
+}
+
+// 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:
+ explicit 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;
+};
+
+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;
+}
+
+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 {
+ if (!aTransform.Determinant()) {
+ // If the transform is not invertible, then don't consider point inside.
+ return false;
+ }
+
+ 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 {
+ if (!aTransform.Determinant()) {
+ // If the transform is not invertible, then don't consider point inside.
+ return false;
+ }
+
+ 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;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/PathD2D.h b/gfx/2d/PathD2D.h
new file mode 100644
index 0000000000..3a46b489e1
--- /dev/null
+++ b/gfx/2d/PathD2D.h
@@ -0,0 +1,112 @@
+/* -*- 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_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, override)
+ 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 already_AddRefed<Path> Finish();
+
+ virtual BackendType GetBackendType() const { return mBackendType; }
+
+ ID2D1GeometrySink* GetSink() { return mSink; }
+
+ bool IsFigureActive() const { return mFigureActive; }
+
+ static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
+
+ private:
+ friend class PathD2D;
+
+ void EnsureActive(const Point& aPoint);
+
+ RefPtr<ID2D1GeometrySink> mSink;
+ RefPtr<ID2D1PathGeometry> mGeometry;
+
+ bool mFigureActive;
+ FillRule mFillRule;
+ BackendType mBackendType;
+};
+
+class PathD2D : public Path {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathD2D, override)
+ 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;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PATHD2D_H_ */
diff --git a/gfx/2d/PathHelpers.cpp b/gfx/2d/PathHelpers.cpp
new file mode 100644
index 0000000000..66fce104b3
--- /dev/null
+++ b/gfx/2d/PathHelpers.cpp
@@ -0,0 +1,290 @@
+/* -*- 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 "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,
+ const Maybe<Matrix>& aTransform) {
+ // 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) {
+ Point pt(aRect.X() + aRadii[eCornerTopLeft].width, aRect.Y());
+ if (aTransform) {
+ pt = aTransform->TransformPoint(pt);
+ }
+ aPathBuilder->MoveTo(pt);
+ } else {
+ Point pt(aRect.X() + aRect.Width() - aRadii[eCornerTopRight].width,
+ aRect.Y());
+ if (aTransform) {
+ pt = aTransform->TransformPoint(pt);
+ }
+ aPathBuilder->MoveTo(pt);
+ }
+
+ 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;
+
+ if (aTransform.isNothing()) {
+ aPathBuilder->LineTo(p0);
+ aPathBuilder->BezierTo(p1, p2, p3);
+ } else {
+ const Matrix& transform = *aTransform;
+ aPathBuilder->LineTo(transform.TransformPoint(p0));
+ aPathBuilder->BezierTo(transform.TransformPoint(p1),
+ transform.TransformPoint(p2),
+ transform.TransformPoint(p3));
+ }
+ } else {
+ if (aTransform.isNothing()) {
+ aPathBuilder->LineTo(pc);
+ } else {
+ aPathBuilder->LineTo(aTransform->TransformPoint(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);
+
+ // Even if the stroke only partially covers a pixel, it must still render to
+ // full pixels. Round up to compensate for this.
+ dx = ceil(dx);
+ dy = ceil(dy);
+
+ return Margin(dy, dx, dy, dx);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h
new file mode 100644
index 0000000000..22befa91db
--- /dev/null
+++ b/gfx/2d/PathHelpers.h
@@ -0,0 +1,390 @@
+/* -*- 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_PATHHELPERS_H_
+#define MOZILLA_GFX_PATHHELPERS_H_
+
+#include "2D.h"
+#include "UserData.h"
+
+#include <cmath>
+
+namespace mozilla {
+namespace gfx {
+
+struct PathOp {
+ ~PathOp() = default;
+
+ enum OpType {
+ OP_MOVETO = 0,
+ OP_LINETO,
+ OP_BEZIERTO,
+ OP_QUADRATICBEZIERTO,
+ OP_ARC,
+ OP_CLOSE
+ };
+
+ OpType mType;
+ Point mP1;
+#if (!defined(__GNUC__) || __GNUC__ >= 7) && defined(__clang__)
+ PathOp() {}
+
+ union {
+ struct {
+ Point mP2;
+ Point mP3;
+ };
+ struct {
+ float mRadius;
+ float mStartAngle;
+ float mEndAngle;
+ bool mAntiClockwise;
+ };
+ };
+#else
+ PathOp() = default;
+
+ Point mP2;
+ Point mP3;
+ float mRadius;
+ float mStartAngle;
+ float mEndAngle;
+ bool mAntiClockwise;
+#endif
+};
+
+const int32_t sPointCount[] = {1, 1, 3, 2, 0, 0};
+
+// 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, const Matrix& aTransform = Matrix()) {
+ 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);
+ transform *= aTransform;
+ 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();
+}
+
+/**
+ * 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, const Maybe<Matrix>& aTransform = Nothing());
+
+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_ */
diff --git a/gfx/2d/PathRecording.cpp b/gfx/2d/PathRecording.cpp
new file mode 100644
index 0000000000..a3aaea442a
--- /dev/null
+++ b/gfx/2d/PathRecording.cpp
@@ -0,0 +1,307 @@
+/* -*- 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 "PathRecording.h"
+#include "DrawEventRecorder.h"
+#include "RecordedEventImpl.h"
+
+namespace mozilla {
+namespace gfx {
+
+#define NEXT_PARAMS(_type) \
+ const _type params = *reinterpret_cast<const _type*>(nextByte); \
+ nextByte += sizeof(_type);
+
+bool PathOps::StreamToSink(PathSink& aPathSink) const {
+ if (mPathData.empty()) {
+ return true;
+ }
+
+ const uint8_t* nextByte = mPathData.data();
+ const uint8_t* end = nextByte + mPathData.size();
+ while (nextByte < end) {
+ const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+ nextByte += sizeof(OpType);
+ switch (opType) {
+ case OpType::OP_MOVETO: {
+ NEXT_PARAMS(Point)
+ aPathSink.MoveTo(params);
+ break;
+ }
+ case OpType::OP_LINETO: {
+ NEXT_PARAMS(Point)
+ aPathSink.LineTo(params);
+ break;
+ }
+ case OpType::OP_BEZIERTO: {
+ NEXT_PARAMS(ThreePoints)
+ aPathSink.BezierTo(params.p1, params.p2, params.p3);
+ break;
+ }
+ case OpType::OP_QUADRATICBEZIERTO: {
+ NEXT_PARAMS(TwoPoints)
+ aPathSink.QuadraticBezierTo(params.p1, params.p2);
+ break;
+ }
+ case OpType::OP_ARC: {
+ NEXT_PARAMS(ArcParams)
+ aPathSink.Arc(params.origin, params.radius, params.startAngle,
+ params.endAngle, params.antiClockwise);
+ break;
+ }
+ case OpType::OP_CLOSE:
+ aPathSink.Close();
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#define CHECKED_NEXT_PARAMS(_type) \
+ if (nextByte + sizeof(_type) > end) { \
+ return false; \
+ } \
+ NEXT_PARAMS(_type)
+
+bool PathOps::CheckedStreamToSink(PathSink& aPathSink) const {
+ if (mPathData.empty()) {
+ return true;
+ }
+
+ const uint8_t* nextByte = mPathData.data();
+ const uint8_t* end = nextByte + mPathData.size();
+ while (true) {
+ if (nextByte == end) {
+ break;
+ }
+
+ if (nextByte + sizeof(OpType) > end) {
+ return false;
+ }
+
+ const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+ nextByte += sizeof(OpType);
+ switch (opType) {
+ case OpType::OP_MOVETO: {
+ CHECKED_NEXT_PARAMS(Point)
+ aPathSink.MoveTo(params);
+ break;
+ }
+ case OpType::OP_LINETO: {
+ CHECKED_NEXT_PARAMS(Point)
+ aPathSink.LineTo(params);
+ break;
+ }
+ case OpType::OP_BEZIERTO: {
+ CHECKED_NEXT_PARAMS(ThreePoints)
+ aPathSink.BezierTo(params.p1, params.p2, params.p3);
+ break;
+ }
+ case OpType::OP_QUADRATICBEZIERTO: {
+ CHECKED_NEXT_PARAMS(TwoPoints)
+ aPathSink.QuadraticBezierTo(params.p1, params.p2);
+ break;
+ }
+ case OpType::OP_ARC: {
+ CHECKED_NEXT_PARAMS(ArcParams)
+ aPathSink.Arc(params.origin, params.radius, params.startAngle,
+ params.endAngle, params.antiClockwise);
+ break;
+ }
+ case OpType::OP_CLOSE:
+ aPathSink.Close();
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+#undef CHECKED_NEXT_PARAMS
+
+PathOps PathOps::TransformedCopy(const Matrix& aTransform) const {
+ PathOps newPathOps;
+ const uint8_t* nextByte = mPathData.data();
+ const uint8_t* end = nextByte + mPathData.size();
+ while (nextByte < end) {
+ const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+ nextByte += sizeof(OpType);
+ switch (opType) {
+ case OpType::OP_MOVETO: {
+ NEXT_PARAMS(Point)
+ newPathOps.MoveTo(aTransform.TransformPoint(params));
+ break;
+ }
+ case OpType::OP_LINETO: {
+ NEXT_PARAMS(Point)
+ newPathOps.LineTo(aTransform.TransformPoint(params));
+ break;
+ }
+ case OpType::OP_BEZIERTO: {
+ NEXT_PARAMS(ThreePoints)
+ newPathOps.BezierTo(aTransform.TransformPoint(params.p1),
+ aTransform.TransformPoint(params.p2),
+ aTransform.TransformPoint(params.p3));
+ break;
+ }
+ case OpType::OP_QUADRATICBEZIERTO: {
+ NEXT_PARAMS(TwoPoints)
+ newPathOps.QuadraticBezierTo(aTransform.TransformPoint(params.p1),
+ aTransform.TransformPoint(params.p2));
+ break;
+ }
+ case OpType::OP_ARC: {
+ NEXT_PARAMS(ArcParams)
+ ArcToBezier(&newPathOps, params.origin,
+ gfx::Size(params.radius, params.radius), params.startAngle,
+ params.endAngle, params.antiClockwise, 0.0f, aTransform);
+ break;
+ }
+ case OpType::OP_CLOSE:
+ newPathOps.Close();
+ break;
+ default:
+ MOZ_CRASH("We control mOpTypes, so this should never happen.");
+ }
+ }
+
+ return newPathOps;
+}
+
+#undef NEXT_PARAMS
+
+size_t PathOps::NumberOfOps() const {
+ size_t size = 0;
+ const uint8_t* nextByte = mPathData.data();
+ const uint8_t* end = nextByte + mPathData.size();
+ while (nextByte < end) {
+ size++;
+ const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
+ nextByte += sizeof(OpType);
+ switch (opType) {
+ case OpType::OP_MOVETO:
+ nextByte += sizeof(Point);
+ break;
+ case OpType::OP_LINETO:
+ nextByte += sizeof(Point);
+ break;
+ case OpType::OP_BEZIERTO:
+ nextByte += sizeof(ThreePoints);
+ break;
+ case OpType::OP_QUADRATICBEZIERTO:
+ nextByte += sizeof(TwoPoints);
+ break;
+ case OpType::OP_ARC:
+ nextByte += sizeof(ArcParams);
+ break;
+ case OpType::OP_CLOSE:
+ break;
+ default:
+ MOZ_CRASH("We control mOpTypes, so this should never happen.");
+ }
+ }
+
+ return size;
+}
+
+void PathBuilderRecording::MoveTo(const Point& aPoint) {
+ mPathOps.MoveTo(aPoint);
+ mBeginPoint = aPoint;
+ mCurrentPoint = aPoint;
+}
+
+void PathBuilderRecording::LineTo(const Point& aPoint) {
+ mPathOps.LineTo(aPoint);
+ mCurrentPoint = aPoint;
+}
+
+void PathBuilderRecording::BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) {
+ mPathOps.BezierTo(aCP1, aCP2, aCP3);
+ mCurrentPoint = aCP3;
+}
+
+void PathBuilderRecording::QuadraticBezierTo(const Point& aCP1,
+ const Point& aCP2) {
+ mPathOps.QuadraticBezierTo(aCP1, aCP2);
+ mCurrentPoint = aCP2;
+}
+
+void PathBuilderRecording::Close() {
+ mPathOps.Close();
+ mCurrentPoint = mBeginPoint;
+}
+
+void PathBuilderRecording::Arc(const Point& aOrigin, float aRadius,
+ float aStartAngle, float aEndAngle,
+ bool aAntiClockwise) {
+ mPathOps.Arc(aOrigin, aRadius, aStartAngle, aEndAngle, aAntiClockwise);
+
+ mCurrentPoint = aOrigin + Point(cosf(aEndAngle), sinf(aEndAngle)) * aRadius;
+}
+
+already_AddRefed<Path> PathBuilderRecording::Finish() {
+ return MakeAndAddRef<PathRecording>(mBackendType, std::move(mPathOps),
+ mFillRule, mBeginPoint, mCurrentPoint);
+}
+
+PathRecording::PathRecording(BackendType aBackend, PathOps&& aOps,
+ FillRule aFillRule, const Point& aCurrentPoint,
+ const Point& aBeginPoint)
+ : mBackendType(aBackend),
+ mPathOps(std::move(aOps)),
+ mFillRule(aFillRule),
+ mCurrentPoint(aCurrentPoint),
+ mBeginPoint(aBeginPoint) {}
+
+PathRecording::~PathRecording() {
+ for (size_t i = 0; i < mStoredRecorders.size(); i++) {
+ mStoredRecorders[i]->RemoveStoredObject(this);
+ mStoredRecorders[i]->RecordEvent(RecordedPathDestruction(this));
+ }
+}
+
+void PathRecording::EnsurePath() const {
+ if (mPath) {
+ return;
+ }
+ if (RefPtr<PathBuilder> pathBuilder =
+ Factory::CreatePathBuilder(mBackendType, mFillRule)) {
+ if (!mPathOps.StreamToSink(*pathBuilder)) {
+ MOZ_ASSERT(false, "Failed to stream PathOps to PathBuilder");
+ } else {
+ mPath = pathBuilder->Finish();
+ MOZ_ASSERT(!!mPath, "Failed finishing Path from PathBuilder");
+ }
+ } else {
+ MOZ_ASSERT(false, "Failed to create PathBuilder for PathRecording");
+ }
+}
+
+already_AddRefed<PathBuilder> PathRecording::CopyToBuilder(
+ FillRule aFillRule) const {
+ RefPtr<PathBuilderRecording> recording =
+ new PathBuilderRecording(mBackendType, PathOps(mPathOps), aFillRule);
+ recording->SetCurrentPoint(mCurrentPoint);
+ recording->SetBeginPoint(mBeginPoint);
+ return recording.forget();
+}
+
+already_AddRefed<PathBuilder> PathRecording::TransformedCopyToBuilder(
+ const Matrix& aTransform, FillRule aFillRule) const {
+ RefPtr<PathBuilderRecording> recording = new PathBuilderRecording(
+ mBackendType, mPathOps.TransformedCopy(aTransform), aFillRule);
+ recording->SetCurrentPoint(aTransform.TransformPoint(mCurrentPoint));
+ recording->SetBeginPoint(aTransform.TransformPoint(mBeginPoint));
+ return recording.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/PathRecording.h b/gfx/2d/PathRecording.h
new file mode 100644
index 0000000000..49629f9ee6
--- /dev/null
+++ b/gfx/2d/PathRecording.h
@@ -0,0 +1,238 @@
+/* -*- 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_PATHRECORDING_H_
+#define MOZILLA_GFX_PATHRECORDING_H_
+
+#include "2D.h"
+#include <vector>
+#include <ostream>
+
+#include "PathHelpers.h"
+#include "RecordingTypes.h"
+
+namespace mozilla {
+namespace gfx {
+
+class PathOps {
+ public:
+ PathOps() = default;
+
+ template <class S>
+ explicit PathOps(S& aStream);
+
+ PathOps(const PathOps& aOther) = default;
+ PathOps& operator=(const PathOps&) = delete; // assign using std::move()!
+
+ PathOps(PathOps&& aOther) = default;
+ PathOps& operator=(PathOps&& aOther) = default;
+
+ template <class S>
+ void Record(S& aStream) const;
+
+ bool StreamToSink(PathSink& aPathSink) const;
+
+ bool CheckedStreamToSink(PathSink& aPathSink) const;
+
+ PathOps TransformedCopy(const Matrix& aTransform) const;
+
+ size_t NumberOfOps() const;
+
+ void MoveTo(const Point& aPoint) { AppendPathOp(OpType::OP_MOVETO, aPoint); }
+
+ void LineTo(const Point& aPoint) { AppendPathOp(OpType::OP_LINETO, aPoint); }
+
+ void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) {
+ AppendPathOp(OpType::OP_BEZIERTO, ThreePoints{aCP1, aCP2, aCP3});
+ }
+
+ void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
+ AppendPathOp(OpType::OP_QUADRATICBEZIERTO, TwoPoints{aCP1, aCP2});
+ }
+
+ void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise) {
+ AppendPathOp(OpType::OP_ARC, ArcParams{aOrigin, aRadius, aStartAngle,
+ aEndAngle, aAntiClockwise});
+ }
+
+ void Close() {
+ size_t oldSize = mPathData.size();
+ mPathData.resize(oldSize + sizeof(OpType));
+ *reinterpret_cast<OpType*>(mPathData.data() + oldSize) = OpType::OP_CLOSE;
+ }
+
+ private:
+ enum class OpType : uint32_t {
+ OP_MOVETO = 0,
+ OP_LINETO,
+ OP_BEZIERTO,
+ OP_QUADRATICBEZIERTO,
+ OP_ARC,
+ OP_CLOSE,
+ OP_INVALID
+ };
+
+ template <typename T>
+ void AppendPathOp(const OpType& aOpType, const T& aOpParams) {
+ size_t oldSize = mPathData.size();
+ mPathData.resize(oldSize + sizeof(OpType) + sizeof(T));
+ memcpy(mPathData.data() + oldSize, &aOpType, sizeof(OpType));
+ oldSize += sizeof(OpType);
+ memcpy(mPathData.data() + oldSize, &aOpParams, sizeof(T));
+ }
+
+ struct TwoPoints {
+ Point p1;
+ Point p2;
+ };
+
+ struct ThreePoints {
+ Point p1;
+ Point p2;
+ Point p3;
+ };
+
+ struct ArcParams {
+ Point origin;
+ float radius;
+ float startAngle;
+ float endAngle;
+ bool antiClockwise;
+ };
+
+ std::vector<uint8_t> mPathData;
+};
+
+template <class S>
+PathOps::PathOps(S& aStream) {
+ ReadVector(aStream, mPathData);
+}
+
+template <class S>
+inline void PathOps::Record(S& aStream) const {
+ WriteVector(aStream, mPathData);
+}
+
+class PathRecording;
+class DrawEventRecorderPrivate;
+
+class PathBuilderRecording final : public PathBuilder {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderRecording, override)
+
+ PathBuilderRecording(BackendType aBackend, FillRule aFillRule)
+ : mBackendType(aBackend), mFillRule(aFillRule) {}
+
+ PathBuilderRecording(BackendType aBackend, PathOps&& aPathOps,
+ FillRule aFillRule)
+ : mBackendType(aBackend),
+ mFillRule(aFillRule),
+ mPathOps(std::move(aPathOps)) {}
+
+ /* Move the current point in the path, any figure currently being drawn will
+ * be considered closed during fill operations, however when stroking the
+ * closing line segment will not be drawn.
+ */
+ void MoveTo(const Point& aPoint) final;
+
+ /* Add a linesegment to the current figure */
+ void LineTo(const Point& aPoint) final;
+
+ /* Add a cubic bezier curve to the current figure */
+ void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) final;
+
+ /* Add a quadratic bezier curve to the current figure */
+ void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) final;
+
+ /* Close the current figure, this will essentially generate a line segment
+ * from the current point to the starting point for the current figure
+ */
+ void Close() final;
+
+ /* Add an arc to the current figure */
+ void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise) final;
+
+ already_AddRefed<Path> Finish() final;
+
+ BackendType GetBackendType() const final { return BackendType::RECORDING; }
+
+ private:
+ BackendType mBackendType;
+ FillRule mFillRule;
+ PathOps mPathOps;
+};
+
+class PathRecording final : public Path {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathRecording, override)
+
+ PathRecording(BackendType aBackend, PathOps&& aOps, FillRule aFillRule,
+ const Point& aCurrentPoint, const Point& aBeginPoint);
+
+ ~PathRecording();
+
+ BackendType GetBackendType() const final { return BackendType::RECORDING; }
+ already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const final;
+ already_AddRefed<PathBuilder> TransformedCopyToBuilder(
+ const Matrix& aTransform, FillRule aFillRule) const final;
+ bool ContainsPoint(const Point& aPoint,
+ const Matrix& aTransform) const final {
+ EnsurePath();
+ return mPath->ContainsPoint(aPoint, aTransform);
+ }
+ bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
+ const Point& aPoint,
+ const Matrix& aTransform) const final {
+ EnsurePath();
+ return mPath->StrokeContainsPoint(aStrokeOptions, aPoint, aTransform);
+ }
+
+ Rect GetBounds(const Matrix& aTransform = Matrix()) const final {
+ EnsurePath();
+ return mPath->GetBounds(aTransform);
+ }
+
+ Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform = Matrix()) const final {
+ EnsurePath();
+ return mPath->GetStrokedBounds(aStrokeOptions, aTransform);
+ }
+
+ Maybe<Rect> AsRect() const final {
+ EnsurePath();
+ return mPath->AsRect();
+ }
+
+ void StreamToSink(PathSink* aSink) const final {
+ mPathOps.StreamToSink(*aSink);
+ }
+
+ FillRule GetFillRule() const final { return mFillRule; }
+
+ private:
+ friend class DrawTargetWrapAndRecord;
+ friend class DrawTargetRecording;
+ friend class RecordedPathCreation;
+
+ void EnsurePath() const;
+
+ BackendType mBackendType;
+ mutable RefPtr<Path> mPath;
+ PathOps mPathOps;
+ FillRule mFillRule;
+ Point mCurrentPoint;
+ Point mBeginPoint;
+
+ // Event recorders that have this path in their event stream.
+ std::vector<RefPtr<DrawEventRecorderPrivate>> mStoredRecorders;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PATHRECORDING_H_ */
diff --git a/gfx/2d/PathSkia.cpp b/gfx/2d/PathSkia.cpp
new file mode 100644
index 0000000000..5902161df9
--- /dev/null
+++ b/gfx/2d/PathSkia.cpp
@@ -0,0 +1,273 @@
+/* -*- 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 "PathSkia.h"
+#include "HelpersSkia.h"
+#include "PathHelpers.h"
+#include "skia/include/core/SkPathUtils.h"
+#include "skia/src/core/SkGeometry.h"
+
+namespace mozilla::gfx {
+
+already_AddRefed<PathBuilder> PathBuilderSkia::Create(FillRule aFillRule) {
+ return MakeAndAddRef<PathBuilderSkia>(aFillRule);
+}
+
+PathBuilderSkia::PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath,
+ FillRule aFillRule)
+ : mPath(aPath) {
+ SkMatrix matrix;
+ GfxMatrixToSkiaMatrix(aTransform, matrix);
+ mPath.transform(matrix);
+ SetFillRule(aFillRule);
+}
+
+PathBuilderSkia::PathBuilderSkia(FillRule aFillRule) { SetFillRule(aFillRule); }
+
+void PathBuilderSkia::SetFillRule(FillRule aFillRule) {
+ mFillRule = aFillRule;
+ if (mFillRule == FillRule::FILL_WINDING) {
+ mPath.setFillType(SkPathFillType::kWinding);
+ } else {
+ mPath.setFillType(SkPathFillType::kEvenOdd);
+ }
+}
+
+void PathBuilderSkia::MoveTo(const Point& aPoint) {
+ mPath.moveTo(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y));
+ mCurrentPoint = aPoint;
+ mBeginPoint = aPoint;
+}
+
+void PathBuilderSkia::LineTo(const Point& aPoint) {
+ if (!mPath.countPoints()) {
+ MoveTo(aPoint);
+ } else {
+ mPath.lineTo(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y));
+ }
+ mCurrentPoint = aPoint;
+}
+
+void PathBuilderSkia::BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) {
+ if (!mPath.countPoints()) {
+ MoveTo(aCP1);
+ }
+ mPath.cubicTo(SkFloatToScalar(aCP1.x), SkFloatToScalar(aCP1.y),
+ SkFloatToScalar(aCP2.x), SkFloatToScalar(aCP2.y),
+ SkFloatToScalar(aCP3.x), SkFloatToScalar(aCP3.y));
+ mCurrentPoint = aCP3;
+}
+
+void PathBuilderSkia::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) {
+ if (!mPath.countPoints()) {
+ MoveTo(aCP1);
+ }
+ mPath.quadTo(SkFloatToScalar(aCP1.x), SkFloatToScalar(aCP1.y),
+ SkFloatToScalar(aCP2.x), SkFloatToScalar(aCP2.y));
+ mCurrentPoint = aCP2;
+}
+
+void PathBuilderSkia::Close() {
+ mPath.close();
+ mCurrentPoint = mBeginPoint;
+}
+
+void PathBuilderSkia::Arc(const Point& aOrigin, float aRadius,
+ float aStartAngle, float aEndAngle,
+ bool aAntiClockwise) {
+ ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+ aAntiClockwise);
+}
+
+already_AddRefed<Path> PathBuilderSkia::Finish() {
+ RefPtr<Path> path =
+ MakeAndAddRef<PathSkia>(mPath, mFillRule, mCurrentPoint, mBeginPoint);
+ mCurrentPoint = Point(0.0, 0.0);
+ mBeginPoint = Point(0.0, 0.0);
+ return path.forget();
+}
+
+void PathBuilderSkia::AppendPath(const SkPath& aPath) { mPath.addPath(aPath); }
+
+already_AddRefed<PathBuilder> PathSkia::CopyToBuilder(
+ FillRule aFillRule) const {
+ return TransformedCopyToBuilder(Matrix(), aFillRule);
+}
+
+already_AddRefed<PathBuilder> PathSkia::TransformedCopyToBuilder(
+ const Matrix& aTransform, FillRule aFillRule) const {
+ RefPtr<PathBuilderSkia> builder =
+ MakeAndAddRef<PathBuilderSkia>(aTransform, mPath, aFillRule);
+
+ builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint);
+ builder->mBeginPoint = aTransform.TransformPoint(mBeginPoint);
+
+ return builder.forget();
+}
+
+static bool SkPathContainsPoint(const SkPath& aPath, const Point& aPoint,
+ const Matrix& aTransform) {
+ Matrix inverse = aTransform;
+ if (!inverse.Invert()) {
+ return false;
+ }
+
+ SkPoint point = PointToSkPoint(inverse.TransformPoint(aPoint));
+ return aPath.contains(point.fX, point.fY);
+}
+
+bool PathSkia::ContainsPoint(const Point& aPoint,
+ const Matrix& aTransform) const {
+ if (!mPath.isFinite()) {
+ return false;
+ }
+
+ return SkPathContainsPoint(mPath, aPoint, aTransform);
+}
+
+bool PathSkia::GetFillPath(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform, SkPath& aFillPath,
+ const Maybe<Rect>& aClipRect) const {
+ SkPaint paint;
+ if (!StrokeOptionsToPaint(paint, aStrokeOptions)) {
+ return false;
+ }
+
+ SkMatrix skiaMatrix;
+ GfxMatrixToSkiaMatrix(aTransform, skiaMatrix);
+
+ Maybe<SkRect> cullRect;
+ if (aClipRect.isSome()) {
+ cullRect = Some(RectToSkRect(aClipRect.ref()));
+ }
+
+ return skpathutils::FillPathWithPaint(mPath, paint, &aFillPath,
+ cullRect.ptrOr(nullptr), skiaMatrix);
+}
+
+bool PathSkia::StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
+ const Point& aPoint,
+ const Matrix& aTransform) const {
+ if (!mPath.isFinite()) {
+ return false;
+ }
+
+ SkPath strokePath;
+ if (!GetFillPath(aStrokeOptions, aTransform, strokePath)) {
+ return false;
+ }
+
+ return SkPathContainsPoint(strokePath, aPoint, aTransform);
+}
+
+Rect PathSkia::GetBounds(const Matrix& aTransform) const {
+ if (!mPath.isFinite()) {
+ return Rect();
+ }
+
+ Rect bounds = SkRectToRect(mPath.computeTightBounds());
+ return aTransform.TransformBounds(bounds);
+}
+
+Rect PathSkia::GetStrokedBounds(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform) const {
+ if (!mPath.isFinite()) {
+ return Rect();
+ }
+
+ SkPath fillPath;
+ if (!GetFillPath(aStrokeOptions, aTransform, fillPath)) {
+ return Rect();
+ }
+
+ Rect bounds = SkRectToRect(fillPath.computeTightBounds());
+ return aTransform.TransformBounds(bounds);
+}
+
+Rect PathSkia::GetFastBounds(const Matrix& aTransform,
+ const StrokeOptions* aStrokeOptions) const {
+ if (!mPath.isFinite()) {
+ return Rect();
+ }
+ SkRect bounds = mPath.getBounds();
+ if (aStrokeOptions) {
+ // If the path is stroked, ensure that the bounds are inflated by any
+ // relevant options such as line width. Avoid using dash path effects
+ // for performance and to ensure computeFastStrokeBounds succeeds.
+ SkPaint paint;
+ if (!StrokeOptionsToPaint(paint, *aStrokeOptions, false)) {
+ return Rect();
+ }
+ SkRect outBounds = SkRect::MakeEmpty();
+ bounds = paint.computeFastStrokeBounds(bounds, &outBounds);
+ }
+ return aTransform.TransformBounds(SkRectToRect(bounds));
+}
+
+int ConvertConicToQuads(const Point& aP0, const Point& aP1, const Point& aP2,
+ float aWeight, std::vector<Point>& aQuads) {
+ SkConic conic(PointToSkPoint(aP0), PointToSkPoint(aP1), PointToSkPoint(aP2),
+ aWeight);
+ int pow2 = conic.computeQuadPOW2(0.25f);
+ aQuads.resize(1 + 2 * (1 << pow2));
+ int numQuads =
+ conic.chopIntoQuadsPOW2(reinterpret_cast<SkPoint*>(&aQuads[0]), pow2);
+ if (numQuads < 1 << pow2) {
+ aQuads.resize(1 + 2 * numQuads);
+ }
+ return numQuads;
+}
+
+void PathSkia::StreamToSink(PathSink* aSink) const {
+ SkPath::RawIter iter(mPath);
+
+ SkPoint points[4];
+ SkPath::Verb currentVerb;
+ while ((currentVerb = iter.next(points)) != SkPath::kDone_Verb) {
+ switch (currentVerb) {
+ case SkPath::kMove_Verb:
+ aSink->MoveTo(SkPointToPoint(points[0]));
+ break;
+ case SkPath::kLine_Verb:
+ aSink->LineTo(SkPointToPoint(points[1]));
+ break;
+ case SkPath::kCubic_Verb:
+ aSink->BezierTo(SkPointToPoint(points[1]), SkPointToPoint(points[2]),
+ SkPointToPoint(points[3]));
+ break;
+ case SkPath::kQuad_Verb:
+ aSink->QuadraticBezierTo(SkPointToPoint(points[1]),
+ SkPointToPoint(points[2]));
+ break;
+ case SkPath::kConic_Verb: {
+ std::vector<Point> quads;
+ int numQuads = ConvertConicToQuads(
+ SkPointToPoint(points[0]), SkPointToPoint(points[1]),
+ SkPointToPoint(points[2]), iter.conicWeight(), quads);
+ for (int i = 0; i < numQuads; i++) {
+ aSink->QuadraticBezierTo(quads[2 * i + 1], quads[2 * i + 2]);
+ }
+ break;
+ }
+ case SkPath::kClose_Verb:
+ aSink->Close();
+ break;
+ default:
+ MOZ_ASSERT(false);
+ // Unexpected verb found in path!
+ }
+ }
+}
+
+Maybe<Rect> PathSkia::AsRect() const {
+ SkRect rect;
+ if (mPath.isRect(&rect)) {
+ return Some(SkRectToRect(rect));
+ }
+ return Nothing();
+}
+} // namespace mozilla::gfx
diff --git a/gfx/2d/PathSkia.h b/gfx/2d/PathSkia.h
new file mode 100644
index 0000000000..b31e9665f4
--- /dev/null
+++ b/gfx/2d/PathSkia.h
@@ -0,0 +1,110 @@
+/* -*- 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_PATH_SKIA_H_
+#define MOZILLA_GFX_PATH_SKIA_H_
+
+#include "2D.h"
+#include "skia/include/core/SkPath.h"
+
+namespace mozilla {
+namespace gfx {
+
+class PathSkia;
+
+class PathBuilderSkia : public PathBuilder {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderSkia, override)
+
+ PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath,
+ FillRule aFillRule);
+ explicit PathBuilderSkia(FillRule aFillRule);
+
+ void MoveTo(const Point& aPoint) override;
+ void LineTo(const Point& aPoint) override;
+ void BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) override;
+ void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override;
+ void Close() override;
+ void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise = false) override;
+ already_AddRefed<Path> Finish() override;
+
+ void AppendPath(const SkPath& aPath);
+
+ BackendType GetBackendType() const override { return BackendType::SKIA; }
+
+ static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
+
+ private:
+ friend class PathSkia;
+
+ void SetFillRule(FillRule aFillRule);
+
+ SkPath mPath;
+ FillRule mFillRule;
+};
+
+class PathSkia : public Path {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathSkia, override)
+
+ PathSkia(SkPath& aPath, FillRule aFillRule, Point aCurrentPoint = Point(),
+ Point aBeginPoint = Point())
+ : mFillRule(aFillRule),
+ mCurrentPoint(aCurrentPoint),
+ mBeginPoint(aBeginPoint) {
+ mPath.swap(aPath);
+ }
+
+ BackendType GetBackendType() const override { return BackendType::SKIA; }
+
+ already_AddRefed<PathBuilder> CopyToBuilder(
+ FillRule aFillRule) const override;
+ already_AddRefed<PathBuilder> TransformedCopyToBuilder(
+ const Matrix& aTransform, FillRule aFillRule) const override;
+
+ bool ContainsPoint(const Point& aPoint,
+ const Matrix& aTransform) const override;
+
+ bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
+ const Point& aPoint,
+ const Matrix& aTransform) const override;
+
+ Rect GetBounds(const Matrix& aTransform = Matrix()) const override;
+
+ Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform = Matrix()) const override;
+
+ Rect GetFastBounds(
+ const Matrix& aTransform = Matrix(),
+ const StrokeOptions* aStrokeOptions = nullptr) const override;
+
+ void StreamToSink(PathSink* aSink) const override;
+
+ FillRule GetFillRule() const override { return mFillRule; }
+
+ const SkPath& GetPath() const { return mPath; }
+
+ Maybe<Rect> AsRect() const override;
+
+ bool GetFillPath(const StrokeOptions& aStrokeOptions,
+ const Matrix& aTransform, SkPath& aFillPath,
+ const Maybe<Rect>& aClipRect = Nothing()) const;
+
+ private:
+ friend class DrawTargetSkia;
+
+ SkPath mPath;
+ FillRule mFillRule;
+ Point mCurrentPoint;
+ Point mBeginPoint;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PATH_SKIA_H_ */
diff --git a/gfx/2d/PatternHelpers.h b/gfx/2d/PatternHelpers.h
new file mode 100644
index 0000000000..09b4cd4d93
--- /dev/null
+++ b/gfx/2d/PatternHelpers.h
@@ -0,0 +1,141 @@
+/* -*- 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_PATTERNHELPERS_H
+#define _MOZILLA_GFX_PATTERNHELPERS_H
+
+#include "mozilla/Alignment.h"
+#include "mozilla/gfx/2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * This class is used to allow general pattern creation functions to return
+ * any type of pattern via an out-paramater without allocating a pattern
+ * instance on the free-store (an instance of this class being created on the
+ * stack before passing it in to the creation function). Without this class
+ * writing pattern creation functions would be a pain since Pattern objects are
+ * not reference counted, making lifetime management of instances created on
+ * the free-store and returned from a creation function hazardous. Besides
+ * that, in the case that ColorPattern's are expected to be common, it is
+ * particularly desirable to avoid the overhead of allocating on the
+ * free-store.
+ */
+class GeneralPattern final {
+ public:
+ explicit GeneralPattern() = default;
+
+ GeneralPattern(const GeneralPattern& aOther) {}
+
+ ~GeneralPattern() {
+ if (mPattern) {
+ mPattern->~Pattern();
+ }
+ }
+
+ Pattern* Init(const Pattern& aPattern) {
+ MOZ_ASSERT(!mPattern);
+ switch (aPattern.GetType()) {
+ case PatternType::COLOR:
+ mPattern = new (mColorPattern.addr())
+ ColorPattern(static_cast<const ColorPattern&>(aPattern));
+ break;
+ case PatternType::LINEAR_GRADIENT:
+ mPattern = new (mLinearGradientPattern.addr()) LinearGradientPattern(
+ static_cast<const LinearGradientPattern&>(aPattern));
+ break;
+ case PatternType::RADIAL_GRADIENT:
+ mPattern = new (mRadialGradientPattern.addr()) RadialGradientPattern(
+ static_cast<const RadialGradientPattern&>(aPattern));
+ break;
+ case PatternType::CONIC_GRADIENT:
+ mPattern = new (mConicGradientPattern.addr()) ConicGradientPattern(
+ static_cast<const ConicGradientPattern&>(aPattern));
+ break;
+ case PatternType::SURFACE:
+ mPattern = new (mSurfacePattern.addr())
+ SurfacePattern(static_cast<const SurfacePattern&>(aPattern));
+ break;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unknown pattern type");
+ }
+ return mPattern;
+ }
+
+ ColorPattern* InitColorPattern(const DeviceColor& aColor) {
+ MOZ_ASSERT(!mPattern);
+ mPattern = new (mColorPattern.addr()) ColorPattern(aColor);
+ return mColorPattern.addr();
+ }
+
+ LinearGradientPattern* InitLinearGradientPattern(
+ const Point& aBegin, const Point& aEnd,
+ already_AddRefed<GradientStops> aStops,
+ const Matrix& aMatrix = Matrix()) {
+ MOZ_ASSERT(!mPattern);
+ mPattern = new (mLinearGradientPattern.addr())
+ LinearGradientPattern(aBegin, aEnd, std::move(aStops), aMatrix);
+ return mLinearGradientPattern.addr();
+ }
+
+ RadialGradientPattern* InitRadialGradientPattern(
+ const Point& aCenter1, const Point& aCenter2, Float aRadius1,
+ Float aRadius2, already_AddRefed<GradientStops> aStops,
+ const Matrix& aMatrix = Matrix()) {
+ MOZ_ASSERT(!mPattern);
+ mPattern = new (mRadialGradientPattern.addr()) RadialGradientPattern(
+ aCenter1, aCenter2, aRadius1, aRadius2, std::move(aStops), aMatrix);
+ return mRadialGradientPattern.addr();
+ }
+
+ ConicGradientPattern* InitConicGradientPattern(
+ const Point& aCenter, Float aAngle, Float aStartOffset, Float aEndOffset,
+ already_AddRefed<GradientStops> aStops,
+ const Matrix& aMatrix = Matrix()) {
+ MOZ_ASSERT(!mPattern);
+ mPattern = new (mConicGradientPattern.addr()) ConicGradientPattern(
+ aCenter, aAngle, aStartOffset, aEndOffset, std::move(aStops), aMatrix);
+ return mConicGradientPattern.addr();
+ }
+
+ SurfacePattern* InitSurfacePattern(
+ SourceSurface* aSourceSurface, ExtendMode aExtendMode,
+ const Matrix& aMatrix = Matrix(),
+ SamplingFilter aSamplingFilter = SamplingFilter::GOOD,
+ const IntRect& aSamplingRect = IntRect()) {
+ MOZ_ASSERT(!mPattern);
+ mPattern = new (mSurfacePattern.addr()) SurfacePattern(
+ aSourceSurface, aExtendMode, aMatrix, aSamplingFilter, aSamplingRect);
+ return mSurfacePattern.addr();
+ }
+
+ Pattern* GetPattern() { return mPattern; }
+
+ const Pattern* GetPattern() const { return mPattern; }
+
+ operator Pattern&() {
+ if (!mPattern) {
+ MOZ_CRASH("GFX: GeneralPattern not initialized");
+ }
+ return *mPattern;
+ }
+
+ private:
+ union {
+ AlignedStorage2<ColorPattern> mColorPattern;
+ AlignedStorage2<LinearGradientPattern> mLinearGradientPattern;
+ AlignedStorage2<RadialGradientPattern> mRadialGradientPattern;
+ AlignedStorage2<ConicGradientPattern> mConicGradientPattern;
+ AlignedStorage2<SurfacePattern> mSurfacePattern;
+ };
+ Pattern* mPattern = nullptr;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_PATTERNHELPERS_H
diff --git a/gfx/2d/Point.h b/gfx/2d/Point.h
new file mode 100644
index 0000000000..fc9b000419
--- /dev/null
+++ b/gfx/2d/Point.h
@@ -0,0 +1,403 @@
+/* -*- 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_POINT_H_
+#define MOZILLA_GFX_POINT_H_
+
+#include "mozilla/Attributes.h"
+#include "Types.h"
+#include "Coord.h"
+#include "BaseCoord.h"
+#include "BasePoint.h"
+#include "BasePoint3D.h"
+#include "BasePoint4D.h"
+#include "BaseSize.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/gfx/NumericTools.h"
+
+#include <cmath>
+#include <type_traits>
+
+namespace mozilla {
+
+template <typename>
+struct IsPixel;
+
+template <>
+struct IsPixel<gfx::UnknownUnits> : std::true_type {};
+
+namespace gfx {
+
+/// Use this for parameters of functions to allow implicit conversions to
+/// integer types but not floating point types.
+/// We use this wrapper to prevent IntSize and IntPoint's constructors to
+/// take foating point values as parameters, and not require their constructors
+/// to have implementations for each permutation of integer types.
+template <typename T>
+struct IntParam {
+ constexpr MOZ_IMPLICIT IntParam(char val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(unsigned char val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(short val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(unsigned short val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(int val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(unsigned int val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(long val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(unsigned long val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(long long val) : value(val) {}
+ constexpr MOZ_IMPLICIT IntParam(unsigned long long val) : value(val) {}
+ template <typename Unit>
+ constexpr MOZ_IMPLICIT IntParam(IntCoordTyped<Unit> val) : value(val) {}
+
+ // Disable the evil ones!
+ MOZ_IMPLICIT IntParam(float val) = delete;
+ MOZ_IMPLICIT IntParam(double val) = delete;
+
+ T value;
+};
+
+template <class Units, class>
+struct PointTyped;
+template <class Units, class>
+struct SizeTyped;
+
+template <class Units>
+struct MOZ_EMPTY_BASES IntPointTyped
+ : public BasePoint<int32_t, IntPointTyped<Units>, IntCoordTyped<Units> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef IntParam<int32_t> ToInt;
+ typedef IntCoordTyped<Units> Coord;
+ typedef BasePoint<int32_t, IntPointTyped<Units>, IntCoordTyped<Units> > Super;
+
+ constexpr IntPointTyped() : Super() {
+ static_assert(sizeof(IntPointTyped) == sizeof(int32_t) * 2,
+ "Would be unfortunate otherwise!");
+ }
+ constexpr IntPointTyped(ToInt aX, ToInt aY)
+ : Super(Coord(aX.value), Coord(aY.value)) {}
+
+ static IntPointTyped Round(float aX, float aY) {
+ return IntPointTyped(int32_t(floorf(aX + 0.5f)),
+ int32_t(floorf(aY + 0.5f)));
+ }
+
+ static IntPointTyped Ceil(float aX, float aY) {
+ return IntPointTyped(int32_t(ceilf(aX)), int32_t(ceilf(aY)));
+ }
+
+ static IntPointTyped Floor(float aX, float aY) {
+ return IntPointTyped(int32_t(floorf(aX)), int32_t(floorf(aY)));
+ }
+
+ static IntPointTyped Truncate(float aX, float aY) {
+ return IntPointTyped(int32_t(aX), int32_t(aY));
+ }
+
+ static IntPointTyped Round(const PointTyped<Units, float>& aPoint);
+ static IntPointTyped Ceil(const PointTyped<Units, float>& aPoint);
+ static IntPointTyped Floor(const PointTyped<Units, float>& aPoint);
+ static IntPointTyped Truncate(const PointTyped<Units, float>& aPoint);
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static IntPointTyped FromUnknownPoint(
+ const IntPointTyped<UnknownUnits>& aPoint) {
+ return IntPointTyped<Units>(aPoint.x, aPoint.y);
+ }
+
+ IntPointTyped<UnknownUnits> ToUnknownPoint() const {
+ return IntPointTyped<UnknownUnits>(this->x, this->y);
+ }
+
+ IntPointTyped RoundedToMultiple(int32_t aMultiplier) const {
+ return {RoundToMultiple(this->x, aMultiplier),
+ RoundToMultiple(this->y, aMultiplier)};
+ }
+};
+typedef IntPointTyped<UnknownUnits> IntPoint;
+
+template <class Units, class F = Float>
+struct MOZ_EMPTY_BASES PointTyped
+ : public BasePoint<F, PointTyped<Units, F>, CoordTyped<Units, F> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef CoordTyped<Units, F> Coord;
+ typedef BasePoint<F, PointTyped<Units, F>, CoordTyped<Units, F> > Super;
+
+ constexpr PointTyped() : Super() {
+ static_assert(sizeof(PointTyped) == sizeof(F) * 2,
+ "Would be unfortunate otherwise!");
+ }
+ constexpr PointTyped(F aX, F aY) : Super(Coord(aX), Coord(aY)) {}
+ // The mixed-type constructors (Float, Coord) and (Coord, Float) are needed to
+ // avoid ambiguities because Coord is implicitly convertible to Float.
+ constexpr PointTyped(F aX, Coord aY) : Super(Coord(aX), aY) {}
+ constexpr PointTyped(Coord aX, F aY) : Super(aX, Coord(aY)) {}
+ constexpr PointTyped(Coord aX, Coord aY) : Super(aX.value, aY.value) {}
+ constexpr MOZ_IMPLICIT PointTyped(const IntPointTyped<Units>& point)
+ : Super(F(point.x), F(point.y)) {}
+
+ bool WithinEpsilonOf(const PointTyped<Units, F>& aPoint, F aEpsilon) const {
+ return fabs(aPoint.x - this->x) < aEpsilon &&
+ fabs(aPoint.y - this->y) < aEpsilon;
+ }
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static PointTyped<Units, F> FromUnknownPoint(
+ const PointTyped<UnknownUnits, F>& aPoint) {
+ return PointTyped<Units, F>(aPoint.x, aPoint.y);
+ }
+
+ PointTyped<UnknownUnits, F> ToUnknownPoint() const {
+ return PointTyped<UnknownUnits, F>(this->x, this->y);
+ }
+};
+typedef PointTyped<UnknownUnits> Point;
+typedef PointTyped<UnknownUnits, double> PointDouble;
+
+template <class Units>
+IntPointTyped<Units> RoundedToInt(const PointTyped<Units>& aPoint) {
+ return IntPointTyped<Units>::Round(aPoint.x, aPoint.y);
+}
+
+template <class Units>
+IntPointTyped<Units> TruncatedToInt(const PointTyped<Units>& aPoint) {
+ return IntPointTyped<Units>::Truncate(aPoint.x, aPoint.y);
+}
+
+template <class Units, class F = Float>
+struct Point3DTyped : public BasePoint3D<F, Point3DTyped<Units, F> > {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BasePoint3D<F, Point3DTyped<Units, F> > Super;
+
+ Point3DTyped() : Super() {
+ static_assert(sizeof(Point3DTyped) == sizeof(F) * 3,
+ "Would be unfortunate otherwise!");
+ }
+ Point3DTyped(F aX, F aY, F aZ) : Super(aX, aY, aZ) {}
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static Point3DTyped<Units, F> FromUnknownPoint(
+ const Point3DTyped<UnknownUnits, F>& aPoint) {
+ return Point3DTyped<Units, F>(aPoint.x, aPoint.y, aPoint.z);
+ }
+
+ Point3DTyped<UnknownUnits, F> ToUnknownPoint() const {
+ return Point3DTyped<UnknownUnits, F>(this->x, this->y, this->z);
+ }
+};
+typedef Point3DTyped<UnknownUnits> Point3D;
+typedef Point3DTyped<UnknownUnits, double> PointDouble3D;
+
+template <typename Units>
+IntPointTyped<Units> IntPointTyped<Units>::Round(
+ const PointTyped<Units, float>& aPoint) {
+ return IntPointTyped::Round(aPoint.x, aPoint.y);
+}
+
+template <typename Units>
+IntPointTyped<Units> IntPointTyped<Units>::Ceil(
+ const PointTyped<Units, float>& aPoint) {
+ return IntPointTyped::Ceil(aPoint.x, aPoint.y);
+}
+
+template <typename Units>
+IntPointTyped<Units> IntPointTyped<Units>::Floor(
+ const PointTyped<Units, float>& aPoint) {
+ return IntPointTyped::Floor(aPoint.x, aPoint.y);
+}
+
+template <typename Units>
+IntPointTyped<Units> IntPointTyped<Units>::Truncate(
+ const PointTyped<Units, float>& aPoint) {
+ return IntPointTyped::Truncate(aPoint.x, aPoint.y);
+}
+
+template <class Units, class F = Float>
+struct Point4DTyped : public BasePoint4D<F, Point4DTyped<Units, F> > {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BasePoint4D<F, Point4DTyped<Units, F> > Super;
+
+ Point4DTyped() : Super() {
+ static_assert(sizeof(Point4DTyped) == sizeof(F) * 4,
+ "Would be unfortunate otherwise!");
+ }
+ Point4DTyped(F aX, F aY, F aZ, F aW) : Super(aX, aY, aZ, aW) {}
+
+ explicit Point4DTyped(const Point3DTyped<Units, F>& aPoint)
+ : Super(aPoint.x, aPoint.y, aPoint.z, 1) {}
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static Point4DTyped<Units, F> FromUnknownPoint(
+ const Point4DTyped<UnknownUnits, F>& aPoint) {
+ return Point4DTyped<Units, F>(aPoint.x, aPoint.y, aPoint.z, aPoint.w);
+ }
+
+ Point4DTyped<UnknownUnits, F> ToUnknownPoint() const {
+ return Point4DTyped<UnknownUnits, F>(this->x, this->y, this->z, this->w);
+ }
+
+ PointTyped<Units, F> As2DPoint() const {
+ return PointTyped<Units, F>(this->x / this->w, this->y / this->w);
+ }
+
+ Point3DTyped<Units, F> As3DPoint() const {
+ return Point3DTyped<Units, F>(this->x / this->w, this->y / this->w,
+ this->z / this->w);
+ }
+};
+typedef Point4DTyped<UnknownUnits> Point4D;
+typedef Point4DTyped<UnknownUnits, double> PointDouble4D;
+
+template <class Units>
+struct MOZ_EMPTY_BASES IntSizeTyped
+ : public BaseSize<int32_t, IntSizeTyped<Units> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef IntParam<int32_t> ToInt;
+ typedef BaseSize<int32_t, IntSizeTyped<Units> > Super;
+
+ constexpr IntSizeTyped() : Super() {
+ static_assert(sizeof(IntSizeTyped) == sizeof(int32_t) * 2,
+ "Would be unfortunate otherwise!");
+ }
+ constexpr IntSizeTyped(ToInt aWidth, ToInt aHeight)
+ : Super(aWidth.value, aHeight.value) {}
+
+ static IntSizeTyped Round(float aWidth, float aHeight) {
+ return IntSizeTyped(int32_t(floorf(aWidth + 0.5)),
+ int32_t(floorf(aHeight + 0.5)));
+ }
+
+ static IntSizeTyped Truncate(float aWidth, float aHeight) {
+ return IntSizeTyped(int32_t(aWidth), int32_t(aHeight));
+ }
+
+ static IntSizeTyped Ceil(float aWidth, float aHeight) {
+ return IntSizeTyped(int32_t(ceil(aWidth)), int32_t(ceil(aHeight)));
+ }
+
+ static IntSizeTyped Floor(float aWidth, float aHeight) {
+ return IntSizeTyped(int32_t(floorf(aWidth)), int32_t(floorf(aHeight)));
+ }
+
+ static IntSizeTyped Round(const SizeTyped<Units, float>& aSize);
+ static IntSizeTyped Ceil(const SizeTyped<Units, float>& aSize);
+ static IntSizeTyped Floor(const SizeTyped<Units, float>& aSize);
+ static IntSizeTyped Truncate(const SizeTyped<Units, float>& aSize);
+
+ IntSizeTyped TruncatedToMultiple(int32_t aMultiplier) const {
+ if (aMultiplier == 1) {
+ return *this;
+ }
+ return {RoundDownToMultiple(this->width, aMultiplier),
+ RoundDownToMultiple(this->height, aMultiplier)};
+ }
+
+ IntSizeTyped CeiledToMultiple(int32_t aMultiplier) const {
+ if (aMultiplier == 1) {
+ return *this;
+ }
+ return {RoundUpToMultiple(this->width, aMultiplier),
+ RoundUpToMultiple(this->height, aMultiplier)};
+ }
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static IntSizeTyped FromUnknownSize(const IntSizeTyped<UnknownUnits>& aSize) {
+ return IntSizeTyped(aSize.width, aSize.height);
+ }
+
+ IntSizeTyped<UnknownUnits> ToUnknownSize() const {
+ return IntSizeTyped<UnknownUnits>(this->width, this->height);
+ }
+};
+typedef IntSizeTyped<UnknownUnits> IntSize;
+typedef Maybe<IntSize> MaybeIntSize;
+
+template <class Units, class F = Float>
+struct MOZ_EMPTY_BASES SizeTyped : public BaseSize<F, SizeTyped<Units, F> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BaseSize<F, SizeTyped<Units, F> > Super;
+
+ constexpr SizeTyped() : Super() {
+ static_assert(sizeof(SizeTyped) == sizeof(F) * 2,
+ "Would be unfortunate otherwise!");
+ }
+ constexpr SizeTyped(F aWidth, F aHeight) : Super(aWidth, aHeight) {}
+ explicit SizeTyped(const IntSizeTyped<Units>& size)
+ : Super(F(size.width), F(size.height)) {}
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static SizeTyped<Units, F> FromUnknownSize(
+ const SizeTyped<UnknownUnits, F>& aSize) {
+ return SizeTyped<Units, F>(aSize.width, aSize.height);
+ }
+
+ SizeTyped<UnknownUnits, F> ToUnknownSize() const {
+ return SizeTyped<UnknownUnits, F>(this->width, this->height);
+ }
+};
+typedef SizeTyped<UnknownUnits> Size;
+typedef SizeTyped<UnknownUnits, double> SizeDouble;
+
+template <class Units>
+IntSizeTyped<Units> RoundedToInt(const SizeTyped<Units>& aSize) {
+ return IntSizeTyped<Units>(int32_t(floorf(aSize.width + 0.5f)),
+ int32_t(floorf(aSize.height + 0.5f)));
+}
+
+template <typename Units>
+IntSizeTyped<Units> IntSizeTyped<Units>::Round(
+ const SizeTyped<Units, float>& aSize) {
+ return IntSizeTyped::Round(aSize.width, aSize.height);
+}
+
+template <typename Units>
+IntSizeTyped<Units> IntSizeTyped<Units>::Ceil(
+ const SizeTyped<Units, float>& aSize) {
+ return IntSizeTyped::Ceil(aSize.width, aSize.height);
+}
+
+template <typename Units>
+IntSizeTyped<Units> IntSizeTyped<Units>::Floor(
+ const SizeTyped<Units, float>& aSize) {
+ return IntSizeTyped::Floor(aSize.width, aSize.height);
+}
+
+template <typename Units>
+IntSizeTyped<Units> IntSizeTyped<Units>::Truncate(
+ const SizeTyped<Units, float>& aSize) {
+ return IntSizeTyped::Truncate(aSize.width, aSize.height);
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_POINT_H_ */
diff --git a/gfx/2d/Polygon.h b/gfx/2d/Polygon.h
new file mode 100644
index 0000000000..3de3d684f9
--- /dev/null
+++ b/gfx/2d/Polygon.h
@@ -0,0 +1,396 @@
+/* -*- 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_POLYGON_H
+#define MOZILLA_GFX_POLYGON_H
+
+#include <initializer_list>
+#include <utility>
+
+#include "Matrix.h"
+#include "Point.h"
+#include "Triangle.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Calculates the w = 0 intersection point for the edge defined by
+ * |aFirst| and |aSecond|.
+ */
+template <class Units>
+Point4DTyped<Units> CalculateEdgeIntersect(const Point4DTyped<Units>& aFirst,
+ const Point4DTyped<Units>& aSecond) {
+ static const float w = 0.00001f;
+ const float t = (w - aFirst.w) / (aSecond.w - aFirst.w);
+ return aFirst + (aSecond - aFirst) * t;
+}
+
+/**
+ * Clips the polygon defined by |aPoints| so that there are no points with
+ * w <= 0.
+ */
+template <class Units>
+nsTArray<Point4DTyped<Units>> ClipPointsAtInfinity(
+ const nsTArray<Point4DTyped<Units>>& aPoints) {
+ nsTArray<Point4DTyped<Units>> outPoints(aPoints.Length());
+
+ const size_t pointCount = aPoints.Length();
+ for (size_t i = 0; i < pointCount; ++i) {
+ const Point4DTyped<Units>& first = aPoints[i];
+ const Point4DTyped<Units>& second = aPoints[(i + 1) % pointCount];
+
+ if (!first.w || !second.w) {
+ // Skip edges at infinity.
+ continue;
+ }
+
+ if (first.w > 0.0f) {
+ outPoints.AppendElement(first);
+ }
+
+ if ((first.w <= 0.0f) ^ (second.w <= 0.0f)) {
+ outPoints.AppendElement(CalculateEdgeIntersect(first, second));
+ }
+ }
+
+ return outPoints;
+}
+
+/**
+ * Calculates the distances between the points in |aPoints| and the plane
+ * defined by |aPlaneNormal| and |aPlanePoint|.
+ */
+template <class Units>
+nsTArray<float> CalculatePointPlaneDistances(
+ const nsTArray<Point4DTyped<Units>>& aPoints,
+ const Point4DTyped<Units>& aPlaneNormal,
+ const Point4DTyped<Units>& aPlanePoint, size_t& aPos, size_t& aNeg) {
+ // Point classification might produce incorrect results due to numerical
+ // inaccuracies. Using an epsilon value makes the splitting plane "thicker".
+ const float epsilon = 0.05f;
+
+ aPos = aNeg = 0;
+ nsTArray<float> distances(aPoints.Length());
+
+ for (const Point4DTyped<Units>& point : aPoints) {
+ float dot = (point - aPlanePoint).DotProduct(aPlaneNormal);
+
+ if (dot > epsilon) {
+ aPos++;
+ } else if (dot < -epsilon) {
+ aNeg++;
+ } else {
+ // The point is within the thick plane.
+ dot = 0.0f;
+ }
+
+ distances.AppendElement(dot);
+ }
+
+ return distances;
+}
+
+/**
+ * Clips the polygon defined by |aPoints|. The clipping uses previously
+ * calculated plane to point distances and the plane normal |aNormal|.
+ * The result of clipping is stored in |aBackPoints| and |aFrontPoints|.
+ */
+template <class Units>
+void ClipPointsWithPlane(const nsTArray<Point4DTyped<Units>>& aPoints,
+ const Point4DTyped<Units>& aNormal,
+ const nsTArray<float>& aDots,
+ nsTArray<Point4DTyped<Units>>& aBackPoints,
+ nsTArray<Point4DTyped<Units>>& aFrontPoints) {
+ static const auto Sign = [](const float& f) {
+ if (f > 0.0f) return 1;
+ if (f < 0.0f) return -1;
+ return 0;
+ };
+
+ const size_t pointCount = aPoints.Length();
+ for (size_t i = 0; i < pointCount; ++i) {
+ size_t j = (i + 1) % pointCount;
+
+ const Point4DTyped<Units>& a = aPoints[i];
+ const Point4DTyped<Units>& b = aPoints[j];
+ const float dotA = aDots[i];
+ const float dotB = aDots[j];
+
+ // The point is in front of or on the plane.
+ if (dotA >= 0) {
+ aFrontPoints.AppendElement(a);
+ }
+
+ // The point is behind or on the plane.
+ if (dotA <= 0) {
+ aBackPoints.AppendElement(a);
+ }
+
+ // If the sign of the dot products changes between two consecutive
+ // vertices, then the plane intersects with the polygon edge.
+ // The case where the polygon edge is within the plane is handled above.
+ if (Sign(dotA) && Sign(dotB) && Sign(dotA) != Sign(dotB)) {
+ // Calculate the line segment and plane intersection point.
+ const Point4DTyped<Units> ab = b - a;
+ const float dotAB = ab.DotProduct(aNormal);
+ const float t = -dotA / dotAB;
+ const Point4DTyped<Units> p = a + (ab * t);
+
+ // Add the intersection point to both polygons.
+ aBackPoints.AppendElement(p);
+ aFrontPoints.AppendElement(p);
+ }
+ }
+}
+
+/**
+ * PolygonTyped stores the points of a convex planar polygon.
+ */
+template <class Units>
+class PolygonTyped {
+ typedef Point3DTyped<Units> Point3DType;
+ typedef Point4DTyped<Units> Point4DType;
+
+ public:
+ PolygonTyped() = default;
+
+ explicit PolygonTyped(const nsTArray<Point4DType>& aPoints,
+ const Point4DType& aNormal = DefaultNormal())
+ : mNormal(aNormal), mPoints(aPoints) {}
+
+ explicit PolygonTyped(nsTArray<Point4DType>&& aPoints,
+ const Point4DType& aNormal = DefaultNormal())
+ : mNormal(aNormal), mPoints(std::move(aPoints)) {}
+
+ explicit PolygonTyped(const std::initializer_list<Point4DType>& aPoints,
+ const Point4DType& aNormal = DefaultNormal())
+ : mNormal(aNormal), mPoints(aPoints) {
+#ifdef DEBUG
+ EnsurePlanarPolygon();
+#endif
+ }
+
+ /**
+ * Returns the smallest 2D rectangle that can fully cover the polygon.
+ */
+ RectTyped<Units> BoundingBox() const {
+ if (mPoints.IsEmpty()) {
+ return RectTyped<Units>();
+ }
+
+ float minX, maxX, minY, maxY;
+ minX = maxX = mPoints[0].x;
+ minY = maxY = mPoints[0].y;
+
+ for (const Point4DType& point : mPoints) {
+ minX = std::min(point.x, minX);
+ maxX = std::max(point.x, maxX);
+
+ minY = std::min(point.y, minY);
+ maxY = std::max(point.y, maxY);
+ }
+
+ return RectTyped<Units>(minX, minY, maxX - minX, maxY - minY);
+ }
+
+ /**
+ * Clips the polygon against the given 2D rectangle.
+ */
+ PolygonTyped<Units> ClipPolygon(const RectTyped<Units>& aRect) const {
+ if (aRect.IsEmpty()) {
+ return PolygonTyped<Units>();
+ }
+
+ return ClipPolygon(FromRect(aRect));
+ }
+
+ /**
+ * Clips this polygon against |aPolygon| in 2D and returns a new polygon.
+ */
+ PolygonTyped<Units> ClipPolygon(const PolygonTyped<Units>& aPolygon) const {
+ const nsTArray<Point4DType>& points = aPolygon.GetPoints();
+
+ if (mPoints.IsEmpty() || points.IsEmpty()) {
+ return PolygonTyped<Units>();
+ }
+
+ nsTArray<Point4DType> clippedPoints(mPoints.Clone());
+
+ size_t pos, neg;
+ nsTArray<Point4DType> backPoints(4), frontPoints(4);
+
+ // Iterate over all the edges of the clipping polygon |aPolygon| and clip
+ // this polygon against the edges.
+ const size_t pointCount = points.Length();
+ for (size_t i = 0; i < pointCount; ++i) {
+ const Point4DType p1 = points[(i + 1) % pointCount];
+ const Point4DType p2 = points[i];
+
+ // Calculate the normal for the edge defined by |p1| and |p2|.
+ const Point4DType normal(p2.y - p1.y, p1.x - p2.x, 0.0f, 0.0f);
+
+ // Calculate the distances between the points of the polygon and the
+ // plane defined by |aPolygon|.
+ const nsTArray<float> distances =
+ CalculatePointPlaneDistances(clippedPoints, normal, p1, pos, neg);
+
+ backPoints.ClearAndRetainStorage();
+ frontPoints.ClearAndRetainStorage();
+
+ // Clip the polygon points using the previously calculated distances.
+ ClipPointsWithPlane(clippedPoints, normal, distances, backPoints,
+ frontPoints);
+
+ // Only use the points behind the clipping plane.
+ clippedPoints = std::move(backPoints);
+
+ if (clippedPoints.Length() < 3) {
+ // The clipping created a polygon with no area.
+ return PolygonTyped<Units>();
+ }
+ }
+
+ return PolygonTyped<Units>(std::move(clippedPoints), mNormal);
+ }
+
+ /**
+ * Returns a new polygon containing the bounds of the given 2D rectangle.
+ */
+ static PolygonTyped<Units> FromRect(const RectTyped<Units>& aRect) {
+ nsTArray<Point4DType> points{
+ Point4DType(aRect.X(), aRect.Y(), 0.0f, 1.0f),
+ Point4DType(aRect.X(), aRect.YMost(), 0.0f, 1.0f),
+ Point4DType(aRect.XMost(), aRect.YMost(), 0.0f, 1.0f),
+ Point4DType(aRect.XMost(), aRect.Y(), 0.0f, 1.0f)};
+
+ return PolygonTyped<Units>(std::move(points));
+ }
+
+ const Point4DType& GetNormal() const { return mNormal; }
+
+ const nsTArray<Point4DType>& GetPoints() const { return mPoints; }
+
+ bool IsEmpty() const {
+ // If the polygon has less than three points, it has no visible area.
+ return mPoints.Length() < 3;
+ }
+
+ /**
+ * Returns a list of triangles covering the polygon.
+ */
+ nsTArray<TriangleTyped<Units>> ToTriangles() const {
+ nsTArray<TriangleTyped<Units>> triangles;
+
+ if (IsEmpty()) {
+ return triangles;
+ }
+
+ // This fan triangulation method only works for convex polygons.
+ for (size_t i = 1; i < mPoints.Length() - 1; ++i) {
+ TriangleTyped<Units> triangle(Point(mPoints[0].x, mPoints[0].y),
+ Point(mPoints[i].x, mPoints[i].y),
+ Point(mPoints[i + 1].x, mPoints[i + 1].y));
+ triangles.AppendElement(std::move(triangle));
+ }
+
+ return triangles;
+ }
+
+ void TransformToLayerSpace(const Matrix4x4Typed<Units, Units>& aTransform) {
+ TransformPoints(aTransform, true);
+ mNormal = DefaultNormal();
+ }
+
+ void TransformToScreenSpace(
+ const Matrix4x4Typed<Units, Units>& aTransform,
+ const Matrix4x4Typed<Units, Units>& aInverseTransform) {
+ TransformPoints(aTransform, false);
+
+ // Perspective projection transformation might produce points with w <= 0,
+ // so we need to clip these points.
+ mPoints = ClipPointsAtInfinity(mPoints);
+
+ // Normal vectors should be transformed using inverse transpose.
+ mNormal = aInverseTransform.TransposeTransform4D(mNormal);
+ }
+
+ void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform) {
+ MOZ_ASSERT(!aTransform.IsSingular());
+
+ TransformToScreenSpace(aTransform, aTransform.Inverse());
+ }
+
+ private:
+ static Point4DType DefaultNormal() {
+ return Point4DType(0.0f, 0.0f, 1.0f, 0.0f);
+ }
+
+#ifdef DEBUG
+ void EnsurePlanarPolygon() const {
+ if (mPoints.Length() <= 3) {
+ // Polygons with three or less points are guaranteed to be planar.
+ return;
+ }
+
+ // This normal calculation method works only for planar polygons.
+ // The resulting normal vector will point towards the viewer when the
+ // polygon has a counter-clockwise winding order from the perspective
+ // of the viewer.
+ Point3DType normal;
+ const Point3DType p0 = mPoints[0].As3DPoint();
+
+ for (size_t i = 1; i < mPoints.Length() - 1; ++i) {
+ const Point3DType p1 = mPoints[i].As3DPoint();
+ const Point3DType p2 = mPoints[i + 1].As3DPoint();
+
+ normal += (p1 - p0).CrossProduct(p2 - p0);
+ }
+
+ // Ensure that at least one component is greater than zero.
+ // This avoids division by zero when normalizing the vector.
+ bool hasNonZeroComponent = std::abs(normal.x) > 0.0f ||
+ std::abs(normal.y) > 0.0f ||
+ std::abs(normal.z) > 0.0f;
+
+ MOZ_ASSERT(hasNonZeroComponent);
+
+ normal.Normalize();
+
+ // Ensure that the polygon is planar.
+ // http://mathworld.wolfram.com/Point-PlaneDistance.html
+ const float epsilon = 0.01f;
+ for (const Point4DType& point : mPoints) {
+ const Point3DType p1 = point.As3DPoint();
+ const float d = normal.DotProduct(p1 - p0);
+
+ MOZ_ASSERT(std::abs(d) < epsilon);
+ }
+ }
+#endif
+
+ void TransformPoints(const Matrix4x4Typed<Units, Units>& aTransform,
+ const bool aDivideByW) {
+ for (Point4DType& point : mPoints) {
+ point = aTransform.TransformPoint(point);
+
+ if (aDivideByW && point.w > 0.0f) {
+ point = point / point.w;
+ }
+ }
+ }
+
+ Point4DType mNormal;
+ CopyableTArray<Point4DType> mPoints;
+};
+
+typedef PolygonTyped<UnknownUnits> Polygon;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_POLYGON_H */
diff --git a/gfx/2d/QuartzSupport.h b/gfx/2d/QuartzSupport.h
new file mode 100644
index 0000000000..10571dbcfb
--- /dev/null
+++ b/gfx/2d/QuartzSupport.h
@@ -0,0 +1,106 @@
+/* -*- 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 nsCoreAnimationSupport_h__
+#define nsCoreAnimationSupport_h__
+#ifdef XP_MACOSX
+
+# import <OpenGL/OpenGL.h>
+# import <OpenGL/gl.h>
+# import "ApplicationServices/ApplicationServices.h"
+# include "gfxTypes.h"
+# include "mozilla/RefPtr.h"
+# include "mozilla/gfx/MacIOSurface.h"
+# include "nsError.h"
+
+// Get the system color space.
+CGColorSpaceRef CreateSystemColorSpace();
+
+// Manages a CARenderer
+struct _CGLContextObject;
+
+enum AllowOfflineRendererEnum {
+ ALLOW_OFFLINE_RENDERER,
+ DISALLOW_OFFLINE_RENDERER
+};
+
+class nsCARenderer : public mozilla::RefCounted<nsCARenderer> {
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(nsCARenderer)
+ nsCARenderer()
+ : mCARenderer(nullptr),
+ mWrapperCALayer(nullptr),
+ mFBOTexture(0),
+ mOpenGLContext(nullptr),
+ mCGImage(nullptr),
+ mCGData(nullptr),
+ mIOSurface(nullptr),
+ mFBO(0),
+ mIOTexture(0),
+ mUnsupportedWidth(UINT32_MAX),
+ mUnsupportedHeight(UINT32_MAX),
+ mAllowOfflineRenderer(DISALLOW_OFFLINE_RENDERER),
+ mContentsScaleFactor(1.0) {}
+ ~nsCARenderer();
+ // aWidth and aHeight are 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. Multiply
+ // display pixels by aContentsScaleFactor to get device pixels.
+ nsresult SetupRenderer(void* aCALayer, int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ AllowOfflineRendererEnum aAllowOfflineRenderer);
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // aContentsScaleFactor to get device pixels.
+ nsresult Render(int aWidth, int aHeight, double aContentsScaleFactor,
+ CGImageRef* aOutCAImage);
+ bool isInit() { return mCARenderer != nullptr; }
+ /*
+ * Render the CALayer to an IOSurface. If no IOSurface
+ * is attached then an internal pixel buffer will be
+ * used.
+ */
+ void AttachIOSurface(MacIOSurface* aSurface);
+ IOSurfaceID GetIOSurfaceID();
+ // aX, aY, aWidth and aHeight are in "display pixels". Multiply by
+ // surf->GetContentsScaleFactor() to get device pixels.
+ static nsresult DrawSurfaceToCGContext(CGContextRef aContext,
+ MacIOSurface* surf,
+ CGColorSpaceRef aColorSpace, int aX,
+ int aY, size_t aWidth, size_t aHeight);
+
+ // Remove & Add the layer without destroying
+ // the renderer for fast back buffer swapping.
+ void DetachCALayer();
+ void AttachCALayer(void* aCALayer);
+# ifdef DEBUG
+ static void SaveToDisk(MacIOSurface* surf);
+# endif
+ private:
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ void SetBounds(int aWidth, int aHeight);
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ void SetViewport(int aWidth, int aHeight);
+ void Destroy();
+
+ void* mCARenderer;
+ void* mWrapperCALayer;
+ GLuint mFBOTexture;
+ _CGLContextObject* mOpenGLContext;
+ CGImageRef mCGImage;
+ void* mCGData;
+ RefPtr<MacIOSurface> mIOSurface;
+ uint32_t mFBO;
+ uint32_t mIOTexture;
+ int mUnsupportedWidth;
+ int mUnsupportedHeight;
+ AllowOfflineRendererEnum mAllowOfflineRenderer;
+ double mContentsScaleFactor;
+};
+
+#endif // XP_MACOSX
+#endif // nsCoreAnimationSupport_h__
diff --git a/gfx/2d/QuartzSupport.mm b/gfx/2d/QuartzSupport.mm
new file mode 100644
index 0000000000..91b754e380
--- /dev/null
+++ b/gfx/2d/QuartzSupport.mm
@@ -0,0 +1,582 @@
+/* -*- 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 "QuartzSupport.h"
+#include "nsDebug.h"
+#include "MacIOSurface.h"
+#include "mozilla/Sprintf.h"
+
+#import <QuartzCore/QuartzCore.h>
+#import <AppKit/NSOpenGL.h>
+#import <OpenGL/CGLIOSurface.h>
+#include <dlfcn.h>
+#include "GLDefs.h"
+
+#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"
+
+@interface CALayer (ContentsScale)
+- (double)contentsScale;
+- (void)setContentsScale:(double)scale;
+@end
+
+CGColorSpaceRef CreateSystemColorSpace() {
+ CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
+ if (!cspace) {
+ cspace = ::CGColorSpaceCreateDeviceRGB();
+ }
+ return cspace;
+}
+
+nsCARenderer::~nsCARenderer() { Destroy(); }
+
+static void cgdata_release_callback(void* aCGData, const void* data, size_t size) {
+ if (aCGData) {
+ free(aCGData);
+ }
+}
+
+void nsCARenderer::Destroy() {
+ if (mCARenderer) {
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ // Bug 556453:
+ // Explicitly remove the layer from the renderer
+ // otherwise it does not always happen right away.
+ caRenderer.layer = nullptr;
+ [caRenderer release];
+ }
+ if (mWrapperCALayer) {
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ [wrapperLayer release];
+ }
+ if (mOpenGLContext) {
+ if (mFBO || mIOTexture || mFBOTexture) {
+ // Release these resources with the context that allocated them
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+
+ if (mFBOTexture) {
+ ::glDeleteTextures(1, &mFBOTexture);
+ }
+ if (mIOTexture) {
+ ::glDeleteTextures(1, &mIOTexture);
+ }
+ if (mFBO) {
+ ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ ::glDeleteFramebuffersEXT(1, &mFBO);
+ }
+
+ if (oldContext) ::CGLSetCurrentContext(oldContext);
+ }
+ ::CGLDestroyContext((CGLContextObj)mOpenGLContext);
+ }
+ if (mCGImage) {
+ ::CGImageRelease(mCGImage);
+ }
+ // mCGData is deallocated by cgdata_release_callback
+
+ mCARenderer = nil;
+ mWrapperCALayer = nil;
+ mFBOTexture = 0;
+ mOpenGLContext = nullptr;
+ mCGImage = nullptr;
+ mIOSurface = nullptr;
+ mFBO = 0;
+ mIOTexture = 0;
+}
+
+nsresult nsCARenderer::SetupRenderer(void* aCALayer, int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ AllowOfflineRendererEnum aAllowOfflineRenderer) {
+ mAllowOfflineRenderer = aAllowOfflineRenderer;
+
+ if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0) return NS_ERROR_FAILURE;
+
+ if (aWidth == mUnsupportedWidth && aHeight == mUnsupportedHeight) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CGLPixelFormatAttribute attributes[] = {kCGLPFAAccelerated, kCGLPFADepthSize,
+ (CGLPixelFormatAttribute)24, kCGLPFAAllowOfflineRenderers,
+ (CGLPixelFormatAttribute)0};
+
+ if (mAllowOfflineRenderer == DISALLOW_OFFLINE_RENDERER) {
+ attributes[3] = (CGLPixelFormatAttribute)0;
+ }
+
+ GLint screen;
+ CGLPixelFormatObj format;
+ if (::CGLChoosePixelFormat(attributes, &format, &screen) != kCGLNoError) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (::CGLCreateContext(format, nullptr, &mOpenGLContext) != kCGLNoError) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ ::CGLDestroyPixelFormat(format);
+
+ CARenderer* caRenderer = [[CARenderer rendererWithCGLContext:mOpenGLContext options:nil] retain];
+ if (caRenderer == nil) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ CALayer* wrapperCALayer = [[CALayer layer] retain];
+ if (wrapperCALayer == nil) {
+ [caRenderer release];
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ mCARenderer = caRenderer;
+ mWrapperCALayer = wrapperCALayer;
+ caRenderer.layer = wrapperCALayer;
+ [wrapperCALayer addSublayer:(CALayer*)aCALayer];
+ mContentsScaleFactor = aContentsScaleFactor;
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ SetBounds(aWidth, aHeight);
+
+ // We target rendering to a CGImage if no shared IOSurface are given.
+ if (!mIOSurface) {
+ mCGData = malloc(aWidth * intScaleFactor * aHeight * 4 * intScaleFactor);
+ if (!mCGData) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ memset(mCGData, 0, aWidth * intScaleFactor * aHeight * 4 * intScaleFactor);
+
+ CGDataProviderRef dataProvider = nullptr;
+ dataProvider = ::CGDataProviderCreateWithData(
+ mCGData, mCGData, aHeight * intScaleFactor * aWidth * 4 * intScaleFactor,
+ cgdata_release_callback);
+ if (!dataProvider) {
+ cgdata_release_callback(mCGData, mCGData,
+ aHeight * intScaleFactor * aWidth * 4 * intScaleFactor);
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ CGColorSpaceRef colorSpace = CreateSystemColorSpace();
+
+ mCGImage = ::CGImageCreate(aWidth * intScaleFactor, aHeight * intScaleFactor, 8, 32,
+ aWidth * intScaleFactor * 4, colorSpace,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ dataProvider, nullptr, true, kCGRenderingIntentDefault);
+
+ ::CGDataProviderRelease(dataProvider);
+ ::CGColorSpaceRelease(colorSpace);
+ if (!mCGImage) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+
+ if (mIOSurface) {
+ // Create the IOSurface mapped texture.
+ ::glGenTextures(1, &mIOTexture);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mIOTexture);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ ::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA,
+ aWidth * intScaleFactor, aHeight * intScaleFactor, GL_BGRA,
+ GL_UNSIGNED_INT_8_8_8_8_REV, mIOSurface->GetIOSurfaceRef().get(), 0);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+ } else {
+ ::glGenTextures(1, &mFBOTexture);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFBOTexture);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+ }
+
+ // Create the fbo
+ ::glGenFramebuffersEXT(1, &mFBO);
+ ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO);
+ if (mIOSurface) {
+ ::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, mIOTexture, 0);
+ } else {
+ ::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, mFBOTexture, 0);
+ }
+
+ // Make sure that the Framebuffer configuration is supported on the client machine
+ GLenum fboStatus;
+ fboStatus = ::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+ if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT) {
+ NS_ERROR("FBO not supported");
+ if (oldContext) ::CGLSetCurrentContext(oldContext);
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ SetViewport(aWidth, aHeight);
+
+ GLenum result = ::glGetError();
+ if (result != GL_NO_ERROR) {
+ NS_ERROR("Unexpected OpenGL Error");
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ if (oldContext) ::CGLSetCurrentContext(oldContext);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (oldContext) ::CGLSetCurrentContext(oldContext);
+
+ return NS_OK;
+}
+
+void nsCARenderer::SetBounds(int aWidth, int aHeight) {
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* pluginLayer = (CALayer*)[sublayers objectAtIndex:0];
+
+ // Create a transaction and disable animations
+ // to make the position update instant.
+ [CATransaction begin];
+ NSMutableDictionary* newActions = [[NSMutableDictionary alloc]
+ initWithObjectsAndKeys:[NSNull null], @"onOrderIn", [NSNull null], @"onOrderOut",
+ [NSNull null], @"sublayers", [NSNull null], @"contents", [NSNull null],
+ @"position", [NSNull null], @"bounds", nil];
+ wrapperLayer.actions = newActions;
+ [newActions release];
+
+ // If we're in HiDPI mode, mContentsScaleFactor will (presumably) be 2.0.
+ // For some reason, to make things work properly in HiDPI mode we need to
+ // make caRenderer's 'bounds' and 'layer' different sizes -- to set 'bounds'
+ // to the size of 'layer's backing store. And to avoid this possibly
+ // confusing the plugin, we need to hide it's effects from the plugin by
+ // making pluginLayer (usually the CALayer* provided by the plugin) a
+ // sublayer of our own wrapperLayer (see bug 829284).
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ [CATransaction setValue:[NSNumber numberWithFloat:0.0f] forKey:kCATransactionAnimationDuration];
+ [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
+ [wrapperLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
+ [wrapperLayer setPosition:CGPointMake(aWidth / 2.0, aHeight / 2.0)];
+ [pluginLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
+ [pluginLayer setFrame:CGRectMake(0, 0, aWidth, aHeight)];
+ caRenderer.bounds = CGRectMake(0, 0, aWidth * intScaleFactor, aHeight * intScaleFactor);
+ if (mContentsScaleFactor != 1.0) {
+ CGAffineTransform affineTransform = [wrapperLayer affineTransform];
+ affineTransform.a = mContentsScaleFactor;
+ affineTransform.d = mContentsScaleFactor;
+ affineTransform.tx = ((double)aWidth) / mContentsScaleFactor;
+ affineTransform.ty = ((double)aHeight) / mContentsScaleFactor;
+ [wrapperLayer setAffineTransform:affineTransform];
+ } else {
+ // These settings are the default values. But they might have been
+ // changed as above if we were previously running in a HiDPI mode
+ // (i.e. if we just switched from that to a non-HiDPI mode).
+ [wrapperLayer setAffineTransform:CGAffineTransformIdentity];
+ }
+ [CATransaction commit];
+}
+
+void nsCARenderer::SetViewport(int aWidth, int aHeight) {
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ aWidth *= intScaleFactor;
+ aHeight *= intScaleFactor;
+
+ ::glViewport(0.0, 0.0, aWidth, aHeight);
+ ::glMatrixMode(GL_PROJECTION);
+ ::glLoadIdentity();
+ ::glOrtho(0.0, aWidth, 0.0, aHeight, -1, 1);
+
+ // Render upside down to speed up CGContextDrawImage
+ ::glTranslatef(0.0f, aHeight, 0.0);
+ ::glScalef(1.0, -1.0, 1.0);
+}
+
+void nsCARenderer::AttachIOSurface(MacIOSurface* aSurface) {
+ if (mIOSurface && aSurface->GetIOSurfaceID() == mIOSurface->GetIOSurfaceID()) {
+ return;
+ }
+
+ mIOSurface = aSurface;
+
+ // Update the framebuffer and viewport
+ if (mCARenderer) {
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ int width = caRenderer.bounds.size.width / intScaleFactor;
+ int height = caRenderer.bounds.size.height / intScaleFactor;
+
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mIOTexture);
+ ::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA,
+ mIOSurface->GetDevicePixelWidth(), mIOSurface->GetDevicePixelHeight(),
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ mIOSurface->GetIOSurfaceRef().get(), 0);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+
+ // Rebind the FBO to make it live
+ ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO);
+
+ if (static_cast<int>(mIOSurface->GetWidth()) != width ||
+ static_cast<int>(mIOSurface->GetHeight()) != height) {
+ width = mIOSurface->GetWidth();
+ height = mIOSurface->GetHeight();
+ SetBounds(width, height);
+ SetViewport(width, height);
+ }
+
+ if (oldContext) {
+ ::CGLSetCurrentContext(oldContext);
+ }
+ }
+}
+
+IOSurfaceID nsCARenderer::GetIOSurfaceID() {
+ if (!mIOSurface) {
+ return 0;
+ }
+
+ return mIOSurface->GetIOSurfaceID();
+}
+
+nsresult nsCARenderer::Render(int aWidth, int aHeight, double aContentsScaleFactor,
+ CGImageRef* aOutCGImage) {
+ if (!aOutCGImage && !mIOSurface) {
+ NS_ERROR("No target destination for rendering");
+ } else if (aOutCGImage) {
+ // We are expected to return a CGImageRef, we will set
+ // it to nullptr in case we fail before the image is ready.
+ *aOutCGImage = nullptr;
+ }
+
+ if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0) return NS_OK;
+
+ if (!mCARenderer || !mWrapperCALayer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ size_t intScaleFactor = ceil(aContentsScaleFactor);
+ int renderer_width = caRenderer.bounds.size.width / intScaleFactor;
+ int renderer_height = caRenderer.bounds.size.height / intScaleFactor;
+
+ if (renderer_width != aWidth || renderer_height != aHeight ||
+ mContentsScaleFactor != aContentsScaleFactor) {
+ // XXX: This should be optimized to not rescale the buffer
+ // if we are resizing down.
+ // caLayer may be the CALayer* provided by the plugin, so we need to
+ // preserve it across the call to Destroy().
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* caLayer = (CALayer*)[sublayers objectAtIndex:0];
+ // mIOSurface is set by AttachIOSurface(), not by SetupRenderer(). So
+ // since it may have been set by a prior call to AttachIOSurface(), we
+ // need to preserve it across the call to Destroy().
+ RefPtr<MacIOSurface> ioSurface = mIOSurface;
+ Destroy();
+ mIOSurface = ioSurface;
+ if (SetupRenderer(caLayer, aWidth, aHeight, aContentsScaleFactor, mAllowOfflineRenderer) !=
+ NS_OK) {
+ return NS_ERROR_FAILURE;
+ }
+
+ caRenderer = (CARenderer*)mCARenderer;
+ }
+
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+ if (!mIOSurface) {
+ // If no shared IOSurface is given render to our own
+ // texture for readback.
+ ::glGenTextures(1, &mFBOTexture);
+ }
+
+ GLenum result = ::glGetError();
+ if (result != GL_NO_ERROR) {
+ NS_ERROR("Unexpected OpenGL Error");
+ Destroy();
+ if (oldContext) ::CGLSetCurrentContext(oldContext);
+ return NS_ERROR_FAILURE;
+ }
+
+ ::glClearColor(0.0, 0.0, 0.0, 0.0);
+ ::glClear(GL_COLOR_BUFFER_BIT);
+
+ [CATransaction commit];
+ double caTime = ::CACurrentMediaTime();
+ [caRenderer beginFrameAtTime:caTime timeStamp:nullptr];
+ [caRenderer addUpdateRect:CGRectMake(0, 0, aWidth * intScaleFactor, aHeight * intScaleFactor)];
+ [caRenderer render];
+ [caRenderer endFrame];
+
+ // Read the data back either to the IOSurface or mCGImage.
+ if (mIOSurface) {
+ ::glFlush();
+ } else {
+ ::glPixelStorei(GL_PACK_ALIGNMENT, 4);
+ ::glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ ::glPixelStorei(GL_PACK_SKIP_ROWS, 0);
+ ::glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
+
+ ::glReadPixels(0.0f, 0.0f, aWidth * intScaleFactor, aHeight * intScaleFactor, GL_BGRA,
+ GL_UNSIGNED_BYTE, mCGData);
+
+ *aOutCGImage = mCGImage;
+ }
+
+ if (oldContext) {
+ ::CGLSetCurrentContext(oldContext);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsCARenderer::DrawSurfaceToCGContext(CGContextRef aContext, MacIOSurface* surf,
+ CGColorSpaceRef aColorSpace, int aX, int aY,
+ size_t aWidth, size_t aHeight) {
+ surf->Lock();
+ size_t bytesPerRow = surf->GetBytesPerRow();
+ size_t ioWidth = surf->GetWidth();
+ size_t ioHeight = surf->GetHeight();
+
+ // We get rendering glitches if we use a width/height that falls
+ // outside of the IOSurface.
+ if (aWidth + aX > ioWidth) aWidth = ioWidth - aX;
+ if (aHeight + aY > ioHeight) aHeight = ioHeight - aY;
+
+ if (aX < 0 || static_cast<size_t>(aX) >= ioWidth || aY < 0 ||
+ static_cast<size_t>(aY) >= ioHeight) {
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+
+ void* ioData = surf->GetBaseAddress();
+ CGDataProviderRef dataProvider =
+ ::CGDataProviderCreateWithData(ioData, ioData, ioHeight * (bytesPerRow)*4,
+ nullptr); // No release callback
+ if (!dataProvider) {
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+
+ CGImageRef cgImage = ::CGImageCreate(ioWidth, ioHeight, 8, 32, bytesPerRow, aColorSpace,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ dataProvider, nullptr, true, kCGRenderingIntentDefault);
+ ::CGDataProviderRelease(dataProvider);
+ if (!cgImage) {
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+ CGImageRef subImage =
+ ::CGImageCreateWithImageInRect(cgImage, ::CGRectMake(aX, aY, aWidth, aHeight));
+ if (!subImage) {
+ ::CGImageRelease(cgImage);
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+
+ ::CGContextScaleCTM(aContext, 1.0f, -1.0f);
+ ::CGContextDrawImage(aContext, CGRectMake(aX, -(CGFloat)aY - (CGFloat)aHeight, aWidth, aHeight),
+ subImage);
+
+ ::CGImageRelease(subImage);
+ ::CGImageRelease(cgImage);
+ surf->Unlock();
+ return NS_OK;
+}
+
+void nsCARenderer::DetachCALayer() {
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* oldLayer = (CALayer*)[sublayers objectAtIndex:0];
+ [oldLayer removeFromSuperlayer];
+}
+
+void nsCARenderer::AttachCALayer(void* aCALayer) {
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* oldLayer = (CALayer*)[sublayers objectAtIndex:0];
+ [oldLayer removeFromSuperlayer];
+ [wrapperLayer addSublayer:(CALayer*)aCALayer];
+}
+
+#ifdef DEBUG
+
+int sSaveToDiskSequence = 0;
+void nsCARenderer::SaveToDisk(MacIOSurface* surf) {
+ surf->Lock();
+ size_t bytesPerRow = surf->GetBytesPerRow();
+ size_t ioWidth = surf->GetWidth();
+ size_t ioHeight = surf->GetHeight();
+ void* ioData = surf->GetBaseAddress();
+ CGDataProviderRef dataProvider =
+ ::CGDataProviderCreateWithData(ioData, ioData, ioHeight * (bytesPerRow)*4,
+ nullptr); // No release callback
+ if (!dataProvider) {
+ surf->Unlock();
+ return;
+ }
+
+ CGColorSpaceRef colorSpace = CreateSystemColorSpace();
+ CGImageRef cgImage = ::CGImageCreate(ioWidth, ioHeight, 8, 32, bytesPerRow, colorSpace,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ dataProvider, nullptr, true, kCGRenderingIntentDefault);
+ ::CGDataProviderRelease(dataProvider);
+ ::CGColorSpaceRelease(colorSpace);
+ if (!cgImage) {
+ surf->Unlock();
+ return;
+ }
+
+ char cstr[1000];
+ SprintfLiteral(cstr, "file:///Users/benoitgirard/debug/iosurface_%i.png", ++sSaveToDiskSequence);
+
+ CFStringRef cfStr =
+ ::CFStringCreateWithCString(kCFAllocatorDefault, cstr, kCFStringEncodingMacRoman);
+
+ printf("Exporting: %s\n", cstr);
+ CFURLRef url = ::CFURLCreateWithString(nullptr, cfStr, nullptr);
+ ::CFRelease(cfStr);
+
+ CFStringRef type = kUTTypePNG;
+ size_t count = 1;
+ CFDictionaryRef options = nullptr;
+ CGImageDestinationRef dest = ::CGImageDestinationCreateWithURL(url, type, count, options);
+ ::CFRelease(url);
+
+ ::CGImageDestinationAddImage(dest, cgImage, nullptr);
+
+ ::CGImageDestinationFinalize(dest);
+ ::CFRelease(dest);
+ ::CGImageRelease(cgImage);
+
+ surf->Unlock();
+
+ return;
+}
+
+#endif
diff --git a/gfx/2d/Quaternion.cpp b/gfx/2d/Quaternion.cpp
new file mode 100644
index 0000000000..1921f78ae1
--- /dev/null
+++ b/gfx/2d/Quaternion.cpp
@@ -0,0 +1,23 @@
+/* -*- 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 "Quaternion.h"
+#include "Matrix.h"
+#include "Tools.h"
+#include <algorithm>
+#include <ostream>
+#include <math.h>
+
+namespace mozilla {
+namespace gfx {
+
+std::ostream& operator<<(std::ostream& aStream, const Quaternion& aQuat) {
+ return aStream << "< " << aQuat.x << " " << aQuat.y << " " << aQuat.z << " "
+ << aQuat.w << ">";
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/Quaternion.h b/gfx/2d/Quaternion.h
new file mode 100644
index 0000000000..a952612b04
--- /dev/null
+++ b/gfx/2d/Quaternion.h
@@ -0,0 +1,150 @@
+/* -*- 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_QUATERNION_H_
+#define MOZILLA_GFX_QUATERNION_H_
+
+#include "Types.h"
+#include <math.h>
+#include <ostream>
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/gfx/Point.h"
+
+namespace mozilla {
+namespace gfx {
+
+template <class T>
+class BaseQuaternion {
+ public:
+ BaseQuaternion() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
+
+ BaseQuaternion(T aX, T aY, T aZ, T aW) : x(aX), y(aY), z(aZ), w(aW) {}
+
+ BaseQuaternion(const BaseQuaternion& aOther) {
+ x = aOther.x;
+ y = aOther.y;
+ z = aOther.z;
+ w = aOther.w;
+ }
+
+ T x, y, z, w;
+
+ template <class U>
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseQuaternion<U>& aQuat);
+
+ void Set(T aX, T aY, T aZ, T aW) {
+ x = aX;
+ y = aY;
+ z = aZ;
+ w = aW;
+ }
+
+ // Assumes upper 3x3 of aMatrix is a pure rotation matrix (no scaling)
+ void SetFromRotationMatrix(
+ const Matrix4x4Typed<UnknownUnits, UnknownUnits, T>& m) {
+ const T trace = m._11 + m._22 + m._33 + 1.0f;
+
+ if (trace > 1e-4) {
+ const T s = 0.5f / sqrt(trace);
+ w = 0.25f / s;
+ x = (m._23 - m._32) * s;
+ y = (m._31 - m._13) * s;
+ z = (m._12 - m._21) * s;
+ } else if (m._11 > m._22 && m._11 > m._33) {
+ const T s = 2.0f * sqrt(1.0f + m._11 - m._22 - m._33);
+ w = (m._23 - m._32) / s;
+ x = 0.25f * s;
+ y = (m._21 + m._12) / s;
+ z = (m._31 + m._13) / s;
+ } else if (m._22 > m._33) {
+ const T s = 2.0 * sqrt(1.0f + m._22 - m._11 - m._33);
+ w = (m._31 - m._13) / s;
+ x = (m._21 + m._12) / s;
+ y = 0.25f * s;
+ z = (m._32 + m._23) / s;
+ } else {
+ const T s = 2.0 * sqrt(1.0f + m._33 - m._11 - m._22);
+ w = (m._12 - m._21) / s;
+ x = (m._31 + m._13) / s;
+ y = (m._32 + m._23) / s;
+ z = 0.25f * s;
+ }
+
+ Normalize();
+ }
+
+ // result = this * aQuat
+ BaseQuaternion operator*(const BaseQuaternion& aQuat) const {
+ BaseQuaternion o;
+ const T bx = aQuat.x, by = aQuat.y, bz = aQuat.z, bw = aQuat.w;
+
+ o.x = x * bw + w * bx + y * bz - z * by;
+ o.y = y * bw + w * by + z * bx - x * bz;
+ o.z = z * bw + w * bz + x * by - y * bx;
+ o.w = w * bw - x * bx - y * by - z * bz;
+ return o;
+ }
+
+ BaseQuaternion& operator*=(const BaseQuaternion& aQuat) {
+ *this = *this * aQuat;
+ return *this;
+ }
+
+ T Length() const { return sqrt(x * x + y * y + z * z + w * w); }
+
+ BaseQuaternion& Conjugate() {
+ x *= -1.f;
+ y *= -1.f;
+ z *= -1.f;
+ return *this;
+ }
+
+ BaseQuaternion& Normalize() {
+ T l = Length();
+ if (l) {
+ l = 1.0f / l;
+ x *= l;
+ y *= l;
+ z *= l;
+ w *= l;
+ } else {
+ x = y = z = 0.f;
+ w = 1.f;
+ }
+ return *this;
+ }
+
+ BaseQuaternion& Invert() { return Conjugate().Normalize(); }
+
+ BaseQuaternion Inverse() const {
+ BaseQuaternion q = *this;
+ q.Invert();
+ return q;
+ }
+
+ Point3DTyped<UnknownUnits, T> RotatePoint(
+ const Point3DTyped<UnknownUnits, T>& aPoint) const {
+ T uvx = T(2.0) * (y * aPoint.z - z * aPoint.y);
+ T uvy = T(2.0) * (z * aPoint.x - x * aPoint.z);
+ T uvz = T(2.0) * (x * aPoint.y - y * aPoint.x);
+
+ return Point3DTyped<UnknownUnits, T>(
+ aPoint.x + w * uvx + y * uvz - z * uvy,
+ aPoint.y + w * uvy + z * uvx - x * uvz,
+ aPoint.z + w * uvz + x * uvy - y * uvx);
+ }
+};
+
+typedef BaseQuaternion<Float> Quaternion;
+typedef BaseQuaternion<Double> QuaternionDouble;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/RadialGradientEffectD2D1.cpp b/gfx/2d/RadialGradientEffectD2D1.cpp
new file mode 100644
index 0000000000..34a5d9dcdb
--- /dev/null
+++ b/gfx/2d/RadialGradientEffectD2D1.cpp
@@ -0,0 +1,405 @@
+/* -*- 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 "RadialGradientEffectD2D1.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='RadialGradientEffect'/>
+ <Property name='Author' type='string' value='Mozilla'/>
+ <Property name='Category' type='string' value='Pattern effects'/>
+ <Property name='Description' type='string' value='This effect is used to render radial gradients in a manner compliant with the 2D Canvas specification.'/>
+ <Inputs>
+ <Input name='Geometry'/>
+ </Inputs>
+ <Property name='StopCollection' type='iunknown'>
+ <Property name='DisplayName' type='string' value='Gradient stop collection'/>
+ </Property>
+ <Property name='Center1' type='vector2'>
+ <Property name='DisplayName' type='string' value='Inner circle center'/>
+ </Property>
+ <Property name='Center2' type='vector2'>
+ <Property name='DisplayName' type='string' value='Outer circle center'/>
+ </Property>
+ <Property name='Radius1' type='float'>
+ <Property name='DisplayName' type='string' value='Inner circle radius'/>
+ </Property>
+ <Property name='Radius2' type='float'>
+ <Property name='DisplayName' type='string' value='Outer circle radius'/>
+ </Property>
+ <Property name='Transform' type='matrix3x2'>
+ <Property name='DisplayName' type='string' value='Transform applied to the pattern'/>
+ </Property>
+
+ </Effect>
+ );
+
+// {FB947CDA-718E-40CC-AE7B-D255830D7D14}
+static const GUID GUID_SampleRadialGradientPS = {
+ 0xfb947cda,
+ 0x718e,
+ 0x40cc,
+ {0xae, 0x7b, 0xd2, 0x55, 0x83, 0xd, 0x7d, 0x14}};
+// {2C468128-6546-453C-8E25-F2DF0DE10A0F}
+static const GUID GUID_SampleRadialGradientA0PS = {
+ 0x2c468128, 0x6546, 0x453c, {0x8e, 0x25, 0xf2, 0xdf, 0xd, 0xe1, 0xa, 0xf}};
+
+namespace mozilla {
+namespace gfx {
+
+RadialGradientEffectD2D1::RadialGradientEffectD2D1()
+ : mRefCount(0),
+ mCenter1(D2D1::Vector2F(0, 0)),
+ mCenter2(D2D1::Vector2F(0, 0)),
+ mRadius1(0),
+ mRadius2(0),
+ mTransform(D2D1::IdentityMatrix())
+
+{}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal,
+ ID2D1TransformGraph* pTransformGraph) {
+ HRESULT hr;
+
+ hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientPS,
+ SampleRadialGradientPS,
+ sizeof(SampleRadialGradientPS));
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientA0PS,
+ SampleRadialGradientA0PS,
+ sizeof(SampleRadialGradientA0PS));
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ hr = pTransformGraph->SetSingleTransformNode(this);
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ mEffectContext = pContextInternal;
+
+ return S_OK;
+}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) {
+ if (changeType == D2D1_CHANGE_TYPE_NONE) {
+ return S_OK;
+ }
+
+ // We'll need to inverse transform our pixel, precompute inverse here.
+ Matrix mat = ToMatrix(mTransform);
+ if (!mat.Invert()) {
+ // Singular
+ return S_OK;
+ }
+
+ if (!mStopCollection) {
+ return S_OK;
+ }
+
+ D2D1_POINT_2F dc =
+ D2D1::Point2F(mCenter2.x - mCenter1.x, mCenter2.y - mCenter1.y);
+ float dr = mRadius2 - mRadius1;
+ float A = dc.x * dc.x + dc.y * dc.y - dr * dr;
+
+ HRESULT hr;
+
+ if (A == 0) {
+ hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientA0PS);
+ } else {
+ hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientPS);
+ }
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ RefPtr<ID2D1ResourceTexture> tex = CreateGradientTexture();
+ hr = mDrawInfo->SetResourceTexture(1, tex);
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ struct PSConstantBuffer {
+ float diff[3];
+ float padding;
+ float center1[2];
+ float A;
+ float radius1;
+ float sq_radius1;
+ float repeat_correct;
+ float allow_odd;
+ float padding2[1];
+ float transform[8];
+ };
+
+ PSConstantBuffer buffer = {
+ {dc.x, dc.y, dr},
+ 0.0f,
+ {mCenter1.x, mCenter1.y},
+ A,
+ mRadius1,
+ mRadius1 * mRadius1,
+ mStopCollection->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP ? 1.0f : 0.0f,
+ mStopCollection->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR ? 1.0f : 0.0f,
+ {0.0f},
+ {mat._11, mat._21, mat._31, 0.0f, mat._12, mat._22, mat._32, 0.0f}};
+
+ hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer));
+
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ return S_OK;
+}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) {
+ return pGraph->SetSingleTransformNode(this);
+}
+
+IFACEMETHODIMP_(ULONG)
+RadialGradientEffectD2D1::AddRef() { return ++mRefCount; }
+
+IFACEMETHODIMP_(ULONG)
+RadialGradientEffectD2D1::Release() {
+ if (!--mRefCount) {
+ delete this;
+ return 0;
+ }
+ return mRefCount;
+}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::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;
+}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::MapInputRectsToOutputRect(
+ const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects,
+ UINT32 inputRectCount, D2D1_RECT_L* pOutputRect,
+ D2D1_RECT_L* pOutputOpaqueSubRect) {
+ if (inputRectCount != 1) {
+ return E_INVALIDARG;
+ }
+
+ *pOutputRect = *pInputRects;
+ *pOutputOpaqueSubRect = *pInputOpaqueSubRects;
+ return S_OK;
+}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::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
+RadialGradientEffectD2D1::MapInvalidRect(
+ UINT32 inputIndex, D2D1_RECT_L invalidInputRect,
+ D2D1_RECT_L* pInvalidOutputRect) const {
+ MOZ_ASSERT(inputIndex == 0);
+
+ *pInvalidOutputRect = invalidInputRect;
+ return S_OK;
+}
+
+IFACEMETHODIMP
+RadialGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo* pDrawInfo) {
+ mDrawInfo = pDrawInfo;
+ return S_OK;
+}
+
+HRESULT
+RadialGradientEffectD2D1::Register(ID2D1Factory1* aFactory) {
+ D2D1_PROPERTY_BINDING bindings[] = {
+ D2D1_VALUE_TYPE_BINDING(L"StopCollection",
+ &RadialGradientEffectD2D1::SetStopCollection,
+ &RadialGradientEffectD2D1::GetStopCollection),
+ D2D1_VALUE_TYPE_BINDING(L"Center1", &RadialGradientEffectD2D1::SetCenter1,
+ &RadialGradientEffectD2D1::GetCenter1),
+ D2D1_VALUE_TYPE_BINDING(L"Center2", &RadialGradientEffectD2D1::SetCenter2,
+ &RadialGradientEffectD2D1::GetCenter2),
+ D2D1_VALUE_TYPE_BINDING(L"Radius1", &RadialGradientEffectD2D1::SetRadius1,
+ &RadialGradientEffectD2D1::GetRadius1),
+ D2D1_VALUE_TYPE_BINDING(L"Radius2", &RadialGradientEffectD2D1::SetRadius2,
+ &RadialGradientEffectD2D1::GetRadius2),
+ D2D1_VALUE_TYPE_BINDING(L"Transform",
+ &RadialGradientEffectD2D1::SetTransform,
+ &RadialGradientEffectD2D1::GetTransform)};
+ HRESULT hr = aFactory->RegisterEffectFromString(
+ CLSID_RadialGradientEffect, kXmlDescription, bindings,
+ ARRAYSIZE(bindings), CreateEffect);
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to register radial gradient effect.";
+ }
+ return hr;
+}
+
+void RadialGradientEffectD2D1::Unregister(ID2D1Factory1* aFactory) {
+ aFactory->UnregisterEffect(CLSID_RadialGradientEffect);
+}
+
+HRESULT __stdcall RadialGradientEffectD2D1::CreateEffect(
+ IUnknown** aEffectImpl) {
+ *aEffectImpl = static_cast<ID2D1EffectImpl*>(new RadialGradientEffectD2D1());
+ (*aEffectImpl)->AddRef();
+
+ return S_OK;
+}
+
+HRESULT
+RadialGradientEffectD2D1::SetStopCollection(IUnknown* aStopCollection) {
+ if (SUCCEEDED(aStopCollection->QueryInterface(
+ (ID2D1GradientStopCollection**)getter_AddRefs(mStopCollection)))) {
+ return S_OK;
+ }
+
+ return E_INVALIDARG;
+}
+
+already_AddRefed<ID2D1ResourceTexture>
+RadialGradientEffectD2D1::CreateGradientTexture() {
+ std::vector<D2D1_GRADIENT_STOP> rawStops;
+ rawStops.resize(mStopCollection->GetGradientStopCount());
+ mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size());
+
+ std::vector<unsigned char> textureData;
+ textureData.resize(4096 * 4);
+ unsigned char* texData = &textureData.front();
+
+ float prevColorPos = 0;
+ float nextColorPos = 1.0f;
+ D2D1_COLOR_F prevColor = rawStops[0].color;
+ D2D1_COLOR_F nextColor = prevColor;
+
+ if (rawStops.size() >= 2) {
+ nextColor = rawStops[1].color;
+ nextColorPos = rawStops[1].position;
+ }
+
+ uint32_t stopPosition = 2;
+
+ // Not the most optimized way but this will do for now.
+ for (int i = 0; i < 4096; i++) {
+ // The 4095 seems a little counter intuitive, but we want the gradient
+ // color at offset 0 at the first pixel, and at offset 1.0f at the last
+ // pixel.
+ float pos = float(i) / 4095;
+
+ while (pos > nextColorPos) {
+ prevColor = nextColor;
+ prevColorPos = nextColorPos;
+ if (rawStops.size() > stopPosition) {
+ nextColor = rawStops[stopPosition].color;
+ nextColorPos = rawStops[stopPosition++].position;
+ } else {
+ nextColorPos = 1.0f;
+ }
+ }
+
+ float interp;
+
+ if (nextColorPos != prevColorPos) {
+ interp = (pos - prevColorPos) / (nextColorPos - prevColorPos);
+ } else {
+ interp = 0;
+ }
+
+ DeviceColor newColor(prevColor.r + (nextColor.r - prevColor.r) * interp,
+ prevColor.g + (nextColor.g - prevColor.g) * interp,
+ prevColor.b + (nextColor.b - prevColor.b) * interp,
+ prevColor.a + (nextColor.a - prevColor.a) * interp);
+
+ // Note D2D expects RGBA here!!
+ texData[i * 4] = (unsigned char)(255.0f * newColor.r);
+ texData[i * 4 + 1] = (unsigned char)(255.0f * newColor.g);
+ texData[i * 4 + 2] = (unsigned char)(255.0f * newColor.b);
+ texData[i * 4 + 3] = (unsigned char)(255.0f * newColor.a);
+ }
+
+ RefPtr<ID2D1ResourceTexture> tex;
+
+ UINT32 width = 4096;
+ UINT32 stride = 4096 * 4;
+ D2D1_RESOURCE_TEXTURE_PROPERTIES props;
+ // Older shader models do not support 1D textures. So just use a width x 1
+ // texture.
+ props.dimensions = 2;
+ UINT32 dims[] = {width, 1};
+ props.extents = dims;
+ props.channelDepth = D2D1_CHANNEL_DEPTH_4;
+ props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM;
+ props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR;
+ D2D1_EXTEND_MODE extendMode[] = {mStopCollection->GetExtendMode(),
+ mStopCollection->GetExtendMode()};
+ props.extendModes = extendMode;
+
+ HRESULT hr = mEffectContext->CreateResourceTexture(
+ nullptr, &props, &textureData.front(), &stride, 4096 * 4,
+ getter_AddRefs(tex));
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to create resource texture: " << hexa(hr);
+ }
+
+ return tex.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/RadialGradientEffectD2D1.h b/gfx/2d/RadialGradientEffectD2D1.h
new file mode 100644
index 0000000000..a49671e6bd
--- /dev/null
+++ b/gfx/2d/RadialGradientEffectD2D1.h
@@ -0,0 +1,103 @@
+/* -*- 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_RADIALGRADIENTEFFECTD2D1_H_
+#define MOZILLA_GFX_RADIALGRADIENTEFFECTD2D1_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_RadialGradientEffect, 0x97143dc6, 0xcbc4, 0x4dd4, 0xa8, 0xba,
+ 0x13, 0x34, 0x2b, 0xb, 0xa4, 0x6d);
+
+// Macro to keep our class nice and clean.
+#define SIMPLE_PROP(type, name) \
+ public: \
+ HRESULT Set##name(type a##name) { \
+ m##name = a##name; \
+ return S_OK; \
+ } \
+ type Get##name() const { return m##name; } \
+ \
+ private: \
+ type m##name;
+
+namespace mozilla {
+namespace gfx {
+
+enum {
+ RADIAL_PROP_STOP_COLLECTION = 0,
+ RADIAL_PROP_CENTER_1,
+ RADIAL_PROP_CENTER_2,
+ RADIAL_PROP_RADIUS_1,
+ RADIAL_PROP_RADIUS_2,
+ RADIAL_PROP_TRANSFORM
+};
+
+class RadialGradientEffectD2D1 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);
+
+ static HRESULT Register(ID2D1Factory1* aFactory);
+ static void Unregister(ID2D1Factory1* aFactory);
+ static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl);
+
+ HRESULT SetStopCollection(IUnknown* aStopCollection);
+ IUnknown* GetStopCollection() const { return mStopCollection; }
+
+ private:
+ already_AddRefed<ID2D1ResourceTexture> CreateGradientTexture();
+
+ RadialGradientEffectD2D1();
+
+ uint32_t mRefCount;
+ RefPtr<ID2D1GradientStopCollection> mStopCollection;
+ RefPtr<ID2D1EffectContext> mEffectContext;
+ RefPtr<ID2D1DrawInfo> mDrawInfo;
+ SIMPLE_PROP(D2D1_VECTOR_2F, Center1);
+ SIMPLE_PROP(D2D1_VECTOR_2F, Center2);
+ SIMPLE_PROP(FLOAT, Radius1);
+ SIMPLE_PROP(FLOAT, Radius2);
+ SIMPLE_PROP(D2D_MATRIX_3X2_F, Transform);
+};
+
+} // namespace gfx
+} // namespace mozilla
+#undef SIMPLE_PROP
+
+#endif
diff --git a/gfx/2d/RecordedEvent.cpp b/gfx/2d/RecordedEvent.cpp
new file mode 100644
index 0000000000..04de404378
--- /dev/null
+++ b/gfx/2d/RecordedEvent.cpp
@@ -0,0 +1,209 @@
+/* -*- 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 "RecordedEventImpl.h"
+
+#include "PathRecording.h"
+#include "RecordingTypes.h"
+#include "Tools.h"
+#include "Filters.h"
+#include "Logging.h"
+#include "ScaledFontBase.h"
+#include "SFNTData.h"
+#include "InlineTranslator.h"
+
+namespace mozilla {
+namespace gfx {
+
+/* static */
+bool RecordedEvent::DoWithEventFromStream(
+ EventStream& aStream, EventType aType,
+ const std::function<bool(RecordedEvent*)>& aAction) {
+ return DoWithEvent(aStream, aType, aAction);
+}
+
+/* static */
+bool RecordedEvent::DoWithEventFromStream(
+ EventRingBuffer& aStream, EventType aType,
+ const std::function<bool(RecordedEvent*)>& aAction) {
+ return DoWithEvent(aStream, aType, aAction);
+}
+
+std::string RecordedEvent::GetEventName(EventType aType) {
+ switch (aType) {
+ case DRAWTARGETCREATION:
+ return "DrawTarget Creation";
+ case DRAWTARGETDESTRUCTION:
+ return "DrawTarget Destruction";
+ case FILLRECT:
+ return "FillRect";
+ case STROKERECT:
+ return "StrokeRect";
+ case STROKELINE:
+ return "StrokeLine";
+ case CLEARRECT:
+ return "ClearRect";
+ case COPYSURFACE:
+ return "CopySurface";
+ case SETTRANSFORM:
+ return "SetTransform";
+ case PUSHCLIP:
+ return "PushClip";
+ case PUSHCLIPRECT:
+ return "PushClipRect";
+ case POPCLIP:
+ return "PopClip";
+ case FILL:
+ return "Fill";
+ case FILLGLYPHS:
+ return "FillGlyphs";
+ case MASK:
+ return "Mask";
+ case STROKE:
+ return "Stroke";
+ case DRAWSURFACE:
+ return "DrawSurface";
+ case DRAWDEPENDENTSURFACE:
+ return "DrawDependentSurface";
+ case DRAWSURFACEWITHSHADOW:
+ return "DrawSurfaceWithShadow";
+ case DRAWFILTER:
+ return "DrawFilter";
+ case PATHCREATION:
+ return "PathCreation";
+ case PATHDESTRUCTION:
+ return "PathDestruction";
+ case SOURCESURFACECREATION:
+ return "SourceSurfaceCreation";
+ case SOURCESURFACEDESTRUCTION:
+ return "SourceSurfaceDestruction";
+ case FILTERNODECREATION:
+ return "FilterNodeCreation";
+ case FILTERNODEDESTRUCTION:
+ return "FilterNodeDestruction";
+ case GRADIENTSTOPSCREATION:
+ return "GradientStopsCreation";
+ case GRADIENTSTOPSDESTRUCTION:
+ return "GradientStopsDestruction";
+ case SNAPSHOT:
+ return "Snapshot";
+ case SCALEDFONTCREATION:
+ return "ScaledFontCreation";
+ case SCALEDFONTDESTRUCTION:
+ return "ScaledFontDestruction";
+ case MASKSURFACE:
+ return "MaskSurface";
+ case FILTERNODESETATTRIBUTE:
+ return "SetAttribute";
+ case FILTERNODESETINPUT:
+ return "SetInput";
+ case CREATESIMILARDRAWTARGET:
+ return "CreateSimilarDrawTarget";
+ case FONTDATA:
+ return "FontData";
+ case FONTDESC:
+ return "FontDescriptor";
+ case PUSHLAYER:
+ return "PushLayer";
+ case POPLAYER:
+ return "PopLayer";
+ case UNSCALEDFONTCREATION:
+ return "UnscaledFontCreation";
+ case UNSCALEDFONTDESTRUCTION:
+ return "UnscaledFontDestruction";
+ case EXTERNALSURFACECREATION:
+ return "ExternalSourceSurfaceCreation";
+ case LINK:
+ return "Link";
+ case DESTINATION:
+ return "Destination";
+ default:
+ return "Unknown";
+ }
+}
+
+template <class S>
+void RecordedEvent::RecordUnscaledFontImpl(UnscaledFont* aUnscaledFont,
+ S& aOutput) {
+ RecordedFontData fontData(aUnscaledFont);
+ 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.
+ WriteElement(aOutput, fontData.mType);
+ fontData.RecordToStream(aOutput);
+
+ auto r = RecordedUnscaledFontCreation(aUnscaledFont, fontDetails);
+ WriteElement(aOutput, r.mType);
+ r.RecordToStream(aOutput);
+ } else {
+ // If that fails, record just the font description and try to load it from
+ // the system on the other side.
+ RecordedFontDescriptor fontDesc(aUnscaledFont);
+ if (fontDesc.IsValid()) {
+ WriteElement(aOutput, fontDesc.RecordedEvent::mType);
+ fontDesc.RecordToStream(aOutput);
+ } else {
+ gfxWarning()
+ << "DrawTargetRecording::FillGlyphs failed to serialise UnscaledFont";
+ }
+ }
+}
+
+void RecordedEvent::RecordUnscaledFont(UnscaledFont* aUnscaledFont,
+ std::ostream* aOutput) {
+ RecordUnscaledFontImpl(aUnscaledFont, *aOutput);
+}
+
+void RecordedEvent::RecordUnscaledFont(UnscaledFont* aUnscaledFont,
+ MemStream& aOutput) {
+ RecordUnscaledFontImpl(aUnscaledFont, aOutput);
+}
+
+already_AddRefed<DrawTarget> Translator::CreateDrawTarget(
+ ReferencePtr aRefPtr, const IntSize& aSize, SurfaceFormat aFormat) {
+ RefPtr<DrawTarget> newDT =
+ GetReferenceDrawTarget()->CreateSimilarDrawTarget(aSize, aFormat);
+ AddDrawTarget(aRefPtr, newDT);
+ return newDT.forget();
+}
+
+void Translator::DrawDependentSurface(ReferencePtr aDrawTarget, uint64_t aKey,
+ const Rect& aRect) {
+ if (!mDependentSurfaces) {
+ return;
+ }
+
+ DrawTarget* dt = LookupDrawTarget(aDrawTarget);
+ if (!dt) {
+ return;
+ }
+
+ RefPtr<RecordedDependentSurface> recordedSurface =
+ mDependentSurfaces->Get(aKey);
+ if (!recordedSurface) {
+ return;
+ }
+
+ dt->PushClipRect(aRect);
+
+ // Construct a new translator, so we can recurse into translating this
+ // sub-recording into the same DT. Set an initial transform for the
+ // translator, so that all commands get moved into the rect we want to draw.
+ Matrix transform = dt->GetTransform();
+ transform.PreTranslate(aRect.TopLeft());
+ InlineTranslator translator(dt, nullptr);
+ translator.SetReferenceDrawTargetTransform(transform);
+
+ translator.SetDependentSurfaces(mDependentSurfaces);
+ translator.TranslateRecording((char*)recordedSurface->mRecording.mData,
+ recordedSurface->mRecording.mLen);
+
+ dt->PopClip();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h
new file mode 100644
index 0000000000..3ffdf43272
--- /dev/null
+++ b/gfx/2d/RecordedEvent.h
@@ -0,0 +1,517 @@
+/* -*- 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_RECORDEDEVENT_H_
+#define MOZILLA_GFX_RECORDEDEVENT_H_
+
+#include <ostream>
+#include <sstream>
+#include <cstring>
+#include <functional>
+#include <vector>
+
+#include "RecordingTypes.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/ipc/ByteBuf.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace gfx {
+
+const uint32_t kMagicInt = 0xc001feed;
+
+// A change in major revision means a change in event binary format, causing
+// loss of backwards compatibility. Old streams will not work in a player
+// using a newer major revision. And new streams will not work in a player
+// using an older major revision.
+const uint16_t kMajorRevision = 10;
+// A change in minor revision means additions of new events. New streams will
+// not play in older players.
+const uint16_t kMinorRevision = 3;
+
+struct ReferencePtr {
+ ReferencePtr() : mLongPtr(0) {}
+
+ MOZ_IMPLICIT ReferencePtr(const void* aLongPtr)
+ : mLongPtr(uint64_t(aLongPtr)) {}
+
+ template <typename T>
+ MOZ_IMPLICIT ReferencePtr(const RefPtr<T>& aPtr)
+ : mLongPtr(uint64_t(aPtr.get())) {}
+
+ ReferencePtr& operator=(const void* aLongPtr) {
+ mLongPtr = uint64_t(aLongPtr);
+ return *this;
+ }
+
+ template <typename T>
+ ReferencePtr& operator=(const RefPtr<T>& aPtr) {
+ mLongPtr = uint64_t(aPtr.get());
+ return *this;
+ }
+
+ operator void*() const { return (void*)mLongPtr; }
+
+ uint64_t mLongPtr;
+};
+
+struct RecordedFontDetails {
+ uint64_t fontDataKey = 0;
+ uint32_t size = 0;
+ uint32_t index = 0;
+};
+
+struct RecordedDependentSurface {
+ NS_INLINE_DECL_REFCOUNTING(RecordedDependentSurface);
+
+ RecordedDependentSurface(const IntSize& aSize,
+ mozilla::ipc::ByteBuf&& aRecording)
+ : mSize(aSize), mRecording(std::move(aRecording)) {}
+
+ IntSize mSize;
+ mozilla::ipc::ByteBuf mRecording;
+
+ private:
+ ~RecordedDependentSurface() = default;
+};
+
+// Used by the Azure drawing debugger (player2d)
+inline std::string StringFromPtr(ReferencePtr aPtr) {
+ std::stringstream stream;
+ stream << aPtr;
+ return stream.str();
+}
+
+class Translator {
+ public:
+ virtual ~Translator() = default;
+
+ virtual DrawTarget* LookupDrawTarget(ReferencePtr aRefPtr) = 0;
+ virtual Path* LookupPath(ReferencePtr aRefPtr) = 0;
+ virtual SourceSurface* LookupSourceSurface(ReferencePtr aRefPtr) = 0;
+ virtual FilterNode* LookupFilterNode(ReferencePtr aRefPtr) = 0;
+ virtual already_AddRefed<GradientStops> LookupGradientStops(
+ ReferencePtr aRefPtr) = 0;
+ virtual ScaledFont* LookupScaledFont(ReferencePtr aRefPtr) = 0;
+ virtual UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) = 0;
+ virtual NativeFontResource* LookupNativeFontResource(uint64_t aKey) = 0;
+ virtual already_AddRefed<SourceSurface> LookupExternalSurface(uint64_t aKey) {
+ return nullptr;
+ }
+ void DrawDependentSurface(ReferencePtr aDrawTarget, uint64_t aKey,
+ const Rect& aRect);
+ virtual void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget* aDT) = 0;
+ virtual void RemoveDrawTarget(ReferencePtr aRefPtr) = 0;
+ virtual void AddPath(ReferencePtr aRefPtr, Path* aPath) = 0;
+ virtual void RemovePath(ReferencePtr aRefPtr) = 0;
+ virtual void AddSourceSurface(ReferencePtr aRefPtr, SourceSurface* aPath) = 0;
+ virtual void RemoveSourceSurface(ReferencePtr aRefPtr) = 0;
+ virtual void AddFilterNode(mozilla::gfx::ReferencePtr aRefPtr,
+ FilterNode* aSurface) = 0;
+ virtual void RemoveFilterNode(mozilla::gfx::ReferencePtr aRefPtr) = 0;
+
+ /**
+ * Get GradientStops compatible with the translation DrawTarget type.
+ * @param aRawStops array of raw gradient stops required
+ * @param aNumStops length of aRawStops
+ * @param aExtendMode extend mode required
+ * @return an already addrefed GradientStops for our DrawTarget type
+ */
+ virtual already_AddRefed<GradientStops> GetOrCreateGradientStops(
+ DrawTarget* aDrawTarget, GradientStop* aRawStops, uint32_t aNumStops,
+ ExtendMode aExtendMode) {
+ return aDrawTarget->CreateGradientStops(aRawStops, aNumStops, aExtendMode);
+ }
+ virtual void AddGradientStops(ReferencePtr aRefPtr, GradientStops* aPath) = 0;
+ virtual void RemoveGradientStops(ReferencePtr aRefPtr) = 0;
+ virtual void AddScaledFont(ReferencePtr aRefPtr, ScaledFont* aScaledFont) = 0;
+ virtual void RemoveScaledFont(ReferencePtr aRefPtr) = 0;
+ virtual void AddUnscaledFont(ReferencePtr aRefPtr,
+ UnscaledFont* aUnscaledFont) = 0;
+ virtual void RemoveUnscaledFont(ReferencePtr aRefPtr) = 0;
+ virtual void AddNativeFontResource(
+ uint64_t aKey, NativeFontResource* aNativeFontResource) = 0;
+
+ virtual already_AddRefed<DrawTarget> CreateDrawTarget(ReferencePtr aRefPtr,
+ const IntSize& aSize,
+ SurfaceFormat aFormat);
+ virtual DrawTarget* GetReferenceDrawTarget() = 0;
+ virtual Matrix GetReferenceDrawTargetTransform() { return Matrix(); }
+ virtual void* GetFontContext() { return nullptr; }
+
+ void SetDependentSurfaces(
+ nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>*
+ aDependentSurfaces) {
+ mDependentSurfaces = aDependentSurfaces;
+ }
+
+ nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>*
+ mDependentSurfaces = nullptr;
+};
+
+struct ColorPatternStorage {
+ DeviceColor mColor;
+};
+
+struct LinearGradientPatternStorage {
+ Point mBegin;
+ Point mEnd;
+ ReferencePtr mStops;
+ Matrix mMatrix;
+};
+
+struct RadialGradientPatternStorage {
+ Point mCenter1;
+ Point mCenter2;
+ Float mRadius1;
+ Float mRadius2;
+ ReferencePtr mStops;
+ Matrix mMatrix;
+};
+
+struct ConicGradientPatternStorage {
+ Point mCenter;
+ Float mAngle;
+ Float mStartOffset;
+ Float mEndOffset;
+ ReferencePtr mStops;
+ Matrix mMatrix;
+};
+
+struct SurfacePatternStorage {
+ ExtendMode mExtend;
+ SamplingFilter mSamplingFilter;
+ ReferencePtr mSurface;
+ Matrix mMatrix;
+ IntRect mSamplingRect;
+};
+
+struct PatternStorage {
+ PatternType mType;
+ union {
+ char* mStorage;
+ char mColor[sizeof(ColorPatternStorage)];
+ char mLinear[sizeof(LinearGradientPatternStorage)];
+ char mRadial[sizeof(RadialGradientPatternStorage)];
+ char mConic[sizeof(ConicGradientPatternStorage)];
+ char mSurface[sizeof(SurfacePatternStorage)];
+ };
+};
+
+/* SizeCollector and MemWriter are used
+ * in a pair to first collect the size of the
+ * event that we're going to write and then
+ * to write it without checking each individual
+ * size. */
+struct SizeCollector {
+ SizeCollector() : mTotalSize(0) {}
+ void write(const char*, size_t s) { mTotalSize += s; }
+ size_t mTotalSize;
+};
+
+struct MemWriter {
+ explicit MemWriter(char* aPtr) : mPtr(aPtr) {}
+ void write(const char* aData, size_t aSize) {
+ memcpy(mPtr, aData, aSize);
+ mPtr += aSize;
+ }
+ char* mPtr;
+};
+
+// This is a simple interface for an EventRingBuffer, so we can use it in the
+// RecordedEvent reading and writing machinery.
+class EventRingBuffer {
+ public:
+ /**
+ * Templated RecordEvent function so that when we have enough contiguous
+ * space we can record into the buffer quickly using MemWriter.
+ *
+ * @param aRecordedEvent the event to record
+ */
+ template <class RE>
+ void RecordEvent(const RE* aRecordedEvent) {
+ SizeCollector size;
+ WriteElement(size, aRecordedEvent->GetType());
+ aRecordedEvent->Record(size);
+ if (size.mTotalSize > mAvailable) {
+ WaitForAndRecalculateAvailableSpace();
+ }
+ if (size.mTotalSize <= mAvailable) {
+ MemWriter writer(mBufPos);
+ WriteElement(writer, aRecordedEvent->GetType());
+ aRecordedEvent->Record(writer);
+ UpdateWriteTotalsBy(size.mTotalSize);
+ } else {
+ WriteElement(*this, aRecordedEvent->GetType());
+ aRecordedEvent->Record(*this);
+ }
+ }
+
+ /**
+ * Simple write function required by WriteElement.
+ *
+ * @param aData the data to be written to the buffer
+ * @param aSize the number of chars to write
+ */
+ virtual void write(const char* const aData, const size_t aSize) = 0;
+
+ /**
+ * Simple read function required by ReadElement.
+ *
+ * @param aOut the pointer to read into
+ * @param aSize the number of chars to read
+ */
+ virtual void read(char* const aOut, const size_t aSize) = 0;
+
+ virtual bool good() const = 0;
+
+ virtual void SetIsBad() = 0;
+
+ protected:
+ /**
+ * Wait until space is available for writing and then set mBufPos and
+ * mAvailable.
+ */
+ virtual bool WaitForAndRecalculateAvailableSpace() = 0;
+
+ /**
+ * Update write count, mBufPos and mAvailable.
+ *
+ * @param aCount number of bytes written
+ */
+ virtual void UpdateWriteTotalsBy(uint32_t aCount) = 0;
+
+ char* mBufPos = nullptr;
+ uint32_t mAvailable = 0;
+};
+
+struct MemStream {
+ char* mData;
+ size_t mLength;
+ size_t mCapacity;
+ bool mValid = true;
+ bool Resize(size_t aSize) {
+ if (!mValid) {
+ return false;
+ }
+ mLength = aSize;
+ if (mLength > mCapacity) {
+ mCapacity = mCapacity * 2;
+ // check if the doubled capacity is enough
+ // otherwise use double mLength
+ if (mLength > mCapacity) {
+ mCapacity = mLength * 2;
+ }
+ char* data = (char*)realloc(mData, mCapacity);
+ if (!data) {
+ free(mData);
+ }
+ mData = data;
+ }
+ if (mData) {
+ return true;
+ }
+ NS_ERROR("Failed to allocate MemStream!");
+ mValid = false;
+ mLength = 0;
+ mCapacity = 0;
+ return false;
+ }
+
+ void reset() {
+ free(mData);
+ mData = nullptr;
+ mValid = true;
+ mLength = 0;
+ mCapacity = 0;
+ }
+
+ MemStream(const MemStream&) = delete;
+ MemStream(MemStream&&) = delete;
+ MemStream& operator=(const MemStream&) = delete;
+ MemStream& operator=(MemStream&&) = delete;
+
+ void write(const char* aData, size_t aSize) {
+ if (Resize(mLength + aSize)) {
+ memcpy(mData + mLength - aSize, aData, aSize);
+ }
+ }
+
+ MemStream() : mData(nullptr), mLength(0), mCapacity(0) {}
+ ~MemStream() { free(mData); }
+};
+
+class EventStream {
+ public:
+ virtual void write(const char* aData, size_t aSize) = 0;
+ virtual void read(char* aOut, size_t aSize) = 0;
+ virtual bool good() = 0;
+ virtual void SetIsBad() = 0;
+};
+
+class RecordedEvent {
+ public:
+ enum EventType {
+ DRAWTARGETCREATION = 0,
+ DRAWTARGETDESTRUCTION,
+ FILLRECT,
+ STROKERECT,
+ STROKELINE,
+ CLEARRECT,
+ COPYSURFACE,
+ SETTRANSFORM,
+ PUSHCLIP,
+ PUSHCLIPRECT,
+ POPCLIP,
+ FILL,
+ FILLGLYPHS,
+ MASK,
+ STROKE,
+ DRAWSURFACE,
+ DRAWDEPENDENTSURFACE,
+ DRAWSURFACEWITHSHADOW,
+ PATHCREATION,
+ PATHDESTRUCTION,
+ SOURCESURFACECREATION,
+ SOURCESURFACEDESTRUCTION,
+ GRADIENTSTOPSCREATION,
+ GRADIENTSTOPSDESTRUCTION,
+ SNAPSHOT,
+ SCALEDFONTCREATION,
+ SCALEDFONTDESTRUCTION,
+ MASKSURFACE,
+ FILTERNODECREATION,
+ FILTERNODEDESTRUCTION,
+ DRAWFILTER,
+ FILTERNODESETATTRIBUTE,
+ FILTERNODESETINPUT,
+ CREATESIMILARDRAWTARGET,
+ CREATECLIPPEDDRAWTARGET,
+ CREATEDRAWTARGETFORFILTER,
+ FONTDATA,
+ FONTDESC,
+ PUSHLAYER,
+ PUSHLAYERWITHBLEND,
+ POPLAYER,
+ UNSCALEDFONTCREATION,
+ UNSCALEDFONTDESTRUCTION,
+ INTOLUMINANCE,
+ EXTERNALSURFACECREATION,
+ FLUSH,
+ DETACHALLSNAPSHOTS,
+ OPTIMIZESOURCESURFACE,
+ LINK,
+ DESTINATION,
+ LAST,
+ };
+
+ virtual ~RecordedEvent() = default;
+
+ static std::string GetEventName(EventType aType);
+
+ /**
+ * Play back this event using the translator. Note that derived classes
+ * should
+ * only return false when there is a fatal error, as it will probably mean
+ * the
+ * translation will abort.
+ * @param aTranslator Translator to be used for retrieving other referenced
+ * objects and making playback decisions.
+ * @return true unless a fatal problem has occurred and playback should
+ * abort.
+ */
+ virtual bool PlayEvent(Translator* aTranslator) const { return true; }
+
+ virtual void RecordToStream(std::ostream& aStream) const = 0;
+ virtual void RecordToStream(EventStream& aStream) const = 0;
+ virtual void RecordToStream(EventRingBuffer& aStream) const = 0;
+ virtual void RecordToStream(MemStream& aStream) const = 0;
+
+ virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const {}
+
+ template <class S>
+ void RecordPatternData(S& aStream,
+ const PatternStorage& aPatternStorage) const;
+ template <class S>
+ void ReadPatternData(S& aStream, PatternStorage& aPatternStorage) const;
+ void StorePattern(PatternStorage& aDestination, const Pattern& aSource) const;
+ template <class S>
+ void RecordStrokeOptions(S& aStream,
+ const StrokeOptions& aStrokeOptions) const;
+ template <class S>
+ void ReadStrokeOptions(S& aStream, StrokeOptions& aStrokeOptions);
+
+ virtual std::string GetName() const = 0;
+
+ virtual ReferencePtr GetDestinedDT() { return nullptr; }
+
+ void OutputSimplePatternInfo(const PatternStorage& aStorage,
+ std::stringstream& aOutput) const;
+
+ template <class S>
+ static bool DoWithEvent(S& aStream, EventType aType,
+ const std::function<bool(RecordedEvent*)>& aAction);
+ static bool DoWithEventFromStream(
+ EventStream& aStream, EventType aType,
+ const std::function<bool(RecordedEvent*)>& aAction);
+ static bool DoWithEventFromStream(
+ EventRingBuffer& aStream, EventType aType,
+ const std::function<bool(RecordedEvent*)>& aAction);
+
+ EventType GetType() const { return (EventType)mType; }
+
+ protected:
+ friend class DrawEventRecorderPrivate;
+ friend class DrawEventRecorderMemory;
+ static void RecordUnscaledFont(UnscaledFont* aUnscaledFont,
+ std::ostream* aOutput);
+ static void RecordUnscaledFont(UnscaledFont* aUnscaledFont,
+ MemStream& aOutput);
+ template <class S>
+ static void RecordUnscaledFontImpl(UnscaledFont* aUnscaledFont, S& aOutput);
+
+ MOZ_IMPLICIT RecordedEvent(int32_t aType) : mType(aType) {}
+
+ int32_t mType;
+ std::vector<Float> mDashPatternStorage;
+};
+
+template <class Derived>
+class RecordedEventDerived : public RecordedEvent {
+ using RecordedEvent::RecordedEvent;
+
+ public:
+ void RecordToStream(std::ostream& aStream) const override {
+ WriteElement(aStream, this->mType);
+ static_cast<const Derived*>(this)->Record(aStream);
+ }
+ void RecordToStream(EventStream& aStream) const override {
+ WriteElement(aStream, this->mType);
+ static_cast<const Derived*>(this)->Record(aStream);
+ }
+ void RecordToStream(EventRingBuffer& aStream) const final {
+ aStream.RecordEvent(static_cast<const Derived*>(this));
+ }
+ void RecordToStream(MemStream& aStream) const override {
+ SizeCollector size;
+ WriteElement(size, this->mType);
+ static_cast<const Derived*>(this)->Record(size);
+
+ if (!aStream.Resize(aStream.mLength + size.mTotalSize)) {
+ return;
+ }
+
+ MemWriter writer(aStream.mData + aStream.mLength - size.mTotalSize);
+ WriteElement(writer, this->mType);
+ static_cast<const Derived*>(this)->Record(writer);
+ }
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/RecordedEventImpl.h b/gfx/2d/RecordedEventImpl.h
new file mode 100644
index 0000000000..7190299825
--- /dev/null
+++ b/gfx/2d/RecordedEventImpl.h
@@ -0,0 +1,4088 @@
+/* -*- 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_RECORDEDEVENTIMPL_H_
+#define MOZILLA_GFX_RECORDEDEVENTIMPL_H_
+
+#include "RecordedEvent.h"
+
+#include "PathRecording.h"
+#include "RecordingTypes.h"
+#include "Tools.h"
+#include "Filters.h"
+#include "Logging.h"
+#include "ScaledFontBase.h"
+#include "SFNTData.h"
+
+namespace mozilla {
+namespace gfx {
+
+template <class Derived>
+class RecordedDrawingEvent : public RecordedEventDerived<Derived> {
+ public:
+ ReferencePtr GetDestinedDT() override { return mDT; }
+
+ protected:
+ RecordedDrawingEvent(RecordedEvent::EventType aType, DrawTarget* aTarget)
+ : RecordedEventDerived<Derived>(aType), mDT(aTarget) {}
+
+ template <class S>
+ RecordedDrawingEvent(RecordedEvent::EventType aType, S& aStream);
+ template <class S>
+ void Record(S& aStream) const;
+
+ ReferencePtr mDT;
+};
+
+class RecordedDrawTargetCreation
+ : public RecordedEventDerived<RecordedDrawTargetCreation> {
+ public:
+ RecordedDrawTargetCreation(ReferencePtr aRefPtr, BackendType aType,
+ const IntRect& aRect, SurfaceFormat aFormat,
+ bool aHasExistingData = false,
+ SourceSurface* aExistingData = nullptr)
+ : RecordedEventDerived(DRAWTARGETCREATION),
+ mRefPtr(aRefPtr),
+ mBackendType(aType),
+ mRect(aRect),
+ mFormat(aFormat),
+ mHasExistingData(aHasExistingData),
+ mExistingData(aExistingData) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "DrawTarget Creation"; }
+
+ ReferencePtr mRefPtr;
+ BackendType mBackendType;
+ IntRect mRect;
+ SurfaceFormat mFormat;
+ bool mHasExistingData;
+ RefPtr<SourceSurface> mExistingData;
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDrawTargetCreation(S& aStream);
+};
+
+class RecordedDrawTargetDestruction
+ : public RecordedEventDerived<RecordedDrawTargetDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedDrawTargetDestruction(ReferencePtr aRefPtr)
+ : RecordedEventDerived(DRAWTARGETDESTRUCTION),
+ mRefPtr(aRefPtr),
+ mBackendType(BackendType::NONE) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "DrawTarget Destruction"; }
+
+ ReferencePtr mRefPtr;
+
+ BackendType mBackendType;
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDrawTargetDestruction(S& aStream);
+};
+
+class RecordedCreateSimilarDrawTarget
+ : public RecordedEventDerived<RecordedCreateSimilarDrawTarget> {
+ public:
+ RecordedCreateSimilarDrawTarget(ReferencePtr aDT, ReferencePtr aRefPtr,
+ const IntSize& aSize, SurfaceFormat aFormat)
+ : RecordedEventDerived(CREATESIMILARDRAWTARGET),
+ mDT(aDT),
+ mRefPtr(aRefPtr),
+ mSize(aSize),
+ mFormat(aFormat) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "CreateSimilarDrawTarget"; }
+
+ ReferencePtr mDT;
+ ReferencePtr mRefPtr;
+ IntSize mSize;
+ SurfaceFormat mFormat;
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedCreateSimilarDrawTarget(S& aStream);
+};
+
+class RecordedCreateClippedDrawTarget
+ : public RecordedDrawingEvent<RecordedCreateClippedDrawTarget> {
+ public:
+ RecordedCreateClippedDrawTarget(DrawTarget* aDT, ReferencePtr aRefPtr,
+ const Rect& aBounds, SurfaceFormat aFormat)
+ : RecordedDrawingEvent(CREATECLIPPEDDRAWTARGET, aDT),
+ mRefPtr(aRefPtr),
+ mBounds(aBounds),
+ mFormat(aFormat) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "CreateClippedDrawTarget"; }
+
+ ReferencePtr mRefPtr;
+ Rect mBounds;
+ SurfaceFormat mFormat;
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedCreateClippedDrawTarget(S& aStream);
+};
+
+class RecordedCreateDrawTargetForFilter
+ : public RecordedDrawingEvent<RecordedCreateDrawTargetForFilter> {
+ public:
+ RecordedCreateDrawTargetForFilter(DrawTarget* aDT, ReferencePtr aRefPtr,
+ const IntSize& aMaxSize,
+ SurfaceFormat aFormat, FilterNode* aFilter,
+ FilterNode* aSource,
+ const Rect& aSourceRect,
+ const Point& aDestPoint)
+ : RecordedDrawingEvent(CREATEDRAWTARGETFORFILTER, aDT),
+ mRefPtr(aRefPtr),
+ mMaxSize(aMaxSize),
+ mFormat(aFormat),
+ mFilter(aFilter),
+ mSource(aSource),
+ mSourceRect(aSourceRect),
+ mDestPoint(aDestPoint) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override {
+ return "CreateSimilarDrawTargetForFilter";
+ }
+
+ ReferencePtr mRefPtr;
+ IntSize mMaxSize;
+ SurfaceFormat mFormat;
+ ReferencePtr mFilter;
+ ReferencePtr mSource;
+ Rect mSourceRect;
+ Point mDestPoint;
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedCreateDrawTargetForFilter(S& aStream);
+};
+
+class RecordedFillRect : public RecordedDrawingEvent<RecordedFillRect> {
+ public:
+ RecordedFillRect(DrawTarget* aDT, const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(FILLRECT, aDT),
+ mRect(aRect),
+ mPattern(),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "FillRect"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFillRect(S& aStream);
+
+ Rect mRect;
+ PatternStorage mPattern;
+ DrawOptions mOptions;
+};
+
+class RecordedStrokeRect : public RecordedDrawingEvent<RecordedStrokeRect> {
+ public:
+ RecordedStrokeRect(DrawTarget* aDT, const Rect& aRect,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(STROKERECT, aDT),
+ mRect(aRect),
+ mPattern(),
+ mStrokeOptions(aStrokeOptions),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "StrokeRect"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedStrokeRect(S& aStream);
+
+ Rect mRect;
+ PatternStorage mPattern;
+ StrokeOptions mStrokeOptions;
+ DrawOptions mOptions;
+};
+
+class RecordedStrokeLine : public RecordedDrawingEvent<RecordedStrokeLine> {
+ public:
+ RecordedStrokeLine(DrawTarget* aDT, const Point& aBegin, const Point& aEnd,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(STROKELINE, aDT),
+ mBegin(aBegin),
+ mEnd(aEnd),
+ mPattern(),
+ mStrokeOptions(aStrokeOptions),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "StrokeLine"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedStrokeLine(S& aStream);
+
+ Point mBegin;
+ Point mEnd;
+ PatternStorage mPattern;
+ StrokeOptions mStrokeOptions;
+ DrawOptions mOptions;
+};
+
+class RecordedFill : public RecordedDrawingEvent<RecordedFill> {
+ public:
+ RecordedFill(DrawTarget* aDT, ReferencePtr aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(FILL, aDT),
+ mPath(aPath),
+ mPattern(),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Fill"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFill(S& aStream);
+
+ ReferencePtr mPath;
+ PatternStorage mPattern;
+ DrawOptions mOptions;
+};
+
+class RecordedFillGlyphs : public RecordedDrawingEvent<RecordedFillGlyphs> {
+ public:
+ RecordedFillGlyphs(DrawTarget* aDT, ReferencePtr aScaledFont,
+ const Pattern& aPattern, const DrawOptions& aOptions,
+ const Glyph* aGlyphs, uint32_t aNumGlyphs)
+ : RecordedDrawingEvent(FILLGLYPHS, aDT),
+ mScaledFont(aScaledFont),
+ mPattern(),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ mNumGlyphs = aNumGlyphs;
+ mGlyphs = new Glyph[aNumGlyphs];
+ memcpy(mGlyphs, aGlyphs, sizeof(Glyph) * aNumGlyphs);
+ }
+ virtual ~RecordedFillGlyphs();
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "FillGlyphs"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFillGlyphs(S& aStream);
+
+ ReferencePtr mScaledFont;
+ PatternStorage mPattern;
+ DrawOptions mOptions;
+ Glyph* mGlyphs = nullptr;
+ uint32_t mNumGlyphs = 0;
+};
+
+class RecordedMask : public RecordedDrawingEvent<RecordedMask> {
+ public:
+ RecordedMask(DrawTarget* aDT, const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(MASK, aDT),
+ mSource(),
+ mMask(),
+ mOptions(aOptions) {
+ StorePattern(mSource, aSource);
+ StorePattern(mMask, aMask);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Mask"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedMask(S& aStream);
+
+ PatternStorage mSource;
+ PatternStorage mMask;
+ DrawOptions mOptions;
+};
+
+class RecordedStroke : public RecordedDrawingEvent<RecordedStroke> {
+ public:
+ RecordedStroke(DrawTarget* aDT, ReferencePtr aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(STROKE, aDT),
+ mPath(aPath),
+ mPattern(),
+ mStrokeOptions(aStrokeOptions),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Stroke"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedStroke(S& aStream);
+
+ ReferencePtr mPath;
+ PatternStorage mPattern;
+ StrokeOptions mStrokeOptions;
+ DrawOptions mOptions;
+};
+
+class RecordedClearRect : public RecordedDrawingEvent<RecordedClearRect> {
+ public:
+ RecordedClearRect(DrawTarget* aDT, const Rect& aRect)
+ : RecordedDrawingEvent(CLEARRECT, aDT), mRect(aRect) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "ClearRect"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedClearRect(S& aStream);
+
+ Rect mRect;
+};
+
+class RecordedCopySurface : public RecordedDrawingEvent<RecordedCopySurface> {
+ public:
+ RecordedCopySurface(DrawTarget* aDT, ReferencePtr aSourceSurface,
+ const IntRect& aSourceRect, const IntPoint& aDest)
+ : RecordedDrawingEvent(COPYSURFACE, aDT),
+ mSourceSurface(aSourceSurface),
+ mSourceRect(aSourceRect),
+ mDest(aDest) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "CopySurface"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedCopySurface(S& aStream);
+
+ ReferencePtr mSourceSurface;
+ IntRect mSourceRect;
+ IntPoint mDest;
+};
+
+class RecordedPushClip : public RecordedDrawingEvent<RecordedPushClip> {
+ public:
+ RecordedPushClip(DrawTarget* aDT, ReferencePtr aPath)
+ : RecordedDrawingEvent(PUSHCLIP, aDT), mPath(aPath) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "PushClip"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPushClip(S& aStream);
+
+ ReferencePtr mPath;
+};
+
+class RecordedPushClipRect : public RecordedDrawingEvent<RecordedPushClipRect> {
+ public:
+ RecordedPushClipRect(DrawTarget* aDT, const Rect& aRect)
+ : RecordedDrawingEvent(PUSHCLIPRECT, aDT), mRect(aRect) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "PushClipRect"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPushClipRect(S& aStream);
+
+ Rect mRect;
+};
+
+class RecordedPopClip : public RecordedDrawingEvent<RecordedPopClip> {
+ public:
+ MOZ_IMPLICIT RecordedPopClip(DrawTarget* aDT)
+ : RecordedDrawingEvent(POPCLIP, aDT) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "PopClip"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPopClip(S& aStream);
+};
+
+class RecordedPushLayer : public RecordedDrawingEvent<RecordedPushLayer> {
+ public:
+ RecordedPushLayer(DrawTarget* aDT, bool aOpaque, Float aOpacity,
+ SourceSurface* aMask, const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground)
+ : RecordedDrawingEvent(PUSHLAYER, aDT),
+ mOpaque(aOpaque),
+ mOpacity(aOpacity),
+ mMask(aMask),
+ mMaskTransform(aMaskTransform),
+ mBounds(aBounds),
+ mCopyBackground(aCopyBackground) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "PushLayer"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPushLayer(S& aStream);
+
+ bool mOpaque;
+ Float mOpacity;
+ ReferencePtr mMask;
+ Matrix mMaskTransform;
+ IntRect mBounds;
+ bool mCopyBackground;
+};
+
+class RecordedPushLayerWithBlend
+ : public RecordedDrawingEvent<RecordedPushLayerWithBlend> {
+ public:
+ RecordedPushLayerWithBlend(DrawTarget* aDT, bool aOpaque, Float aOpacity,
+ SourceSurface* aMask, const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground,
+ CompositionOp aCompositionOp)
+ : RecordedDrawingEvent(PUSHLAYERWITHBLEND, aDT),
+ mOpaque(aOpaque),
+ mOpacity(aOpacity),
+ mMask(aMask),
+ mMaskTransform(aMaskTransform),
+ mBounds(aBounds),
+ mCopyBackground(aCopyBackground),
+ mCompositionOp(aCompositionOp) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "PushLayerWithBlend"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPushLayerWithBlend(S& aStream);
+
+ bool mOpaque;
+ Float mOpacity;
+ ReferencePtr mMask;
+ Matrix mMaskTransform;
+ IntRect mBounds;
+ bool mCopyBackground;
+ CompositionOp mCompositionOp;
+};
+
+class RecordedPopLayer : public RecordedDrawingEvent<RecordedPopLayer> {
+ public:
+ MOZ_IMPLICIT RecordedPopLayer(DrawTarget* aDT)
+ : RecordedDrawingEvent(POPLAYER, aDT) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "PopLayer"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPopLayer(S& aStream);
+};
+
+class RecordedSetTransform : public RecordedDrawingEvent<RecordedSetTransform> {
+ public:
+ RecordedSetTransform(DrawTarget* aDT, const Matrix& aTransform)
+ : RecordedDrawingEvent(SETTRANSFORM, aDT), mTransform(aTransform) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "SetTransform"; }
+
+ Matrix mTransform;
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedSetTransform(S& aStream);
+};
+
+class RecordedDrawSurface : public RecordedDrawingEvent<RecordedDrawSurface> {
+ public:
+ RecordedDrawSurface(DrawTarget* aDT, ReferencePtr aRefSource,
+ const Rect& aDest, const Rect& aSource,
+ const DrawSurfaceOptions& aDSOptions,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(DRAWSURFACE, aDT),
+ mRefSource(aRefSource),
+ mDest(aDest),
+ mSource(aSource),
+ mDSOptions(aDSOptions),
+ mOptions(aOptions) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "DrawSurface"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDrawSurface(S& aStream);
+
+ ReferencePtr mRefSource;
+ Rect mDest;
+ Rect mSource;
+ DrawSurfaceOptions mDSOptions;
+ DrawOptions mOptions;
+};
+
+class RecordedDrawDependentSurface
+ : public RecordedDrawingEvent<RecordedDrawDependentSurface> {
+ public:
+ RecordedDrawDependentSurface(DrawTarget* aDT, uint64_t aId, const Rect& aDest)
+ : RecordedDrawingEvent(DRAWDEPENDENTSURFACE, aDT),
+ mId(aId),
+ mDest(aDest) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "DrawDependentSurface"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDrawDependentSurface(S& aStream);
+
+ uint64_t mId;
+ Rect mDest;
+};
+
+class RecordedDrawSurfaceWithShadow
+ : public RecordedDrawingEvent<RecordedDrawSurfaceWithShadow> {
+ public:
+ RecordedDrawSurfaceWithShadow(DrawTarget* aDT, ReferencePtr aRefSource,
+ const Point& aDest,
+ const ShadowOptions& aShadow, CompositionOp aOp)
+ : RecordedDrawingEvent(DRAWSURFACEWITHSHADOW, aDT),
+ mRefSource(aRefSource),
+ mDest(aDest),
+ mShadow(aShadow),
+ mOp(aOp) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "DrawSurfaceWithShadow"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDrawSurfaceWithShadow(S& aStream);
+
+ ReferencePtr mRefSource;
+ Point mDest;
+ ShadowOptions mShadow;
+ CompositionOp mOp;
+};
+
+class RecordedDrawFilter : public RecordedDrawingEvent<RecordedDrawFilter> {
+ public:
+ RecordedDrawFilter(DrawTarget* aDT, ReferencePtr aNode,
+ const Rect& aSourceRect, const Point& aDestPoint,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(DRAWFILTER, aDT),
+ mNode(aNode),
+ mSourceRect(aSourceRect),
+ mDestPoint(aDestPoint),
+ mOptions(aOptions) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "DrawFilter"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDrawFilter(S& aStream);
+
+ ReferencePtr mNode;
+ Rect mSourceRect;
+ Point mDestPoint;
+ DrawOptions mOptions;
+};
+
+class RecordedPathCreation : public RecordedEventDerived<RecordedPathCreation> {
+ public:
+ MOZ_IMPLICIT RecordedPathCreation(DrawTarget* aDT, PathRecording* aPath);
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Path Creation"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mDT;
+ ReferencePtr mRefPtr;
+ FillRule mFillRule;
+ RefPtr<PathRecording> mPath;
+ UniquePtr<PathOps> mPathOps;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPathCreation(S& aStream);
+};
+
+class RecordedPathDestruction
+ : public RecordedEventDerived<RecordedPathDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedPathDestruction(PathRecording* aPath)
+ : RecordedEventDerived(PATHDESTRUCTION), mRefPtr(aPath) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Path Destruction"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedPathDestruction(S& aStream);
+};
+
+class RecordedSourceSurfaceCreation
+ : public RecordedEventDerived<RecordedSourceSurfaceCreation> {
+ public:
+ RecordedSourceSurfaceCreation(ReferencePtr aRefPtr, uint8_t* aData,
+ int32_t aStride, const IntSize& aSize,
+ SurfaceFormat aFormat)
+ : RecordedEventDerived(SOURCESURFACECREATION),
+ mRefPtr(aRefPtr),
+ mData(aData),
+ mStride(aStride),
+ mSize(aSize),
+ mFormat(aFormat),
+ mDataOwned(false) {}
+
+ ~RecordedSourceSurfaceCreation();
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "SourceSurface Creation"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+ uint8_t* mData = nullptr;
+ int32_t mStride;
+ IntSize mSize;
+ SurfaceFormat mFormat;
+ mutable bool mDataOwned;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedSourceSurfaceCreation(S& aStream);
+};
+
+class RecordedSourceSurfaceDestruction
+ : public RecordedEventDerived<RecordedSourceSurfaceDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedSourceSurfaceDestruction(ReferencePtr aRefPtr)
+ : RecordedEventDerived(SOURCESURFACEDESTRUCTION), mRefPtr(aRefPtr) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "SourceSurface Destruction"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedSourceSurfaceDestruction(S& aStream);
+};
+
+class RecordedOptimizeSourceSurface
+ : public RecordedEventDerived<RecordedOptimizeSourceSurface> {
+ public:
+ RecordedOptimizeSourceSurface(ReferencePtr aSurface, ReferencePtr aDT,
+ ReferencePtr aOptimizedSurface)
+ : RecordedEventDerived(OPTIMIZESOURCESURFACE),
+ mSurface(aSurface),
+ mDT(aDT),
+ mOptimizedSurface(aOptimizedSurface) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "OptimizeSourceSurface"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mSurface;
+ ReferencePtr mDT;
+ ReferencePtr mOptimizedSurface;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedOptimizeSourceSurface(S& aStream);
+};
+
+class RecordedExternalSurfaceCreation
+ : public RecordedEventDerived<RecordedExternalSurfaceCreation> {
+ public:
+ RecordedExternalSurfaceCreation(ReferencePtr aRefPtr, const uint64_t aKey)
+ : RecordedEventDerived(EXTERNALSURFACECREATION),
+ mRefPtr(aRefPtr),
+ mKey(aKey) {}
+
+ ~RecordedExternalSurfaceCreation() = default;
+
+ virtual bool PlayEvent(Translator* aTranslator) const;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const;
+
+ virtual std::string GetName() const {
+ return "SourceSurfaceSharedData Creation";
+ }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+ uint64_t mKey;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedExternalSurfaceCreation(S& aStream);
+};
+
+class RecordedFilterNodeCreation
+ : public RecordedEventDerived<RecordedFilterNodeCreation> {
+ public:
+ RecordedFilterNodeCreation(ReferencePtr aDT, ReferencePtr aRefPtr,
+ FilterType aType)
+ : RecordedEventDerived(FILTERNODECREATION),
+ mDT(aDT),
+ mRefPtr(aRefPtr),
+ mType(aType) {}
+
+ ~RecordedFilterNodeCreation();
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "FilterNode Creation"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mDT;
+ ReferencePtr mRefPtr;
+ FilterType mType;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFilterNodeCreation(S& aStream);
+};
+
+class RecordedFilterNodeDestruction
+ : public RecordedEventDerived<RecordedFilterNodeDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedFilterNodeDestruction(ReferencePtr aRefPtr)
+ : RecordedEventDerived(FILTERNODEDESTRUCTION), mRefPtr(aRefPtr) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "FilterNode Destruction"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFilterNodeDestruction(S& aStream);
+};
+
+class RecordedGradientStopsCreation
+ : public RecordedEventDerived<RecordedGradientStopsCreation> {
+ public:
+ RecordedGradientStopsCreation(ReferencePtr aDT, ReferencePtr aRefPtr,
+ GradientStop* aStops, uint32_t aNumStops,
+ ExtendMode aExtendMode)
+ : RecordedEventDerived(GRADIENTSTOPSCREATION),
+ mDT(aDT),
+ mRefPtr(aRefPtr),
+ mStops(aStops),
+ mNumStops(aNumStops),
+ mExtendMode(aExtendMode),
+ mDataOwned(false) {}
+
+ ~RecordedGradientStopsCreation();
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "GradientStops Creation"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mDT;
+ ReferencePtr mRefPtr;
+ GradientStop* mStops = nullptr;
+ uint32_t mNumStops = 0;
+ ExtendMode mExtendMode;
+ bool mDataOwned;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedGradientStopsCreation(S& aStream);
+};
+
+class RecordedGradientStopsDestruction
+ : public RecordedEventDerived<RecordedGradientStopsDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedGradientStopsDestruction(ReferencePtr aRefPtr)
+ : RecordedEventDerived(GRADIENTSTOPSDESTRUCTION), mRefPtr(aRefPtr) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "GradientStops Destruction"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedGradientStopsDestruction(S& aStream);
+};
+
+class RecordedFlush : public RecordedDrawingEvent<RecordedFlush> {
+ public:
+ explicit RecordedFlush(DrawTarget* aDT) : RecordedDrawingEvent(FLUSH, aDT) {}
+
+ bool PlayEvent(Translator* aTranslator) const final;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ virtual std::string GetName() const override { return "Flush"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFlush(S& aStream);
+};
+
+class RecordedDetachAllSnapshots
+ : public RecordedDrawingEvent<RecordedDetachAllSnapshots> {
+ public:
+ explicit RecordedDetachAllSnapshots(DrawTarget* aDT)
+ : RecordedDrawingEvent(DETACHALLSNAPSHOTS, aDT) {}
+
+ bool PlayEvent(Translator* aTranslator) const final;
+
+ template <class S>
+ void Record(S& aStream) const;
+ virtual void OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const override;
+
+ virtual std::string GetName() const override { return "DetachAllSnapshots"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDetachAllSnapshots(S& aStream);
+};
+
+class RecordedSnapshot : public RecordedEventDerived<RecordedSnapshot> {
+ public:
+ RecordedSnapshot(ReferencePtr aRefPtr, DrawTarget* aDT)
+ : RecordedEventDerived(SNAPSHOT), mRefPtr(aRefPtr), mDT(aDT) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Snapshot"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+ ReferencePtr mDT;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedSnapshot(S& aStream);
+};
+
+class RecordedIntoLuminanceSource
+ : public RecordedEventDerived<RecordedIntoLuminanceSource> {
+ public:
+ RecordedIntoLuminanceSource(ReferencePtr aRefPtr, DrawTarget* aDT,
+ LuminanceType aLuminanceType, float aOpacity)
+ : RecordedEventDerived(INTOLUMINANCE),
+ mRefPtr(aRefPtr),
+ mDT(aDT),
+ mLuminanceType(aLuminanceType),
+ mOpacity(aOpacity) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "IntoLuminanceSource"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+ ReferencePtr mDT;
+ LuminanceType mLuminanceType;
+ float mOpacity;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedIntoLuminanceSource(S& aStream);
+};
+
+class RecordedFontData : public RecordedEventDerived<RecordedFontData> {
+ public:
+ static void FontDataProc(const uint8_t* aData, uint32_t aSize,
+ uint32_t aIndex, void* aBaton) {
+ auto recordedFontData = static_cast<RecordedFontData*>(aBaton);
+ recordedFontData->SetFontData(aData, aSize, aIndex);
+ }
+
+ explicit RecordedFontData(UnscaledFont* aUnscaledFont)
+ : RecordedEventDerived(FONTDATA),
+ mType(aUnscaledFont->GetType()),
+ mFontDetails() {
+ mGetFontFileDataSucceeded =
+ aUnscaledFont->GetFontFileData(&FontDataProc, this) && mData;
+ }
+
+ virtual ~RecordedFontData();
+
+ bool IsValid() const { return mGetFontFileDataSucceeded; }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Font Data"; }
+
+ void SetFontData(const uint8_t* aData, uint32_t aSize, uint32_t aIndex);
+
+ bool GetFontDetails(RecordedFontDetails& fontDetails);
+
+ private:
+ friend class RecordedEvent;
+
+ FontType mType;
+ uint8_t* mData = nullptr;
+ RecordedFontDetails mFontDetails;
+
+ bool mGetFontFileDataSucceeded;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFontData(S& aStream);
+};
+
+class RecordedFontDescriptor
+ : public RecordedEventDerived<RecordedFontDescriptor> {
+ public:
+ static void FontDescCb(const uint8_t* aData, uint32_t aSize, uint32_t aIndex,
+ void* aBaton) {
+ auto recordedFontDesc = static_cast<RecordedFontDescriptor*>(aBaton);
+ recordedFontDesc->SetFontDescriptor(aData, aSize, aIndex);
+ }
+
+ explicit RecordedFontDescriptor(UnscaledFont* aUnscaledFont)
+ : RecordedEventDerived(FONTDESC),
+ mType(aUnscaledFont->GetType()),
+ mIndex(0),
+ mRefPtr(aUnscaledFont) {
+ mHasDesc = aUnscaledFont->GetFontDescriptor(FontDescCb, this);
+ }
+
+ virtual ~RecordedFontDescriptor();
+
+ bool IsValid() const { return mHasDesc; }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Font Desc"; }
+
+ private:
+ friend class RecordedEvent;
+
+ void SetFontDescriptor(const uint8_t* aData, uint32_t aSize, uint32_t aIndex);
+
+ bool mHasDesc;
+
+ FontType mType;
+ std::vector<uint8_t> mData;
+ uint32_t mIndex;
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFontDescriptor(S& aStream);
+};
+
+class RecordedUnscaledFontCreation
+ : public RecordedEventDerived<RecordedUnscaledFontCreation> {
+ public:
+ static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize,
+ void* aBaton) {
+ auto recordedUnscaledFontCreation =
+ static_cast<RecordedUnscaledFontCreation*>(aBaton);
+ recordedUnscaledFontCreation->SetFontInstanceData(aData, aSize);
+ }
+
+ RecordedUnscaledFontCreation(UnscaledFont* aUnscaledFont,
+ RecordedFontDetails aFontDetails)
+ : RecordedEventDerived(UNSCALEDFONTCREATION),
+ mRefPtr(aUnscaledFont),
+ mFontDataKey(aFontDetails.fontDataKey),
+ mIndex(aFontDetails.index) {
+ aUnscaledFont->GetFontInstanceData(FontInstanceDataProc, this);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "UnscaledFont Creation"; }
+
+ void SetFontInstanceData(const uint8_t* aData, uint32_t aSize);
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+ uint64_t mFontDataKey;
+ uint32_t mIndex;
+ std::vector<uint8_t> mInstanceData;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedUnscaledFontCreation(S& aStream);
+};
+
+class RecordedUnscaledFontDestruction
+ : public RecordedEventDerived<RecordedUnscaledFontDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedUnscaledFontDestruction(ReferencePtr aRefPtr)
+ : RecordedEventDerived(UNSCALEDFONTDESTRUCTION), mRefPtr(aRefPtr) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "UnscaledFont Destruction"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedUnscaledFontDestruction(S& aStream);
+};
+
+class RecordedScaledFontCreation
+ : public RecordedEventDerived<RecordedScaledFontCreation> {
+ public:
+ static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize,
+ const FontVariation* aVariations,
+ uint32_t aNumVariations, void* aBaton) {
+ auto recordedScaledFontCreation =
+ static_cast<RecordedScaledFontCreation*>(aBaton);
+ recordedScaledFontCreation->SetFontInstanceData(aData, aSize, aVariations,
+ aNumVariations);
+ }
+
+ RecordedScaledFontCreation(ScaledFont* aScaledFont,
+ UnscaledFont* aUnscaledFont)
+ : RecordedEventDerived(SCALEDFONTCREATION),
+ mRefPtr(aScaledFont),
+ mUnscaledFont(aUnscaledFont),
+ mGlyphSize(aScaledFont->GetSize()) {
+ aScaledFont->GetFontInstanceData(FontInstanceDataProc, this);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "ScaledFont Creation"; }
+
+ void SetFontInstanceData(const uint8_t* aData, uint32_t aSize,
+ const FontVariation* aVariations,
+ uint32_t aNumVariations);
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+ ReferencePtr mUnscaledFont;
+ Float mGlyphSize;
+ std::vector<uint8_t> mInstanceData;
+ std::vector<FontVariation> mVariations;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedScaledFontCreation(S& aStream);
+};
+
+class RecordedScaledFontDestruction
+ : public RecordedEventDerived<RecordedScaledFontDestruction> {
+ public:
+ MOZ_IMPLICIT RecordedScaledFontDestruction(ReferencePtr aRefPtr)
+ : RecordedEventDerived(SCALEDFONTDESTRUCTION), mRefPtr(aRefPtr) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "ScaledFont Destruction"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mRefPtr;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedScaledFontDestruction(S& aStream);
+};
+
+class RecordedMaskSurface : public RecordedDrawingEvent<RecordedMaskSurface> {
+ public:
+ RecordedMaskSurface(DrawTarget* aDT, const Pattern& aPattern,
+ ReferencePtr aRefMask, const Point& aOffset,
+ const DrawOptions& aOptions)
+ : RecordedDrawingEvent(MASKSURFACE, aDT),
+ mPattern(),
+ mRefMask(aRefMask),
+ mOffset(aOffset),
+ mOptions(aOptions) {
+ StorePattern(mPattern, aPattern);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "MaskSurface"; }
+
+ private:
+ friend class RecordedEvent;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedMaskSurface(S& aStream);
+
+ PatternStorage mPattern;
+ ReferencePtr mRefMask;
+ Point mOffset;
+ DrawOptions mOptions;
+};
+
+class RecordedFilterNodeSetAttribute
+ : public RecordedEventDerived<RecordedFilterNodeSetAttribute> {
+ public:
+ enum ArgType {
+ ARGTYPE_UINT32,
+ ARGTYPE_BOOL,
+ ARGTYPE_FLOAT,
+ ARGTYPE_SIZE,
+ ARGTYPE_INTSIZE,
+ ARGTYPE_INTPOINT,
+ ARGTYPE_RECT,
+ ARGTYPE_INTRECT,
+ ARGTYPE_POINT,
+ ARGTYPE_MATRIX,
+ ARGTYPE_MATRIX5X4,
+ ARGTYPE_POINT3D,
+ ARGTYPE_COLOR,
+ ARGTYPE_FLOAT_ARRAY
+ };
+
+ template <typename T>
+ RecordedFilterNodeSetAttribute(FilterNode* aNode, uint32_t aIndex,
+ T aArgument, ArgType aArgType)
+ : RecordedEventDerived(FILTERNODESETATTRIBUTE),
+ mNode(aNode),
+ mIndex(aIndex),
+ mArgType(aArgType) {
+ mPayload.resize(sizeof(T));
+ memcpy(&mPayload.front(), &aArgument, sizeof(T));
+ }
+
+ RecordedFilterNodeSetAttribute(FilterNode* aNode, uint32_t aIndex,
+ const Float* aFloat, uint32_t aSize)
+ : RecordedEventDerived(FILTERNODESETATTRIBUTE),
+ mNode(aNode),
+ mIndex(aIndex),
+ mArgType(ARGTYPE_FLOAT_ARRAY) {
+ mPayload.resize(sizeof(Float) * aSize);
+ memcpy(&mPayload.front(), aFloat, sizeof(Float) * aSize);
+ }
+
+ bool PlayEvent(Translator* aTranslator) const override;
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "SetAttribute"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mNode;
+
+ uint32_t mIndex;
+ ArgType mArgType;
+ std::vector<uint8_t> mPayload;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFilterNodeSetAttribute(S& aStream);
+};
+
+class RecordedFilterNodeSetInput
+ : public RecordedEventDerived<RecordedFilterNodeSetInput> {
+ public:
+ RecordedFilterNodeSetInput(FilterNode* aNode, uint32_t aIndex,
+ FilterNode* aInputNode)
+ : RecordedEventDerived(FILTERNODESETINPUT),
+ mNode(aNode),
+ mIndex(aIndex),
+ mInputFilter(aInputNode),
+ mInputSurface(nullptr) {}
+
+ RecordedFilterNodeSetInput(FilterNode* aNode, uint32_t aIndex,
+ SourceSurface* aInputSurface)
+ : RecordedEventDerived(FILTERNODESETINPUT),
+ mNode(aNode),
+ mIndex(aIndex),
+ mInputFilter(nullptr),
+ mInputSurface(aInputSurface) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "SetInput"; }
+
+ private:
+ friend class RecordedEvent;
+
+ ReferencePtr mNode;
+ uint32_t mIndex;
+ ReferencePtr mInputFilter;
+ ReferencePtr mInputSurface;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedFilterNodeSetInput(S& aStream);
+};
+
+class RecordedLink : public RecordedDrawingEvent<RecordedLink> {
+ public:
+ RecordedLink(DrawTarget* aDT, const char* aDestination, const Rect& aRect)
+ : RecordedDrawingEvent(LINK, aDT),
+ mDestination(aDestination),
+ mRect(aRect) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Link"; }
+
+ private:
+ friend class RecordedEvent;
+
+ std::string mDestination;
+ Rect mRect;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedLink(S& aStream);
+};
+
+class RecordedDestination : public RecordedDrawingEvent<RecordedDestination> {
+ public:
+ RecordedDestination(DrawTarget* aDT, const char* aDestination,
+ const Point& aPoint)
+ : RecordedDrawingEvent(DESTINATION, aDT),
+ mDestination(aDestination),
+ mPoint(aPoint) {}
+
+ bool PlayEvent(Translator* aTranslator) const override;
+ template <class S>
+ void Record(S& aStream) const;
+ void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
+
+ std::string GetName() const override { return "Destination"; }
+
+ private:
+ friend class RecordedEvent;
+
+ std::string mDestination;
+ Point mPoint;
+
+ template <class S>
+ MOZ_IMPLICIT RecordedDestination(S& aStream);
+};
+
+static std::string NameFromBackend(BackendType aType) {
+ switch (aType) {
+ case BackendType::NONE:
+ return "None";
+ case BackendType::DIRECT2D:
+ return "Direct2D";
+ default:
+ return "Unknown";
+ }
+}
+
+template <class S>
+void RecordedEvent::RecordPatternData(S& aStream,
+ const PatternStorage& aPattern) const {
+ WriteElement(aStream, aPattern.mType);
+
+ switch (aPattern.mType) {
+ case PatternType::COLOR: {
+ WriteElement(aStream, *reinterpret_cast<const ColorPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ WriteElement(aStream,
+ *reinterpret_cast<const LinearGradientPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ WriteElement(aStream,
+ *reinterpret_cast<const RadialGradientPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ WriteElement(aStream,
+ *reinterpret_cast<const ConicGradientPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::SURFACE: {
+ WriteElement(aStream, *reinterpret_cast<const SurfacePatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+template <class S>
+void RecordedEvent::ReadPatternData(S& aStream,
+ PatternStorage& aPattern) const {
+ ReadElementConstrained(aStream, aPattern.mType, PatternType::COLOR,
+ kHighestPatternType);
+
+ switch (aPattern.mType) {
+ case PatternType::COLOR: {
+ ReadElement(aStream,
+ *reinterpret_cast<ColorPatternStorage*>(&aPattern.mStorage));
+ return;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ ReadElement(aStream, *reinterpret_cast<LinearGradientPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ ReadElement(aStream, *reinterpret_cast<RadialGradientPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ ReadElement(aStream, *reinterpret_cast<ConicGradientPatternStorage*>(
+ &aPattern.mStorage));
+ return;
+ }
+ case PatternType::SURFACE: {
+ SurfacePatternStorage* sps =
+ reinterpret_cast<SurfacePatternStorage*>(&aPattern.mStorage);
+ ReadElement(aStream, *sps);
+ if (sps->mExtend < ExtendMode::CLAMP ||
+ sps->mExtend > ExtendMode::REFLECT) {
+ aStream.SetIsBad();
+ return;
+ }
+
+ if (sps->mSamplingFilter < SamplingFilter::GOOD ||
+ sps->mSamplingFilter >= SamplingFilter::SENTINEL) {
+ aStream.SetIsBad();
+ }
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+inline void RecordedEvent::StorePattern(PatternStorage& aDestination,
+ const Pattern& aSource) const {
+ aDestination.mType = aSource.GetType();
+
+ switch (aSource.GetType()) {
+ case PatternType::COLOR: {
+ reinterpret_cast<ColorPatternStorage*>(&aDestination.mStorage)->mColor =
+ static_cast<const ColorPattern*>(&aSource)->mColor;
+ return;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ LinearGradientPatternStorage* store =
+ reinterpret_cast<LinearGradientPatternStorage*>(
+ &aDestination.mStorage);
+ const LinearGradientPattern* pat =
+ static_cast<const LinearGradientPattern*>(&aSource);
+ store->mBegin = pat->mBegin;
+ store->mEnd = pat->mEnd;
+ store->mMatrix = pat->mMatrix;
+ store->mStops = pat->mStops.get();
+ return;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ RadialGradientPatternStorage* store =
+ reinterpret_cast<RadialGradientPatternStorage*>(
+ &aDestination.mStorage);
+ const RadialGradientPattern* pat =
+ static_cast<const RadialGradientPattern*>(&aSource);
+ store->mCenter1 = pat->mCenter1;
+ store->mCenter2 = pat->mCenter2;
+ store->mRadius1 = pat->mRadius1;
+ store->mRadius2 = pat->mRadius2;
+ store->mMatrix = pat->mMatrix;
+ store->mStops = pat->mStops.get();
+ return;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ ConicGradientPatternStorage* store =
+ reinterpret_cast<ConicGradientPatternStorage*>(
+ &aDestination.mStorage);
+ const ConicGradientPattern* pat =
+ static_cast<const ConicGradientPattern*>(&aSource);
+ store->mCenter = pat->mCenter;
+ store->mAngle = pat->mAngle;
+ store->mStartOffset = pat->mStartOffset;
+ store->mEndOffset = pat->mEndOffset;
+ store->mMatrix = pat->mMatrix;
+ store->mStops = pat->mStops.get();
+ return;
+ }
+ case PatternType::SURFACE: {
+ SurfacePatternStorage* store =
+ reinterpret_cast<SurfacePatternStorage*>(&aDestination.mStorage);
+ const SurfacePattern* pat = static_cast<const SurfacePattern*>(&aSource);
+ store->mExtend = pat->mExtendMode;
+ store->mSamplingFilter = pat->mSamplingFilter;
+ store->mMatrix = pat->mMatrix;
+ store->mSurface = pat->mSurface;
+ store->mSamplingRect = pat->mSamplingRect;
+ return;
+ }
+ }
+}
+
+template <class S>
+void RecordedEvent::RecordStrokeOptions(
+ S& aStream, const StrokeOptions& aStrokeOptions) const {
+ JoinStyle joinStyle = aStrokeOptions.mLineJoin;
+ CapStyle capStyle = aStrokeOptions.mLineCap;
+
+ WriteElement(aStream, uint64_t(aStrokeOptions.mDashLength));
+ WriteElement(aStream, aStrokeOptions.mDashOffset);
+ WriteElement(aStream, aStrokeOptions.mLineWidth);
+ WriteElement(aStream, aStrokeOptions.mMiterLimit);
+ WriteElement(aStream, joinStyle);
+ WriteElement(aStream, capStyle);
+
+ if (!aStrokeOptions.mDashPattern) {
+ return;
+ }
+
+ aStream.write((char*)aStrokeOptions.mDashPattern,
+ sizeof(Float) * aStrokeOptions.mDashLength);
+}
+
+template <class S>
+void RecordedEvent::ReadStrokeOptions(S& aStream,
+ StrokeOptions& aStrokeOptions) {
+ uint64_t dashLength;
+ JoinStyle joinStyle;
+ CapStyle capStyle;
+
+ ReadElement(aStream, dashLength);
+ ReadElement(aStream, aStrokeOptions.mDashOffset);
+ ReadElement(aStream, aStrokeOptions.mLineWidth);
+ ReadElement(aStream, aStrokeOptions.mMiterLimit);
+ ReadElementConstrained(aStream, joinStyle, JoinStyle::BEVEL,
+ JoinStyle::MITER_OR_BEVEL);
+ ReadElementConstrained(aStream, capStyle, CapStyle::BUTT, CapStyle::SQUARE);
+ // On 32 bit we truncate the value of dashLength.
+ // See also bug 811850 for history.
+ aStrokeOptions.mDashLength = size_t(dashLength);
+ aStrokeOptions.mLineJoin = joinStyle;
+ aStrokeOptions.mLineCap = capStyle;
+
+ if (!aStrokeOptions.mDashLength || !aStream.good()) {
+ return;
+ }
+
+ mDashPatternStorage.resize(aStrokeOptions.mDashLength);
+ aStrokeOptions.mDashPattern = &mDashPatternStorage.front();
+ aStream.read((char*)aStrokeOptions.mDashPattern,
+ sizeof(Float) * aStrokeOptions.mDashLength);
+}
+
+template <class S>
+static void ReadDrawOptions(S& aStream, DrawOptions& aDrawOptions) {
+ ReadElement(aStream, aDrawOptions);
+ if (aDrawOptions.mAntialiasMode < AntialiasMode::NONE ||
+ aDrawOptions.mAntialiasMode > AntialiasMode::DEFAULT) {
+ aStream.SetIsBad();
+ return;
+ }
+
+ if (aDrawOptions.mCompositionOp < CompositionOp::OP_CLEAR ||
+ aDrawOptions.mCompositionOp > CompositionOp::OP_COUNT) {
+ aStream.SetIsBad();
+ }
+}
+
+template <class S>
+static void ReadDrawSurfaceOptions(S& aStream,
+ DrawSurfaceOptions& aDrawSurfaceOptions) {
+ ReadElement(aStream, aDrawSurfaceOptions);
+ if (aDrawSurfaceOptions.mSamplingFilter < SamplingFilter::GOOD ||
+ aDrawSurfaceOptions.mSamplingFilter >= SamplingFilter::SENTINEL) {
+ aStream.SetIsBad();
+ return;
+ }
+
+ if (aDrawSurfaceOptions.mSamplingBounds < SamplingBounds::UNBOUNDED ||
+ aDrawSurfaceOptions.mSamplingBounds > SamplingBounds::BOUNDED) {
+ aStream.SetIsBad();
+ }
+}
+
+inline void RecordedEvent::OutputSimplePatternInfo(
+ const PatternStorage& aStorage, std::stringstream& aOutput) const {
+ switch (aStorage.mType) {
+ case PatternType::COLOR: {
+ const DeviceColor color =
+ reinterpret_cast<const ColorPatternStorage*>(&aStorage.mStorage)
+ ->mColor;
+ aOutput << "DeviceColor: (" << color.r << ", " << color.g << ", "
+ << color.b << ", " << color.a << ")";
+ return;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ const LinearGradientPatternStorage* store =
+ reinterpret_cast<const LinearGradientPatternStorage*>(
+ &aStorage.mStorage);
+
+ aOutput << "LinearGradient (" << store->mBegin.x << ", "
+ << store->mBegin.y << ") - (" << store->mEnd.x << ", "
+ << store->mEnd.y << ") Stops: " << store->mStops;
+ return;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ const RadialGradientPatternStorage* store =
+ reinterpret_cast<const RadialGradientPatternStorage*>(
+ &aStorage.mStorage);
+ aOutput << "RadialGradient (Center 1: (" << store->mCenter1.x << ", "
+ << store->mCenter2.y << ") Radius 2: " << store->mRadius2;
+ return;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ const ConicGradientPatternStorage* store =
+ reinterpret_cast<const ConicGradientPatternStorage*>(
+ &aStorage.mStorage);
+ aOutput << "ConicGradient (Center: (" << store->mCenter.x << ", "
+ << store->mCenter.y << ") Angle: " << store->mAngle
+ << " Range:" << store->mStartOffset << " - " << store->mEndOffset;
+ return;
+ }
+ case PatternType::SURFACE: {
+ const SurfacePatternStorage* store =
+ reinterpret_cast<const SurfacePatternStorage*>(&aStorage.mStorage);
+ aOutput << "Surface (0x" << store->mSurface << ")";
+ return;
+ }
+ }
+}
+
+template <class T>
+template <class S>
+RecordedDrawingEvent<T>::RecordedDrawingEvent(RecordedEvent::EventType aType,
+ S& aStream)
+ : RecordedEventDerived<T>(aType) {
+ ReadElement(aStream, mDT);
+}
+
+template <class T>
+template <class S>
+void RecordedDrawingEvent<T>::Record(S& aStream) const {
+ WriteElement(aStream, mDT);
+}
+
+inline bool RecordedDrawTargetCreation::PlayEvent(
+ Translator* aTranslator) const {
+ RefPtr<DrawTarget> newDT =
+ aTranslator->CreateDrawTarget(mRefPtr, mRect.Size(), mFormat);
+
+ // If we couldn't create a DrawTarget this will probably cause us to crash
+ // with nullptr later in the playback, so return false to abort.
+ if (!newDT) {
+ return false;
+ }
+
+ if (mHasExistingData) {
+ Rect dataRect(0, 0, mExistingData->GetSize().width,
+ mExistingData->GetSize().height);
+ newDT->DrawSurface(mExistingData, dataRect, dataRect);
+ }
+
+ return true;
+}
+
+template <class S>
+void RecordedDrawTargetCreation::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mBackendType);
+ WriteElement(aStream, mRect);
+ WriteElement(aStream, mFormat);
+ WriteElement(aStream, mHasExistingData);
+
+ if (mHasExistingData) {
+ MOZ_ASSERT(mExistingData);
+ MOZ_ASSERT(mExistingData->GetSize() == mRect.Size());
+ RefPtr<DataSourceSurface> dataSurf = mExistingData->GetDataSurface();
+
+ DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ);
+ for (int y = 0; y < mRect.height; y++) {
+ aStream.write((const char*)map.GetData() + y * map.GetStride(),
+ BytesPerPixel(mFormat) * mRect.width);
+ }
+ }
+}
+
+template <class S>
+RecordedDrawTargetCreation::RecordedDrawTargetCreation(S& aStream)
+ : RecordedEventDerived(DRAWTARGETCREATION), mExistingData(nullptr) {
+ ReadElement(aStream, mRefPtr);
+ ReadElementConstrained(aStream, mBackendType, BackendType::NONE,
+ BackendType::WEBRENDER_TEXT);
+ ReadElement(aStream, mRect);
+ ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32,
+ SurfaceFormat::UNKNOWN);
+ ReadElement(aStream, mHasExistingData);
+
+ if (mHasExistingData) {
+ RefPtr<DataSourceSurface> dataSurf =
+ Factory::CreateDataSourceSurface(mRect.Size(), mFormat);
+ if (!dataSurf) {
+ gfxWarning()
+ << "RecordedDrawTargetCreation had to reset mHasExistingData";
+ mHasExistingData = false;
+ return;
+ }
+
+ DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ);
+ for (int y = 0; y < mRect.height; y++) {
+ aStream.read((char*)map.GetData() + y * map.GetStride(),
+ BytesPerPixel(mFormat) * mRect.width);
+ }
+ mExistingData = dataSurf;
+ }
+}
+
+inline void RecordedDrawTargetCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] DrawTarget Creation (Type: "
+ << NameFromBackend(mBackendType) << ", Size: " << mRect.width
+ << "x" << mRect.height << ")";
+}
+
+inline bool RecordedDrawTargetDestruction::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->RemoveDrawTarget(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedDrawTargetDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedDrawTargetDestruction::RecordedDrawTargetDestruction(S& aStream)
+ : RecordedEventDerived(DRAWTARGETDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedDrawTargetDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] DrawTarget Destruction";
+}
+
+inline bool RecordedCreateSimilarDrawTarget::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* drawTarget = aTranslator->LookupDrawTarget(mDT);
+ if (!drawTarget) {
+ return false;
+ }
+
+ RefPtr<DrawTarget> newDT =
+ drawTarget->CreateSimilarDrawTarget(mSize, mFormat);
+
+ // If we couldn't create a DrawTarget this will probably cause us to crash
+ // with nullptr later in the playback, so return false to abort.
+ if (!newDT) {
+ return false;
+ }
+
+ aTranslator->AddDrawTarget(mRefPtr, newDT);
+ return true;
+}
+
+template <class S>
+void RecordedCreateSimilarDrawTarget::Record(S& aStream) const {
+ WriteElement(aStream, mDT);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mSize);
+ WriteElement(aStream, mFormat);
+}
+
+template <class S>
+RecordedCreateSimilarDrawTarget::RecordedCreateSimilarDrawTarget(S& aStream)
+ : RecordedEventDerived(CREATESIMILARDRAWTARGET) {
+ ReadElement(aStream, mDT);
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mSize);
+ ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32,
+ SurfaceFormat::UNKNOWN);
+}
+
+inline void RecordedCreateSimilarDrawTarget::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] [" << mRefPtr
+ << "] CreateSimilarDrawTarget (Size: " << mSize.width << "x"
+ << mSize.height << ")";
+}
+
+inline bool RecordedCreateDrawTargetForFilter::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ IntRect baseRect = dt->GetRect();
+
+ auto maxRect = IntRect(IntPoint(0, 0), mMaxSize);
+
+ auto clone = dt->GetTransform();
+ bool invertible = clone.Invert();
+ // mSourceRect is in filter space. The filter outputs from mSourceRect need
+ // to be drawn at mDestPoint in user space.
+ Rect userSpaceSource = Rect(mDestPoint, mSourceRect.Size());
+ if (invertible) {
+ // Try to reduce the source rect so that it's not much bigger
+ // than the draw target. The result is not minimal. Examples
+ // are left as an exercise for the reader.
+ auto destRect = IntRectToRect(baseRect);
+ Rect userSpaceBounds = clone.TransformBounds(destRect);
+ userSpaceSource = userSpaceSource.Intersect(userSpaceBounds);
+ }
+
+ // Compute how much we moved the top-left of the source rect by, and use that
+ // to compute the new dest point, and move our intersected source rect back
+ // into the (new) filter space.
+ Point shift = userSpaceSource.TopLeft() - mDestPoint;
+ Rect filterSpaceSource =
+ Rect(mSourceRect.TopLeft() + shift, userSpaceSource.Size());
+
+ baseRect = RoundedOut(filterSpaceSource);
+ FilterNode* filter = aTranslator->LookupFilterNode(mFilter);
+ if (!filter) {
+ return false;
+ }
+
+ IntRect transformedRect = filter->MapRectToSource(
+ baseRect, maxRect, aTranslator->LookupFilterNode(mSource));
+
+ // Intersect with maxRect to make sure we didn't end up with something bigger
+ transformedRect = transformedRect.Intersect(maxRect);
+
+ // If we end up with an empty rect make it 1x1 so that things don't break.
+ if (transformedRect.IsEmpty()) {
+ transformedRect = IntRect(0, 0, 1, 1);
+ }
+
+ RefPtr<DrawTarget> newDT =
+ dt->CreateSimilarDrawTarget(transformedRect.Size(), mFormat);
+ newDT =
+ gfx::Factory::CreateOffsetDrawTarget(newDT, transformedRect.TopLeft());
+
+ // If we couldn't create a DrawTarget this will probably cause us to crash
+ // with nullptr later in the playback, so return false to abort.
+ if (!newDT) {
+ return false;
+ }
+
+ aTranslator->AddDrawTarget(mRefPtr, newDT);
+ return true;
+}
+
+inline bool RecordedCreateClippedDrawTarget::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ RefPtr<DrawTarget> newDT = dt->CreateClippedDrawTarget(mBounds, mFormat);
+
+ // If we couldn't create a DrawTarget this will probably cause us to crash
+ // with nullptr later in the playback, so return false to abort.
+ if (!newDT) {
+ return false;
+ }
+
+ aTranslator->AddDrawTarget(mRefPtr, newDT);
+ return true;
+}
+
+template <class S>
+void RecordedCreateClippedDrawTarget::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mBounds);
+ WriteElement(aStream, mFormat);
+}
+
+template <class S>
+RecordedCreateClippedDrawTarget::RecordedCreateClippedDrawTarget(S& aStream)
+ : RecordedDrawingEvent(CREATECLIPPEDDRAWTARGET, aStream) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mBounds);
+ ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32,
+ SurfaceFormat::UNKNOWN);
+}
+
+inline void RecordedCreateClippedDrawTarget::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] CreateClippedDrawTarget ()";
+}
+
+template <class S>
+void RecordedCreateDrawTargetForFilter::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mMaxSize);
+ WriteElement(aStream, mFormat);
+ WriteElement(aStream, mFilter);
+ WriteElement(aStream, mSource);
+ WriteElement(aStream, mSourceRect);
+ WriteElement(aStream, mDestPoint);
+}
+
+template <class S>
+RecordedCreateDrawTargetForFilter::RecordedCreateDrawTargetForFilter(S& aStream)
+ : RecordedDrawingEvent(CREATEDRAWTARGETFORFILTER, aStream) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mMaxSize);
+ ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32,
+ SurfaceFormat::UNKNOWN);
+ ReadElement(aStream, mFilter);
+ ReadElement(aStream, mSource);
+ ReadElement(aStream, mSourceRect);
+ ReadElement(aStream, mDestPoint);
+}
+
+inline void RecordedCreateDrawTargetForFilter::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] CreateDrawTargetForFilter ()";
+}
+
+struct GenericPattern {
+ GenericPattern(const PatternStorage& aStorage, Translator* aTranslator)
+ : mPattern(nullptr), mTranslator(aTranslator) {
+ mStorage = const_cast<PatternStorage*>(&aStorage);
+ }
+
+ ~GenericPattern() {
+ if (mPattern) {
+ mPattern->~Pattern();
+ }
+ }
+
+ operator Pattern*() {
+ switch (mStorage->mType) {
+ case PatternType::COLOR:
+ return new (mColPat) ColorPattern(
+ reinterpret_cast<ColorPatternStorage*>(&mStorage->mStorage)
+ ->mColor);
+ case PatternType::SURFACE: {
+ SurfacePatternStorage* storage =
+ reinterpret_cast<SurfacePatternStorage*>(&mStorage->mStorage);
+ mPattern = new (mSurfPat)
+ SurfacePattern(mTranslator->LookupSourceSurface(storage->mSurface),
+ storage->mExtend, storage->mMatrix,
+ storage->mSamplingFilter, storage->mSamplingRect);
+ return mPattern;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ LinearGradientPatternStorage* storage =
+ reinterpret_cast<LinearGradientPatternStorage*>(
+ &mStorage->mStorage);
+ mPattern = new (mLinGradPat) LinearGradientPattern(
+ storage->mBegin, storage->mEnd,
+ storage->mStops ? mTranslator->LookupGradientStops(storage->mStops)
+ : nullptr,
+ storage->mMatrix);
+ return mPattern;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ RadialGradientPatternStorage* storage =
+ reinterpret_cast<RadialGradientPatternStorage*>(
+ &mStorage->mStorage);
+ mPattern = new (mRadGradPat) RadialGradientPattern(
+ storage->mCenter1, storage->mCenter2, storage->mRadius1,
+ storage->mRadius2,
+ storage->mStops ? mTranslator->LookupGradientStops(storage->mStops)
+ : nullptr,
+ storage->mMatrix);
+ return mPattern;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ ConicGradientPatternStorage* storage =
+ reinterpret_cast<ConicGradientPatternStorage*>(&mStorage->mStorage);
+ mPattern = new (mConGradPat) ConicGradientPattern(
+ storage->mCenter, storage->mAngle, storage->mStartOffset,
+ storage->mEndOffset,
+ storage->mStops ? mTranslator->LookupGradientStops(storage->mStops)
+ : nullptr,
+ storage->mMatrix);
+ return mPattern;
+ }
+ default:
+ return new (mColPat) ColorPattern(DeviceColor());
+ }
+
+ return mPattern;
+ }
+
+ union {
+ char mColPat[sizeof(ColorPattern)];
+ char mLinGradPat[sizeof(LinearGradientPattern)];
+ char mRadGradPat[sizeof(RadialGradientPattern)];
+ char mConGradPat[sizeof(ConicGradientPattern)];
+ char mSurfPat[sizeof(SurfacePattern)];
+ };
+
+ PatternStorage* mStorage;
+ Pattern* mPattern;
+ Translator* mTranslator;
+};
+
+inline bool RecordedFillRect::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->FillRect(mRect, *GenericPattern(mPattern, aTranslator), mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedFillRect::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRect);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+}
+
+template <class S>
+RecordedFillRect::RecordedFillRect(S& aStream)
+ : RecordedDrawingEvent(FILLRECT, aStream) {
+ ReadElement(aStream, mRect);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+}
+
+inline void RecordedFillRect::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] FillRect (" << mRect.X() << ", "
+ << mRect.Y() << " - " << mRect.Width() << " x "
+ << mRect.Height() << ") ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+inline bool RecordedStrokeRect::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->StrokeRect(mRect, *GenericPattern(mPattern, aTranslator), mStrokeOptions,
+ mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedStrokeRect::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRect);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+ RecordStrokeOptions(aStream, mStrokeOptions);
+}
+
+template <class S>
+RecordedStrokeRect::RecordedStrokeRect(S& aStream)
+ : RecordedDrawingEvent(STROKERECT, aStream) {
+ ReadElement(aStream, mRect);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+ ReadStrokeOptions(aStream, mStrokeOptions);
+}
+
+inline void RecordedStrokeRect::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] StrokeRect (" << mRect.X() << ", "
+ << mRect.Y() << " - " << mRect.Width() << " x "
+ << mRect.Height()
+ << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+inline bool RecordedStrokeLine::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->StrokeLine(mBegin, mEnd, *GenericPattern(mPattern, aTranslator),
+ mStrokeOptions, mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedStrokeLine::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mBegin);
+ WriteElement(aStream, mEnd);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+ RecordStrokeOptions(aStream, mStrokeOptions);
+}
+
+template <class S>
+RecordedStrokeLine::RecordedStrokeLine(S& aStream)
+ : RecordedDrawingEvent(STROKELINE, aStream) {
+ ReadElement(aStream, mBegin);
+ ReadElement(aStream, mEnd);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+ ReadStrokeOptions(aStream, mStrokeOptions);
+}
+
+inline void RecordedStrokeLine::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] StrokeLine (" << mBegin.x << ", "
+ << mBegin.y << " - " << mEnd.x << ", " << mEnd.y
+ << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+inline bool RecordedFill::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->Fill(aTranslator->LookupPath(mPath),
+ *GenericPattern(mPattern, aTranslator), mOptions);
+ return true;
+}
+
+template <class S>
+RecordedFill::RecordedFill(S& aStream) : RecordedDrawingEvent(FILL, aStream) {
+ ReadElement(aStream, mPath);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+}
+
+template <class S>
+void RecordedFill::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mPath);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+}
+
+inline void RecordedFill::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] Fill (" << mPath << ") ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+inline RecordedFillGlyphs::~RecordedFillGlyphs() { delete[] mGlyphs; }
+
+inline bool RecordedFillGlyphs::PlayEvent(Translator* aTranslator) const {
+ if (mNumGlyphs > 0 && !mGlyphs) {
+ // Glyph allocation failed
+ return false;
+ }
+
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ ScaledFont* scaledFont = aTranslator->LookupScaledFont(mScaledFont);
+ if (!scaledFont) {
+ return false;
+ }
+
+ GlyphBuffer buffer;
+ buffer.mGlyphs = mGlyphs;
+ buffer.mNumGlyphs = mNumGlyphs;
+ dt->FillGlyphs(scaledFont, buffer, *GenericPattern(mPattern, aTranslator),
+ mOptions);
+ return true;
+}
+
+template <class S>
+RecordedFillGlyphs::RecordedFillGlyphs(S& aStream)
+ : RecordedDrawingEvent(FILLGLYPHS, aStream) {
+ ReadElement(aStream, mScaledFont);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+ ReadElement(aStream, mNumGlyphs);
+ if (!aStream.good() || mNumGlyphs <= 0) {
+ return;
+ }
+
+ mGlyphs = new (fallible) Glyph[mNumGlyphs];
+ if (!mGlyphs) {
+ gfxCriticalNote << "RecordedFillGlyphs failed to allocate glyphs of size "
+ << mNumGlyphs;
+ aStream.SetIsBad();
+ } else {
+ aStream.read((char*)mGlyphs, sizeof(Glyph) * mNumGlyphs);
+ }
+}
+
+template <class S>
+void RecordedFillGlyphs::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mScaledFont);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+ WriteElement(aStream, mNumGlyphs);
+ aStream.write((char*)mGlyphs, sizeof(Glyph) * mNumGlyphs);
+}
+
+inline void RecordedFillGlyphs::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] FillGlyphs (" << mScaledFont << ") ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+inline bool RecordedMask::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->Mask(*GenericPattern(mSource, aTranslator),
+ *GenericPattern(mMask, aTranslator), mOptions);
+ return true;
+}
+
+template <class S>
+RecordedMask::RecordedMask(S& aStream) : RecordedDrawingEvent(MASK, aStream) {
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mSource);
+ ReadPatternData(aStream, mMask);
+}
+
+template <class S>
+void RecordedMask::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mSource);
+ RecordPatternData(aStream, mMask);
+}
+
+inline void RecordedMask::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] Mask (Source: ";
+ OutputSimplePatternInfo(mSource, aStringStream);
+ aStringStream << " Mask: ";
+ OutputSimplePatternInfo(mMask, aStringStream);
+}
+
+inline bool RecordedStroke::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ Path* path = aTranslator->LookupPath(mPath);
+ if (!path) {
+ return false;
+ }
+
+ dt->Stroke(path, *GenericPattern(mPattern, aTranslator), mStrokeOptions,
+ mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedStroke::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mPath);
+ WriteElement(aStream, mOptions);
+ RecordPatternData(aStream, mPattern);
+ RecordStrokeOptions(aStream, mStrokeOptions);
+}
+
+template <class S>
+RecordedStroke::RecordedStroke(S& aStream)
+ : RecordedDrawingEvent(STROKE, aStream) {
+ ReadElement(aStream, mPath);
+ ReadDrawOptions(aStream, mOptions);
+ ReadPatternData(aStream, mPattern);
+ ReadStrokeOptions(aStream, mStrokeOptions);
+}
+
+inline void RecordedStroke::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] Stroke (" << mPath
+ << ") LineWidth: " << mStrokeOptions.mLineWidth << "px ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+inline bool RecordedClearRect::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->ClearRect(mRect);
+ return true;
+}
+
+template <class S>
+void RecordedClearRect::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRect);
+}
+
+template <class S>
+RecordedClearRect::RecordedClearRect(S& aStream)
+ : RecordedDrawingEvent(CLEARRECT, aStream) {
+ ReadElement(aStream, mRect);
+}
+
+inline void RecordedClearRect::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] ClearRect (" << mRect.X() << ", "
+ << mRect.Y() << " - " << mRect.Width() << " x "
+ << mRect.Height() << ") ";
+}
+
+inline bool RecordedCopySurface::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* surface = aTranslator->LookupSourceSurface(mSourceSurface);
+ if (!surface) {
+ return false;
+ }
+
+ dt->CopySurface(surface, mSourceRect, mDest);
+ return true;
+}
+
+template <class S>
+void RecordedCopySurface::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mSourceSurface);
+ WriteElement(aStream, mSourceRect);
+ WriteElement(aStream, mDest);
+}
+
+template <class S>
+RecordedCopySurface::RecordedCopySurface(S& aStream)
+ : RecordedDrawingEvent(COPYSURFACE, aStream) {
+ ReadElement(aStream, mSourceSurface);
+ ReadElement(aStream, mSourceRect);
+ ReadElement(aStream, mDest);
+}
+
+inline void RecordedCopySurface::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] CopySurface (" << mSourceSurface << ")";
+}
+
+inline bool RecordedPushClip::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ Path* path = aTranslator->LookupPath(mPath);
+ if (!path) {
+ return false;
+ }
+
+ dt->PushClip(path);
+ return true;
+}
+
+template <class S>
+void RecordedPushClip::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mPath);
+}
+
+template <class S>
+RecordedPushClip::RecordedPushClip(S& aStream)
+ : RecordedDrawingEvent(PUSHCLIP, aStream) {
+ ReadElement(aStream, mPath);
+}
+
+inline void RecordedPushClip::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] PushClip (" << mPath << ") ";
+}
+
+inline bool RecordedPushClipRect::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->PushClipRect(mRect);
+ return true;
+}
+
+template <class S>
+void RecordedPushClipRect::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRect);
+}
+
+template <class S>
+RecordedPushClipRect::RecordedPushClipRect(S& aStream)
+ : RecordedDrawingEvent(PUSHCLIPRECT, aStream) {
+ ReadElement(aStream, mRect);
+}
+
+inline void RecordedPushClipRect::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] PushClipRect (" << mRect.X() << ", "
+ << mRect.Y() << " - " << mRect.Width() << " x "
+ << mRect.Height() << ") ";
+}
+
+inline bool RecordedPopClip::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->PopClip();
+ return true;
+}
+
+template <class S>
+void RecordedPopClip::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+}
+
+template <class S>
+RecordedPopClip::RecordedPopClip(S& aStream)
+ : RecordedDrawingEvent(POPCLIP, aStream) {}
+
+inline void RecordedPopClip::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] PopClip";
+}
+
+inline bool RecordedPushLayer::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* mask =
+ mMask ? aTranslator->LookupSourceSurface(mMask) : nullptr;
+ dt->PushLayer(mOpaque, mOpacity, mask, mMaskTransform, mBounds,
+ mCopyBackground);
+ return true;
+}
+
+template <class S>
+void RecordedPushLayer::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mOpaque);
+ WriteElement(aStream, mOpacity);
+ WriteElement(aStream, mMask);
+ WriteElement(aStream, mMaskTransform);
+ WriteElement(aStream, mBounds);
+ WriteElement(aStream, mCopyBackground);
+}
+
+template <class S>
+RecordedPushLayer::RecordedPushLayer(S& aStream)
+ : RecordedDrawingEvent(PUSHLAYER, aStream) {
+ ReadElement(aStream, mOpaque);
+ ReadElement(aStream, mOpacity);
+ ReadElement(aStream, mMask);
+ ReadElement(aStream, mMaskTransform);
+ ReadElement(aStream, mBounds);
+ ReadElement(aStream, mCopyBackground);
+}
+
+inline void RecordedPushLayer::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] PushPLayer (Opaque=" << mOpaque
+ << ", Opacity=" << mOpacity << ", Mask Ref=" << mMask << ") ";
+}
+
+inline bool RecordedPushLayerWithBlend::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* mask =
+ mMask ? aTranslator->LookupSourceSurface(mMask) : nullptr;
+ dt->PushLayerWithBlend(mOpaque, mOpacity, mask, mMaskTransform, mBounds,
+ mCopyBackground, mCompositionOp);
+ return true;
+}
+
+template <class S>
+void RecordedPushLayerWithBlend::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mOpaque);
+ WriteElement(aStream, mOpacity);
+ WriteElement(aStream, mMask);
+ WriteElement(aStream, mMaskTransform);
+ WriteElement(aStream, mBounds);
+ WriteElement(aStream, mCopyBackground);
+ WriteElement(aStream, mCompositionOp);
+}
+
+template <class S>
+RecordedPushLayerWithBlend::RecordedPushLayerWithBlend(S& aStream)
+ : RecordedDrawingEvent(PUSHLAYERWITHBLEND, aStream) {
+ ReadElement(aStream, mOpaque);
+ ReadElement(aStream, mOpacity);
+ ReadElement(aStream, mMask);
+ ReadElement(aStream, mMaskTransform);
+ ReadElement(aStream, mBounds);
+ ReadElement(aStream, mCopyBackground);
+ ReadElementConstrained(aStream, mCompositionOp, CompositionOp::OP_OVER,
+ CompositionOp::OP_COUNT);
+}
+
+inline void RecordedPushLayerWithBlend::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] PushLayerWithBlend (Opaque=" << mOpaque
+ << ", Opacity=" << mOpacity << ", Mask Ref=" << mMask << ") ";
+}
+
+inline bool RecordedPopLayer::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->PopLayer();
+ return true;
+}
+
+template <class S>
+void RecordedPopLayer::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+}
+
+template <class S>
+RecordedPopLayer::RecordedPopLayer(S& aStream)
+ : RecordedDrawingEvent(POPLAYER, aStream) {}
+
+inline void RecordedPopLayer::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] PopLayer";
+}
+
+inline bool RecordedSetTransform::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ // If we're drawing to the reference DT, then we need to manually apply
+ // its initial transform, otherwise we'll just clobber it with only the
+ // the transform that was visible to the code doing the recording.
+ if (dt == aTranslator->GetReferenceDrawTarget()) {
+ dt->SetTransform(mTransform *
+ aTranslator->GetReferenceDrawTargetTransform());
+ } else {
+ dt->SetTransform(mTransform);
+ }
+
+ return true;
+}
+
+template <class S>
+void RecordedSetTransform::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mTransform);
+}
+
+template <class S>
+RecordedSetTransform::RecordedSetTransform(S& aStream)
+ : RecordedDrawingEvent(SETTRANSFORM, aStream) {
+ ReadElement(aStream, mTransform);
+}
+
+inline void RecordedSetTransform::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] SetTransform [ " << mTransform._11 << " "
+ << mTransform._12 << " ; " << mTransform._21 << " "
+ << mTransform._22 << " ; " << mTransform._31 << " "
+ << mTransform._32 << " ]";
+}
+
+inline bool RecordedDrawSurface::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* surface = aTranslator->LookupSourceSurface(mRefSource);
+ if (!surface) {
+ return false;
+ }
+
+ dt->DrawSurface(surface, mDest, mSource, mDSOptions, mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedDrawSurface::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRefSource);
+ WriteElement(aStream, mDest);
+ WriteElement(aStream, mSource);
+ WriteElement(aStream, mDSOptions);
+ WriteElement(aStream, mOptions);
+}
+
+template <class S>
+RecordedDrawSurface::RecordedDrawSurface(S& aStream)
+ : RecordedDrawingEvent(DRAWSURFACE, aStream) {
+ ReadElement(aStream, mRefSource);
+ ReadElement(aStream, mDest);
+ ReadElement(aStream, mSource);
+ ReadDrawSurfaceOptions(aStream, mDSOptions);
+ ReadDrawOptions(aStream, mOptions);
+}
+
+inline void RecordedDrawSurface::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] DrawSurface (" << mRefSource << ")";
+}
+
+inline bool RecordedDrawDependentSurface::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->DrawDependentSurface(mDT, mId, mDest);
+ return true;
+}
+
+template <class S>
+void RecordedDrawDependentSurface::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mId);
+ WriteElement(aStream, mDest);
+}
+
+template <class S>
+RecordedDrawDependentSurface::RecordedDrawDependentSurface(S& aStream)
+ : RecordedDrawingEvent(DRAWDEPENDENTSURFACE, aStream) {
+ ReadElement(aStream, mId);
+ ReadElement(aStream, mDest);
+}
+
+inline void RecordedDrawDependentSurface::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] DrawDependentSurface (" << mId << ")";
+}
+
+inline bool RecordedDrawFilter::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ FilterNode* filter = aTranslator->LookupFilterNode(mNode);
+ if (!filter) {
+ return false;
+ }
+
+ dt->DrawFilter(filter, mSourceRect, mDestPoint, mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedDrawFilter::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mNode);
+ WriteElement(aStream, mSourceRect);
+ WriteElement(aStream, mDestPoint);
+ WriteElement(aStream, mOptions);
+}
+
+template <class S>
+RecordedDrawFilter::RecordedDrawFilter(S& aStream)
+ : RecordedDrawingEvent(DRAWFILTER, aStream) {
+ ReadElement(aStream, mNode);
+ ReadElement(aStream, mSourceRect);
+ ReadElement(aStream, mDestPoint);
+ ReadDrawOptions(aStream, mOptions);
+}
+
+inline void RecordedDrawFilter::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] DrawFilter (" << mNode << ")";
+}
+
+inline bool RecordedDrawSurfaceWithShadow::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* surface = aTranslator->LookupSourceSurface(mRefSource);
+ if (!surface) {
+ return false;
+ }
+
+ dt->DrawSurfaceWithShadow(surface, mDest, mShadow, mOp);
+ return true;
+}
+
+template <class S>
+void RecordedDrawSurfaceWithShadow::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRefSource);
+ WriteElement(aStream, mDest);
+ WriteElement(aStream, mShadow);
+ WriteElement(aStream, mOp);
+}
+
+template <class S>
+RecordedDrawSurfaceWithShadow::RecordedDrawSurfaceWithShadow(S& aStream)
+ : RecordedDrawingEvent(DRAWSURFACEWITHSHADOW, aStream) {
+ ReadElement(aStream, mRefSource);
+ ReadElement(aStream, mDest);
+ ReadElement(aStream, mShadow);
+ ReadElementConstrained(aStream, mOp, CompositionOp::OP_OVER,
+ CompositionOp::OP_COUNT);
+}
+
+inline void RecordedDrawSurfaceWithShadow::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] DrawSurfaceWithShadow (" << mRefSource
+ << ") DeviceColor: (" << mShadow.mColor.r << ", "
+ << mShadow.mColor.g << ", " << mShadow.mColor.b << ", "
+ << mShadow.mColor.a << ")";
+}
+
+inline RecordedPathCreation::RecordedPathCreation(DrawTarget* aDT,
+ PathRecording* aPath)
+ : RecordedEventDerived(PATHCREATION),
+ mDT(aDT),
+ mRefPtr(aPath),
+ mFillRule(aPath->mFillRule),
+ mPath(aPath) {}
+
+inline bool RecordedPathCreation::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* drawTarget = aTranslator->LookupDrawTarget(mDT);
+ if (!drawTarget) {
+ return false;
+ }
+
+ RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(mFillRule);
+ if (!mPathOps->CheckedStreamToSink(*builder)) {
+ return false;
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ aTranslator->AddPath(mRefPtr, path);
+ return true;
+}
+
+template <class S>
+void RecordedPathCreation::Record(S& aStream) const {
+ WriteElement(aStream, mDT);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mFillRule);
+ mPath->mPathOps.Record(aStream);
+}
+
+template <class S>
+RecordedPathCreation::RecordedPathCreation(S& aStream)
+ : RecordedEventDerived(PATHCREATION) {
+ ReadElement(aStream, mDT);
+ ReadElement(aStream, mRefPtr);
+ ReadElementConstrained(aStream, mFillRule, FillRule::FILL_WINDING,
+ FillRule::FILL_EVEN_ODD);
+ mPathOps = MakeUnique<PathOps>(aStream);
+}
+
+inline void RecordedPathCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ size_t numberOfOps =
+ mPath ? mPath->mPathOps.NumberOfOps() : mPathOps->NumberOfOps();
+ aStringStream << "[" << mDT << "] [" << mRefPtr
+ << "] Path created (OpCount: " << numberOfOps << ")";
+}
+inline bool RecordedPathDestruction::PlayEvent(Translator* aTranslator) const {
+ aTranslator->RemovePath(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedPathDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedPathDestruction::RecordedPathDestruction(S& aStream)
+ : RecordedEventDerived(PATHDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedPathDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] Path Destroyed";
+}
+
+inline RecordedSourceSurfaceCreation::~RecordedSourceSurfaceCreation() {
+ if (mDataOwned) {
+ delete[] mData;
+ }
+}
+
+inline bool RecordedSourceSurfaceCreation::PlayEvent(
+ Translator* aTranslator) const {
+ if (!mData) {
+ return false;
+ }
+
+ RefPtr<SourceSurface> src = Factory::CreateWrappingDataSourceSurface(
+ mData, mSize.width * BytesPerPixel(mFormat), mSize, mFormat,
+ [](void* aClosure) { delete[] static_cast<uint8_t*>(aClosure); }, mData);
+ if (src) {
+ mDataOwned = false;
+ }
+
+ aTranslator->AddSourceSurface(mRefPtr, src);
+ return true;
+}
+
+template <class S>
+void RecordedSourceSurfaceCreation::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mSize);
+ WriteElement(aStream, mFormat);
+ MOZ_ASSERT(mData);
+ size_t dataFormatWidth = BytesPerPixel(mFormat) * mSize.width;
+ const char* endSrc = (const char*)(mData + (mSize.height * mStride));
+ for (const char* src = (const char*)mData; src < endSrc; src += mStride) {
+ aStream.write(src, dataFormatWidth);
+ }
+}
+
+template <class S>
+RecordedSourceSurfaceCreation::RecordedSourceSurfaceCreation(S& aStream)
+ : RecordedEventDerived(SOURCESURFACECREATION), mDataOwned(true) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mSize);
+ ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32,
+ SurfaceFormat::UNKNOWN);
+
+ if (!Factory::AllowedSurfaceSize(mSize)) {
+ gfxCriticalNote << "RecordedSourceSurfaceCreation read invalid size "
+ << mSize;
+ aStream.SetIsBad();
+ }
+
+ if (!aStream.good()) {
+ return;
+ }
+
+ size_t size = 0;
+ if (mSize.width >= 0 && mSize.height >= 0) {
+ size = size_t(mSize.width) * size_t(mSize.height) * BytesPerPixel(mFormat);
+ mData = new (fallible) uint8_t[size];
+ }
+ if (!mData) {
+ gfxCriticalNote
+ << "RecordedSourceSurfaceCreation failed to allocate data of size "
+ << size;
+ aStream.SetIsBad();
+ } else {
+ aStream.read((char*)mData, size);
+ }
+}
+
+inline void RecordedSourceSurfaceCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr
+ << "] SourceSurface created (Size: " << mSize.width << "x"
+ << mSize.height << ")";
+}
+
+inline bool RecordedSourceSurfaceDestruction::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->RemoveSourceSurface(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedSourceSurfaceDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedSourceSurfaceDestruction::RecordedSourceSurfaceDestruction(S& aStream)
+ : RecordedEventDerived(SOURCESURFACEDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedSourceSurfaceDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] SourceSurface Destroyed";
+}
+
+inline bool RecordedOptimizeSourceSurface::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* surface = aTranslator->LookupSourceSurface(mSurface);
+ if (!surface) {
+ return false;
+ }
+
+ RefPtr<SourceSurface> optimizedSurface = dt->OptimizeSourceSurface(surface);
+ aTranslator->AddSourceSurface(mOptimizedSurface, optimizedSurface);
+ return true;
+}
+
+template <class S>
+void RecordedOptimizeSourceSurface::Record(S& aStream) const {
+ WriteElement(aStream, mSurface);
+ WriteElement(aStream, mDT);
+ WriteElement(aStream, mOptimizedSurface);
+}
+
+template <class S>
+RecordedOptimizeSourceSurface::RecordedOptimizeSourceSurface(S& aStream)
+ : RecordedEventDerived(OPTIMIZESOURCESURFACE) {
+ ReadElement(aStream, mSurface);
+ ReadElement(aStream, mDT);
+ ReadElement(aStream, mOptimizedSurface);
+}
+
+inline void RecordedOptimizeSourceSurface::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mSurface << "] Surface Optimized (DT: " << mDT << ")";
+}
+
+inline bool RecordedExternalSurfaceCreation::PlayEvent(
+ Translator* aTranslator) const {
+ RefPtr<SourceSurface> surface = aTranslator->LookupExternalSurface(mKey);
+ if (!surface) {
+ return false;
+ }
+
+ aTranslator->AddSourceSurface(mRefPtr, surface);
+ return true;
+}
+
+template <class S>
+void RecordedExternalSurfaceCreation::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mKey);
+}
+
+template <class S>
+RecordedExternalSurfaceCreation::RecordedExternalSurfaceCreation(S& aStream)
+ : RecordedEventDerived(EXTERNALSURFACECREATION) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mKey);
+}
+
+inline void RecordedExternalSurfaceCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr
+ << "] SourceSurfaceSharedData created (Key: " << mKey << ")";
+}
+
+inline RecordedFilterNodeCreation::~RecordedFilterNodeCreation() = default;
+
+inline bool RecordedFilterNodeCreation::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* drawTarget = aTranslator->LookupDrawTarget(mDT);
+ if (!drawTarget) {
+ return false;
+ }
+
+ RefPtr<FilterNode> node = drawTarget->CreateFilter(mType);
+ aTranslator->AddFilterNode(mRefPtr, node);
+ return true;
+}
+
+template <class S>
+void RecordedFilterNodeCreation::Record(S& aStream) const {
+ WriteElement(aStream, mDT);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mType);
+}
+
+template <class S>
+RecordedFilterNodeCreation::RecordedFilterNodeCreation(S& aStream)
+ : RecordedEventDerived(FILTERNODECREATION) {
+ ReadElement(aStream, mDT);
+ ReadElement(aStream, mRefPtr);
+ ReadElementConstrained(aStream, mType, FilterType::BLEND,
+ FilterType::OPACITY);
+}
+
+inline void RecordedFilterNodeCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] CreateFilter [" << mRefPtr
+ << "] FilterNode created (Type: " << int(mType) << ")";
+}
+
+inline bool RecordedFilterNodeDestruction::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->RemoveFilterNode(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedFilterNodeDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedFilterNodeDestruction::RecordedFilterNodeDestruction(S& aStream)
+ : RecordedEventDerived(FILTERNODEDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedFilterNodeDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] FilterNode Destroyed";
+}
+
+inline RecordedGradientStopsCreation::~RecordedGradientStopsCreation() {
+ if (mDataOwned) {
+ delete[] mStops;
+ }
+}
+
+inline bool RecordedGradientStopsCreation::PlayEvent(
+ Translator* aTranslator) const {
+ if (mNumStops > 0 && !mStops) {
+ // Stops allocation failed
+ return false;
+ }
+
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ RefPtr<GradientStops> src =
+ aTranslator->GetOrCreateGradientStops(dt, mStops, mNumStops, mExtendMode);
+ aTranslator->AddGradientStops(mRefPtr, src);
+ return true;
+}
+
+template <class S>
+void RecordedGradientStopsCreation::Record(S& aStream) const {
+ WriteElement(aStream, mDT);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mExtendMode);
+ WriteElement(aStream, mNumStops);
+ aStream.write((const char*)mStops, mNumStops * sizeof(GradientStop));
+}
+
+template <class S>
+RecordedGradientStopsCreation::RecordedGradientStopsCreation(S& aStream)
+ : RecordedEventDerived(GRADIENTSTOPSCREATION), mDataOwned(true) {
+ ReadElement(aStream, mDT);
+ ReadElement(aStream, mRefPtr);
+ ReadElementConstrained(aStream, mExtendMode, ExtendMode::CLAMP,
+ ExtendMode::REFLECT);
+ ReadElement(aStream, mNumStops);
+ if (!aStream.good() || mNumStops <= 0) {
+ return;
+ }
+
+ mStops = new (fallible) GradientStop[mNumStops];
+ if (!mStops) {
+ gfxCriticalNote
+ << "RecordedGradientStopsCreation failed to allocate stops of size "
+ << mNumStops;
+ aStream.SetIsBad();
+ } else {
+ aStream.read((char*)mStops, mNumStops * sizeof(GradientStop));
+ }
+}
+
+inline void RecordedGradientStopsCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] [" << mRefPtr
+ << "] GradientStops created (Stops: " << mNumStops << ")";
+}
+
+inline bool RecordedGradientStopsDestruction::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->RemoveGradientStops(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedGradientStopsDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedGradientStopsDestruction::RecordedGradientStopsDestruction(S& aStream)
+ : RecordedEventDerived(GRADIENTSTOPSDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedGradientStopsDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] GradientStops Destroyed";
+}
+
+inline bool RecordedIntoLuminanceSource::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ RefPtr<SourceSurface> src = dt->IntoLuminanceSource(mLuminanceType, mOpacity);
+ aTranslator->AddSourceSurface(mRefPtr, src);
+ return true;
+}
+
+template <class S>
+void RecordedIntoLuminanceSource::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mDT);
+ WriteElement(aStream, mLuminanceType);
+ WriteElement(aStream, mOpacity);
+}
+
+template <class S>
+RecordedIntoLuminanceSource::RecordedIntoLuminanceSource(S& aStream)
+ : RecordedEventDerived(INTOLUMINANCE) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mDT);
+ ReadElementConstrained(aStream, mLuminanceType, LuminanceType::LUMINANCE,
+ LuminanceType::LINEARRGB);
+ ReadElement(aStream, mOpacity);
+}
+
+inline void RecordedIntoLuminanceSource::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] Into Luminance Source (DT: " << mDT
+ << ")";
+}
+
+inline bool RecordedFlush::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->Flush();
+ return true;
+}
+
+template <class S>
+void RecordedFlush::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+}
+
+template <class S>
+RecordedFlush::RecordedFlush(S& aStream)
+ : RecordedDrawingEvent(FLUSH, aStream) {}
+
+inline void RecordedFlush::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] Flush";
+}
+
+inline bool RecordedDetachAllSnapshots::PlayEvent(
+ Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ dt->DetachAllSnapshots();
+ return true;
+}
+
+template <class S>
+void RecordedDetachAllSnapshots::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+}
+
+template <class S>
+RecordedDetachAllSnapshots::RecordedDetachAllSnapshots(S& aStream)
+ : RecordedDrawingEvent(DETACHALLSNAPSHOTS, aStream) {}
+
+inline void RecordedDetachAllSnapshots::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] DetachAllSnapshots";
+}
+
+inline bool RecordedSnapshot::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ RefPtr<SourceSurface> src = aTranslator->LookupDrawTarget(mDT)->Snapshot();
+ aTranslator->AddSourceSurface(mRefPtr, src);
+ return true;
+}
+
+template <class S>
+void RecordedSnapshot::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mDT);
+}
+
+template <class S>
+RecordedSnapshot::RecordedSnapshot(S& aStream)
+ : RecordedEventDerived(SNAPSHOT) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mDT);
+}
+
+inline void RecordedSnapshot::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] Snapshot Created (DT: " << mDT << ")";
+}
+
+inline RecordedFontData::~RecordedFontData() { delete[] mData; }
+
+inline bool RecordedFontData::PlayEvent(Translator* aTranslator) const {
+ if (!mData) {
+ return false;
+ }
+
+ RefPtr<NativeFontResource> fontResource = Factory::CreateNativeFontResource(
+ mData, mFontDetails.size, mType, aTranslator->GetFontContext());
+ if (!fontResource) {
+ return false;
+ }
+
+ aTranslator->AddNativeFontResource(mFontDetails.fontDataKey, fontResource);
+ return true;
+}
+
+template <class S>
+void RecordedFontData::Record(S& aStream) const {
+ MOZ_ASSERT(mGetFontFileDataSucceeded);
+
+ WriteElement(aStream, mType);
+ WriteElement(aStream, mFontDetails.fontDataKey);
+ if (!mData) {
+ WriteElement(aStream, 0);
+ } else {
+ WriteElement(aStream, mFontDetails.size);
+ aStream.write((const char*)mData, mFontDetails.size);
+ }
+}
+
+inline void RecordedFontData::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "Font Data of size " << mFontDetails.size;
+}
+
+inline void RecordedFontData::SetFontData(const uint8_t* aData, uint32_t aSize,
+ uint32_t aIndex) {
+ mData = new (fallible) uint8_t[aSize];
+ if (!mData) {
+ gfxCriticalNote
+ << "RecordedFontData failed to allocate data for recording of size "
+ << aSize;
+ } else {
+ memcpy(mData, aData, aSize);
+ }
+ mFontDetails.fontDataKey = SFNTData::GetUniqueKey(aData, aSize, 0, nullptr);
+ mFontDetails.size = aSize;
+ mFontDetails.index = aIndex;
+}
+
+inline bool RecordedFontData::GetFontDetails(RecordedFontDetails& fontDetails) {
+ if (!mGetFontFileDataSucceeded) {
+ return false;
+ }
+
+ fontDetails.fontDataKey = mFontDetails.fontDataKey;
+ fontDetails.size = mFontDetails.size;
+ fontDetails.index = mFontDetails.index;
+ return true;
+}
+
+template <class S>
+RecordedFontData::RecordedFontData(S& aStream)
+ : RecordedEventDerived(FONTDATA), mType(FontType::UNKNOWN) {
+ ReadElementConstrained(aStream, mType, FontType::DWRITE, FontType::UNKNOWN);
+ ReadElement(aStream, mFontDetails.fontDataKey);
+ ReadElement(aStream, mFontDetails.size);
+ if (!mFontDetails.size || !aStream.good()) {
+ return;
+ }
+
+ mData = new (fallible) uint8_t[mFontDetails.size];
+ if (!mData) {
+ gfxCriticalNote
+ << "RecordedFontData failed to allocate data for playback of size "
+ << mFontDetails.size;
+ aStream.SetIsBad();
+ } else {
+ aStream.read((char*)mData, mFontDetails.size);
+ }
+}
+
+inline RecordedFontDescriptor::~RecordedFontDescriptor() = default;
+
+inline bool RecordedFontDescriptor::PlayEvent(Translator* aTranslator) const {
+ RefPtr<UnscaledFont> font = Factory::CreateUnscaledFontFromFontDescriptor(
+ mType, mData.data(), mData.size(), mIndex);
+ if (!font) {
+ gfxDevCrash(LogReason::InvalidFont)
+ << "Failed creating UnscaledFont of type " << int(mType)
+ << " from font descriptor";
+ return false;
+ }
+
+ aTranslator->AddUnscaledFont(mRefPtr, font);
+ return true;
+}
+
+template <class S>
+void RecordedFontDescriptor::Record(S& aStream) const {
+ MOZ_ASSERT(mHasDesc);
+ WriteElement(aStream, mType);
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mIndex);
+ WriteElement(aStream, (size_t)mData.size());
+ if (mData.size()) {
+ aStream.write((char*)mData.data(), mData.size());
+ }
+}
+
+inline void RecordedFontDescriptor::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] Font Descriptor";
+}
+
+inline void RecordedFontDescriptor::SetFontDescriptor(const uint8_t* aData,
+ uint32_t aSize,
+ uint32_t aIndex) {
+ mData.assign(aData, aData + aSize);
+ mIndex = aIndex;
+}
+
+template <class S>
+RecordedFontDescriptor::RecordedFontDescriptor(S& aStream)
+ : RecordedEventDerived(FONTDESC) {
+ ReadElementConstrained(aStream, mType, FontType::DWRITE, FontType::UNKNOWN);
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mIndex);
+
+ size_t size;
+ ReadElement(aStream, size);
+ if (!aStream.good()) {
+ return;
+ }
+ if (size) {
+ mData.resize(size);
+ aStream.read((char*)mData.data(), size);
+ }
+}
+
+inline bool RecordedUnscaledFontCreation::PlayEvent(
+ Translator* aTranslator) const {
+ NativeFontResource* fontResource =
+ aTranslator->LookupNativeFontResource(mFontDataKey);
+ if (!fontResource) {
+ gfxDevCrash(LogReason::NativeFontResourceNotFound)
+ << "NativeFontResource lookup failed for key |" << hexa(mFontDataKey)
+ << "|.";
+ return false;
+ }
+
+ RefPtr<UnscaledFont> unscaledFont = fontResource->CreateUnscaledFont(
+ mIndex, mInstanceData.data(), mInstanceData.size());
+ aTranslator->AddUnscaledFont(mRefPtr, unscaledFont);
+ return true;
+}
+
+template <class S>
+void RecordedUnscaledFontCreation::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mFontDataKey);
+ WriteElement(aStream, mIndex);
+ WriteElement(aStream, (size_t)mInstanceData.size());
+ if (mInstanceData.size()) {
+ aStream.write((char*)mInstanceData.data(), mInstanceData.size());
+ }
+}
+
+inline void RecordedUnscaledFontCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] UnscaledFont Created";
+}
+
+inline void RecordedUnscaledFontCreation::SetFontInstanceData(
+ const uint8_t* aData, uint32_t aSize) {
+ if (aSize) {
+ mInstanceData.assign(aData, aData + aSize);
+ }
+}
+
+template <class S>
+RecordedUnscaledFontCreation::RecordedUnscaledFontCreation(S& aStream)
+ : RecordedEventDerived(UNSCALEDFONTCREATION) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mFontDataKey);
+ ReadElement(aStream, mIndex);
+
+ size_t size;
+ ReadElement(aStream, size);
+ if (!aStream.good()) {
+ return;
+ }
+ if (size) {
+ mInstanceData.resize(size);
+ aStream.read((char*)mInstanceData.data(), size);
+ }
+}
+
+inline bool RecordedUnscaledFontDestruction::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->RemoveUnscaledFont(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedUnscaledFontDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedUnscaledFontDestruction::RecordedUnscaledFontDestruction(S& aStream)
+ : RecordedEventDerived(UNSCALEDFONTDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedUnscaledFontDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] UnscaledFont Destroyed";
+}
+
+inline bool RecordedScaledFontCreation::PlayEvent(
+ Translator* aTranslator) const {
+ UnscaledFont* unscaledFont = aTranslator->LookupUnscaledFont(mUnscaledFont);
+ if (!unscaledFont) {
+ gfxDevCrash(LogReason::UnscaledFontNotFound)
+ << "UnscaledFont lookup failed for key |" << hexa(mUnscaledFont)
+ << "|.";
+ return false;
+ }
+
+ RefPtr<ScaledFont> scaledFont = unscaledFont->CreateScaledFont(
+ mGlyphSize, mInstanceData.data(), mInstanceData.size(),
+ mVariations.data(), mVariations.size());
+
+ aTranslator->AddScaledFont(mRefPtr, scaledFont);
+ return true;
+}
+
+template <class S>
+void RecordedScaledFontCreation::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+ WriteElement(aStream, mUnscaledFont);
+ WriteElement(aStream, mGlyphSize);
+ WriteElement(aStream, (size_t)mInstanceData.size());
+ if (mInstanceData.size()) {
+ aStream.write((char*)mInstanceData.data(), mInstanceData.size());
+ }
+ WriteElement(aStream, (size_t)mVariations.size());
+ if (mVariations.size()) {
+ aStream.write((char*)mVariations.data(),
+ sizeof(FontVariation) * mVariations.size());
+ }
+}
+
+inline void RecordedScaledFontCreation::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] ScaledFont Created";
+}
+
+inline void RecordedScaledFontCreation::SetFontInstanceData(
+ const uint8_t* aData, uint32_t aSize, const FontVariation* aVariations,
+ uint32_t aNumVariations) {
+ if (aSize) {
+ mInstanceData.assign(aData, aData + aSize);
+ }
+ if (aNumVariations) {
+ mVariations.assign(aVariations, aVariations + aNumVariations);
+ }
+}
+
+template <class S>
+RecordedScaledFontCreation::RecordedScaledFontCreation(S& aStream)
+ : RecordedEventDerived(SCALEDFONTCREATION) {
+ ReadElement(aStream, mRefPtr);
+ ReadElement(aStream, mUnscaledFont);
+ ReadElement(aStream, mGlyphSize);
+
+ size_t size;
+ ReadElement(aStream, size);
+ if (!aStream.good()) {
+ return;
+ }
+ if (size) {
+ mInstanceData.resize(size);
+ aStream.read((char*)mInstanceData.data(), size);
+ }
+
+ size_t numVariations;
+ ReadElement(aStream, numVariations);
+ if (!aStream.good()) {
+ return;
+ }
+ if (numVariations) {
+ mVariations.resize(numVariations);
+ aStream.read((char*)mVariations.data(),
+ sizeof(FontVariation) * numVariations);
+ }
+}
+
+inline bool RecordedScaledFontDestruction::PlayEvent(
+ Translator* aTranslator) const {
+ aTranslator->RemoveScaledFont(mRefPtr);
+ return true;
+}
+
+template <class S>
+void RecordedScaledFontDestruction::Record(S& aStream) const {
+ WriteElement(aStream, mRefPtr);
+}
+
+template <class S>
+RecordedScaledFontDestruction::RecordedScaledFontDestruction(S& aStream)
+ : RecordedEventDerived(SCALEDFONTDESTRUCTION) {
+ ReadElement(aStream, mRefPtr);
+}
+
+inline void RecordedScaledFontDestruction::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mRefPtr << "] ScaledFont Destroyed";
+}
+
+inline bool RecordedMaskSurface::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+
+ SourceSurface* surface = aTranslator->LookupSourceSurface(mRefMask);
+ if (!surface) {
+ return false;
+ }
+
+ dt->MaskSurface(*GenericPattern(mPattern, aTranslator), surface, mOffset,
+ mOptions);
+ return true;
+}
+
+template <class S>
+void RecordedMaskSurface::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ RecordPatternData(aStream, mPattern);
+ WriteElement(aStream, mRefMask);
+ WriteElement(aStream, mOffset);
+ WriteElement(aStream, mOptions);
+}
+
+template <class S>
+RecordedMaskSurface::RecordedMaskSurface(S& aStream)
+ : RecordedDrawingEvent(MASKSURFACE, aStream) {
+ ReadPatternData(aStream, mPattern);
+ ReadElement(aStream, mRefMask);
+ ReadElement(aStream, mOffset);
+ ReadDrawOptions(aStream, mOptions);
+}
+
+inline void RecordedMaskSurface::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mDT << "] MaskSurface (" << mRefMask << ") Offset: ("
+ << mOffset.x << "x" << mOffset.y << ") Pattern: ";
+ OutputSimplePatternInfo(mPattern, aStringStream);
+}
+
+template <typename T>
+void ReplaySetAttribute(FilterNode* aNode, uint32_t aIndex, T aValue) {
+ aNode->SetAttribute(aIndex, aValue);
+}
+
+inline bool RecordedFilterNodeSetAttribute::PlayEvent(
+ Translator* aTranslator) const {
+ FilterNode* node = aTranslator->LookupFilterNode(mNode);
+ if (!node) {
+ return false;
+ }
+
+#define REPLAY_SET_ATTRIBUTE(type, argtype) \
+ case ARGTYPE_##argtype: \
+ ReplaySetAttribute(node, mIndex, *(type*)&mPayload.front()); \
+ break
+
+ switch (mArgType) {
+ REPLAY_SET_ATTRIBUTE(bool, BOOL);
+ REPLAY_SET_ATTRIBUTE(uint32_t, UINT32);
+ REPLAY_SET_ATTRIBUTE(Float, FLOAT);
+ REPLAY_SET_ATTRIBUTE(Size, SIZE);
+ REPLAY_SET_ATTRIBUTE(IntSize, INTSIZE);
+ REPLAY_SET_ATTRIBUTE(IntPoint, INTPOINT);
+ REPLAY_SET_ATTRIBUTE(Rect, RECT);
+ REPLAY_SET_ATTRIBUTE(IntRect, INTRECT);
+ REPLAY_SET_ATTRIBUTE(Point, POINT);
+ REPLAY_SET_ATTRIBUTE(Matrix, MATRIX);
+ REPLAY_SET_ATTRIBUTE(Matrix5x4, MATRIX5X4);
+ REPLAY_SET_ATTRIBUTE(Point3D, POINT3D);
+ REPLAY_SET_ATTRIBUTE(DeviceColor, COLOR);
+ case ARGTYPE_FLOAT_ARRAY:
+ node->SetAttribute(mIndex,
+ reinterpret_cast<const Float*>(&mPayload.front()),
+ mPayload.size() / sizeof(Float));
+ break;
+ }
+
+ return true;
+}
+
+template <class S>
+void RecordedFilterNodeSetAttribute::Record(S& aStream) const {
+ WriteElement(aStream, mNode);
+ WriteElement(aStream, mIndex);
+ WriteElement(aStream, mArgType);
+ WriteElement(aStream, uint64_t(mPayload.size()));
+ aStream.write((const char*)&mPayload.front(), mPayload.size());
+}
+
+template <class S>
+RecordedFilterNodeSetAttribute::RecordedFilterNodeSetAttribute(S& aStream)
+ : RecordedEventDerived(FILTERNODESETATTRIBUTE) {
+ ReadElement(aStream, mNode);
+ ReadElement(aStream, mIndex);
+ ReadElementConstrained(aStream, mArgType, ArgType::ARGTYPE_UINT32,
+ ArgType::ARGTYPE_FLOAT_ARRAY);
+ uint64_t size;
+ ReadElement(aStream, size);
+ if (!aStream.good()) {
+ return;
+ }
+
+ mPayload.resize(size_t(size));
+ aStream.read((char*)&mPayload.front(), size);
+}
+
+inline void RecordedFilterNodeSetAttribute::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ")";
+}
+
+inline bool RecordedFilterNodeSetInput::PlayEvent(
+ Translator* aTranslator) const {
+ FilterNode* node = aTranslator->LookupFilterNode(mNode);
+ if (!node) {
+ return false;
+ }
+
+ if (mInputFilter) {
+ node->SetInput(mIndex, aTranslator->LookupFilterNode(mInputFilter));
+ } else {
+ node->SetInput(mIndex, aTranslator->LookupSourceSurface(mInputSurface));
+ }
+
+ return true;
+}
+
+template <class S>
+void RecordedFilterNodeSetInput::Record(S& aStream) const {
+ WriteElement(aStream, mNode);
+ WriteElement(aStream, mIndex);
+ WriteElement(aStream, mInputFilter);
+ WriteElement(aStream, mInputSurface);
+}
+
+template <class S>
+RecordedFilterNodeSetInput::RecordedFilterNodeSetInput(S& aStream)
+ : RecordedEventDerived(FILTERNODESETINPUT) {
+ ReadElement(aStream, mNode);
+ ReadElement(aStream, mIndex);
+ ReadElement(aStream, mInputFilter);
+ ReadElement(aStream, mInputSurface);
+}
+
+inline void RecordedFilterNodeSetInput::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ", ";
+
+ if (mInputFilter) {
+ aStringStream << "Filter: " << mInputFilter;
+ } else {
+ aStringStream << "Surface: " << mInputSurface;
+ }
+
+ aStringStream << ")";
+}
+
+inline bool RecordedLink::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+ dt->Link(mDestination.c_str(), mRect);
+ return true;
+}
+
+template <class S>
+void RecordedLink::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mRect);
+ uint32_t len = mDestination.length();
+ WriteElement(aStream, len);
+ if (len) {
+ aStream.write(mDestination.data(), len);
+ }
+}
+
+template <class S>
+RecordedLink::RecordedLink(S& aStream) : RecordedDrawingEvent(LINK, aStream) {
+ ReadElement(aStream, mRect);
+ uint32_t len;
+ ReadElement(aStream, len);
+ mDestination.resize(size_t(len));
+ if (len && aStream.good()) {
+ aStream.read(&mDestination.front(), len);
+ }
+}
+
+inline void RecordedLink::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "Link [" << mDestination << " @ " << mRect << "]";
+}
+
+inline bool RecordedDestination::PlayEvent(Translator* aTranslator) const {
+ DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
+ if (!dt) {
+ return false;
+ }
+ dt->Destination(mDestination.c_str(), mPoint);
+ return true;
+}
+
+template <class S>
+void RecordedDestination::Record(S& aStream) const {
+ RecordedDrawingEvent::Record(aStream);
+ WriteElement(aStream, mPoint);
+ uint32_t len = mDestination.length();
+ WriteElement(aStream, len);
+ if (len) {
+ aStream.write(mDestination.data(), len);
+ }
+}
+
+template <class S>
+RecordedDestination::RecordedDestination(S& aStream)
+ : RecordedDrawingEvent(DESTINATION, aStream) {
+ ReadElement(aStream, mPoint);
+ uint32_t len;
+ ReadElement(aStream, len);
+ mDestination.resize(size_t(len));
+ if (len && aStream.good()) {
+ aStream.read(&mDestination.front(), len);
+ }
+}
+
+inline void RecordedDestination::OutputSimpleEventInfo(
+ std::stringstream& aStringStream) const {
+ aStringStream << "Destination [" << mDestination << " @ " << mPoint << "]";
+}
+
+#define FOR_EACH_EVENT(f) \
+ f(DRAWTARGETCREATION, RecordedDrawTargetCreation); \
+ f(DRAWTARGETDESTRUCTION, RecordedDrawTargetDestruction); \
+ f(FILLRECT, RecordedFillRect); \
+ f(STROKERECT, RecordedStrokeRect); \
+ f(STROKELINE, RecordedStrokeLine); \
+ f(CLEARRECT, RecordedClearRect); \
+ f(COPYSURFACE, RecordedCopySurface); \
+ f(SETTRANSFORM, RecordedSetTransform); \
+ f(PUSHCLIPRECT, RecordedPushClipRect); \
+ f(PUSHCLIP, RecordedPushClip); \
+ f(POPCLIP, RecordedPopClip); \
+ f(FILL, RecordedFill); \
+ f(FILLGLYPHS, RecordedFillGlyphs); \
+ f(MASK, RecordedMask); \
+ f(STROKE, RecordedStroke); \
+ f(DRAWSURFACE, RecordedDrawSurface); \
+ f(DRAWDEPENDENTSURFACE, RecordedDrawDependentSurface); \
+ f(DRAWSURFACEWITHSHADOW, RecordedDrawSurfaceWithShadow); \
+ f(DRAWFILTER, RecordedDrawFilter); \
+ f(PATHCREATION, RecordedPathCreation); \
+ f(PATHDESTRUCTION, RecordedPathDestruction); \
+ f(SOURCESURFACECREATION, RecordedSourceSurfaceCreation); \
+ f(SOURCESURFACEDESTRUCTION, RecordedSourceSurfaceDestruction); \
+ f(FILTERNODECREATION, RecordedFilterNodeCreation); \
+ f(FILTERNODEDESTRUCTION, RecordedFilterNodeDestruction); \
+ f(GRADIENTSTOPSCREATION, RecordedGradientStopsCreation); \
+ f(GRADIENTSTOPSDESTRUCTION, RecordedGradientStopsDestruction); \
+ f(SNAPSHOT, RecordedSnapshot); \
+ f(SCALEDFONTCREATION, RecordedScaledFontCreation); \
+ f(SCALEDFONTDESTRUCTION, RecordedScaledFontDestruction); \
+ f(MASKSURFACE, RecordedMaskSurface); \
+ f(FILTERNODESETATTRIBUTE, RecordedFilterNodeSetAttribute); \
+ f(FILTERNODESETINPUT, RecordedFilterNodeSetInput); \
+ f(CREATESIMILARDRAWTARGET, RecordedCreateSimilarDrawTarget); \
+ f(CREATECLIPPEDDRAWTARGET, RecordedCreateClippedDrawTarget); \
+ f(CREATEDRAWTARGETFORFILTER, RecordedCreateDrawTargetForFilter); \
+ f(FONTDATA, RecordedFontData); \
+ f(FONTDESC, RecordedFontDescriptor); \
+ f(PUSHLAYER, RecordedPushLayer); \
+ f(PUSHLAYERWITHBLEND, RecordedPushLayerWithBlend); \
+ f(POPLAYER, RecordedPopLayer); \
+ f(UNSCALEDFONTCREATION, RecordedUnscaledFontCreation); \
+ f(UNSCALEDFONTDESTRUCTION, RecordedUnscaledFontDestruction); \
+ f(INTOLUMINANCE, RecordedIntoLuminanceSource); \
+ f(EXTERNALSURFACECREATION, RecordedExternalSurfaceCreation); \
+ f(FLUSH, RecordedFlush); \
+ f(DETACHALLSNAPSHOTS, RecordedDetachAllSnapshots); \
+ f(OPTIMIZESOURCESURFACE, RecordedOptimizeSourceSurface); \
+ f(LINK, RecordedLink); \
+ f(DESTINATION, RecordedDestination);
+
+#define DO_WITH_EVENT_TYPE(_typeenum, _class) \
+ case _typeenum: { \
+ auto e = _class(aStream); \
+ return aAction(&e); \
+ }
+
+template <class S>
+bool RecordedEvent::DoWithEvent(
+ S& aStream, EventType aType,
+ const std::function<bool(RecordedEvent*)>& aAction) {
+ switch (aType) {
+ FOR_EACH_EVENT(DO_WITH_EVENT_TYPE)
+ default:
+ return false;
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/RecordingTypes.h b/gfx/2d/RecordingTypes.h
new file mode 100644
index 0000000000..818e9c1729
--- /dev/null
+++ b/gfx/2d/RecordingTypes.h
@@ -0,0 +1,71 @@
+/* -*- 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_RECORDINGTYPES_H_
+#define MOZILLA_GFX_RECORDINGTYPES_H_
+
+#include <ostream>
+#include <vector>
+
+#include "Logging.h"
+
+namespace mozilla {
+namespace gfx {
+
+template <class S, class T>
+struct ElementStreamFormat {
+ static void Write(S& aStream, const T& aElement) {
+ aStream.write(reinterpret_cast<const char*>(&aElement), sizeof(T));
+ }
+ static void Read(S& aStream, T& aElement) {
+ aStream.read(reinterpret_cast<char*>(&aElement), sizeof(T));
+ }
+};
+
+template <class S, class T>
+void WriteElement(S& aStream, const T& aElement) {
+ ElementStreamFormat<S, T>::Write(aStream, aElement);
+}
+template <class S, class T>
+void WriteVector(S& aStream, const std::vector<T>& aVector) {
+ size_t size = aVector.size();
+ WriteElement(aStream, size);
+ if (size) {
+ aStream.write(reinterpret_cast<const char*>(aVector.data()),
+ sizeof(T) * size);
+ }
+}
+
+// ReadElement is disabled for enum types. Use ReadElementConstrained instead.
+template <class S, class T,
+ typename = typename std::enable_if<!std::is_enum<T>::value>::type>
+void ReadElement(S& aStream, T& aElement) {
+ ElementStreamFormat<S, T>::Read(aStream, aElement);
+}
+template <class S, class T>
+void ReadElementConstrained(S& aStream, T& aElement, const T& aMinValue,
+ const T& aMaxValue) {
+ ElementStreamFormat<S, T>::Read(aStream, aElement);
+ if (aElement < aMinValue || aElement > aMaxValue) {
+ aStream.SetIsBad();
+ }
+}
+template <class S, class T>
+void ReadVector(S& aStream, std::vector<T>& aVector) {
+ size_t size;
+ ReadElement(aStream, size);
+ if (size && aStream.good()) {
+ aVector.resize(size);
+ aStream.read(reinterpret_cast<char*>(aVector.data()), sizeof(T) * size);
+ } else {
+ aVector.clear();
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_RECORDINGTYPES_H_ */
diff --git a/gfx/2d/Rect.h b/gfx/2d/Rect.h
new file mode 100644
index 0000000000..f52437bbdc
--- /dev/null
+++ b/gfx/2d/Rect.h
@@ -0,0 +1,495 @@
+/* -*- 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_RECT_H_
+#define MOZILLA_GFX_RECT_H_
+
+#include "BaseRect.h"
+#include "BaseMargin.h"
+#include "NumericTools.h"
+#include "Point.h"
+#include "Tools.h"
+#include "mozilla/Maybe.h"
+
+#include <cmath>
+#include <cstdint>
+
+namespace mozilla {
+
+template <typename>
+struct IsPixel;
+
+namespace gfx {
+
+template <class Units, class F>
+struct RectTyped;
+
+template <class Units>
+struct MOZ_EMPTY_BASES IntMarginTyped
+ : public BaseMargin<int32_t, IntMarginTyped<Units> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BaseMargin<int32_t, IntMarginTyped<Units> > Super;
+
+ IntMarginTyped() : Super() {
+ static_assert(sizeof(IntMarginTyped) == sizeof(int32_t) * 4,
+ "Would be unfortunate otherwise!");
+ }
+ IntMarginTyped(int32_t aTop, int32_t aRight, int32_t aBottom, int32_t aLeft)
+ : Super(aTop, aRight, aBottom, aLeft) {}
+
+ // XXX When all of the code is ported, the following functions to convert
+ // to and from unknown types should be removed.
+
+ static IntMarginTyped<Units> FromUnknownMargin(
+ const IntMarginTyped<UnknownUnits>& aMargin) {
+ return IntMarginTyped<Units>(aMargin.top, aMargin.right, aMargin.bottom,
+ aMargin.left);
+ }
+
+ IntMarginTyped<UnknownUnits> ToUnknownMargin() const {
+ return IntMarginTyped<UnknownUnits>(this->top, this->right, this->bottom,
+ this->left);
+ }
+};
+typedef IntMarginTyped<UnknownUnits> IntMargin;
+
+template <class Units, class F = Float>
+struct MarginTyped : public BaseMargin<F, MarginTyped<Units, F> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BaseMargin<F, MarginTyped<Units, F> > Super;
+
+ MarginTyped() : Super() {}
+ MarginTyped(F aTop, F aRight, F aBottom, F aLeft)
+ : Super(aTop, aRight, aBottom, aLeft) {}
+ explicit MarginTyped(const IntMarginTyped<Units>& aMargin)
+ : Super(F(aMargin.top), F(aMargin.right), F(aMargin.bottom),
+ F(aMargin.left)) {}
+
+ bool WithinEpsilonOf(const MarginTyped& aOther, F aEpsilon) const {
+ return fabs(this->left - aOther.left) < aEpsilon &&
+ fabs(this->top - aOther.top) < aEpsilon &&
+ fabs(this->right - aOther.right) < aEpsilon &&
+ fabs(this->bottom - aOther.bottom) < aEpsilon;
+ }
+
+ IntMarginTyped<Units> Rounded() const {
+ return IntMarginTyped<Units>(int32_t(std::floor(this->top + 0.5f)),
+ int32_t(std::floor(this->right + 0.5f)),
+ int32_t(std::floor(this->bottom + 0.5f)),
+ int32_t(std::floor(this->left + 0.5f)));
+ }
+};
+typedef MarginTyped<UnknownUnits> Margin;
+typedef MarginTyped<UnknownUnits, double> MarginDouble;
+
+template <class Units>
+IntMarginTyped<Units> RoundedToInt(const MarginTyped<Units>& aMargin) {
+ return aMargin.Rounded();
+}
+
+template <class Units>
+struct MOZ_EMPTY_BASES IntRectTyped
+ : public BaseRect<int32_t, IntRectTyped<Units>, IntPointTyped<Units>,
+ IntSizeTyped<Units>, IntMarginTyped<Units> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BaseRect<int32_t, IntRectTyped<Units>, IntPointTyped<Units>,
+ IntSizeTyped<Units>, IntMarginTyped<Units> >
+ Super;
+ typedef IntRectTyped<Units> Self;
+ typedef IntParam<int32_t> ToInt;
+
+ IntRectTyped() : Super() {
+ static_assert(sizeof(IntRectTyped) == sizeof(int32_t) * 4,
+ "Would be unfortunate otherwise!");
+ }
+ IntRectTyped(const IntPointTyped<Units>& aPos,
+ const IntSizeTyped<Units>& aSize)
+ : Super(aPos, aSize) {}
+
+ IntRectTyped(ToInt aX, ToInt aY, ToInt aWidth, ToInt aHeight)
+ : Super(aX.value, aY.value, aWidth.value, aHeight.value) {}
+
+ static IntRectTyped<Units> RoundIn(float aX, float aY, float aW, float aH) {
+ return IntRectTyped<Units>::RoundIn(
+ RectTyped<Units, float>(aX, aY, aW, aH));
+ }
+
+ static IntRectTyped<Units> RoundOut(float aX, float aY, float aW, float aH) {
+ return IntRectTyped<Units>::RoundOut(
+ RectTyped<Units, float>(aX, aY, aW, aH));
+ }
+
+ static IntRectTyped<Units> Round(float aX, float aY, float aW, float aH) {
+ return IntRectTyped<Units>::Round(RectTyped<Units, float>(aX, aY, aW, aH));
+ }
+
+ static IntRectTyped<Units> Truncate(float aX, float aY, float aW, float aH) {
+ return IntRectTyped<Units>(IntPointTyped<Units>::Truncate(aX, aY),
+ IntSizeTyped<Units>::Truncate(aW, aH));
+ }
+
+ static IntRectTyped<Units> RoundIn(const RectTyped<Units, float>& aRect) {
+ auto tmp(aRect);
+ tmp.RoundIn();
+ return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()),
+ int32_t(tmp.Width()), int32_t(tmp.Height()));
+ }
+
+ static IntRectTyped<Units> RoundOut(const RectTyped<Units, float>& aRect) {
+ auto tmp(aRect);
+ tmp.RoundOut();
+ return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()),
+ int32_t(tmp.Width()), int32_t(tmp.Height()));
+ }
+
+ static IntRectTyped<Units> Round(const RectTyped<Units, float>& aRect) {
+ auto tmp(aRect);
+ tmp.Round();
+ return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()),
+ int32_t(tmp.Width()), int32_t(tmp.Height()));
+ }
+
+ static IntRectTyped<Units> Truncate(const RectTyped<Units, float>& aRect) {
+ return IntRectTyped::Truncate(aRect.X(), aRect.Y(), aRect.Width(),
+ aRect.Height());
+ }
+
+ // Rounding isn't meaningful on an integer rectangle.
+ void Round() {}
+ void RoundIn() {}
+ void RoundOut() {}
+
+ // XXX When all of the code is ported, the following functions to convert
+ // to and from unknown types should be removed.
+
+ static IntRectTyped<Units> FromUnknownRect(
+ const IntRectTyped<UnknownUnits>& rect) {
+ return IntRectTyped<Units>(rect.X(), rect.Y(), rect.Width(), rect.Height());
+ }
+
+ IntRectTyped<UnknownUnits> ToUnknownRect() const {
+ return IntRectTyped<UnknownUnits>(this->X(), this->Y(), this->Width(),
+ this->Height());
+ }
+
+ bool Overflows() const {
+ CheckedInt<int32_t> xMost = this->X();
+ xMost += this->Width();
+ CheckedInt<int32_t> yMost = this->Y();
+ yMost += this->Height();
+ return !xMost.isValid() || !yMost.isValid();
+ }
+
+ // Same as Union(), but in the cases where aRect is non-empty, the union is
+ // done while guarding against overflow. If an overflow is detected, Nothing
+ // is returned.
+ [[nodiscard]] Maybe<Self> SafeUnion(const Self& aRect) const {
+ if (this->IsEmpty()) {
+ return aRect.Overflows() ? Nothing() : Some(aRect);
+ } else if (aRect.IsEmpty()) {
+ return Some(*static_cast<const Self*>(this));
+ } else {
+ return this->SafeUnionEdges(aRect);
+ }
+ }
+
+ // Same as UnionEdges, but guards against overflow. If an overflow is
+ // detected, Nothing is returned.
+ [[nodiscard]] Maybe<Self> SafeUnionEdges(const Self& aRect) const {
+ if (this->Overflows() || aRect.Overflows()) {
+ return Nothing();
+ }
+ // If neither |this| nor |aRect| overflow, then their XMost/YMost values
+ // should be safe to use.
+ CheckedInt<int32_t> newX = std::min(this->x, aRect.x);
+ CheckedInt<int32_t> newY = std::min(this->y, aRect.y);
+ CheckedInt<int32_t> newXMost = std::max(this->XMost(), aRect.XMost());
+ CheckedInt<int32_t> newYMost = std::max(this->YMost(), aRect.YMost());
+ CheckedInt<int32_t> newW = newXMost - newX;
+ CheckedInt<int32_t> newH = newYMost - newY;
+ if (!newW.isValid() || !newH.isValid()) {
+ return Nothing();
+ }
+ return Some(Self(newX.value(), newY.value(), newW.value(), newH.value()));
+ }
+
+ // This is here only to keep IPDL-generated code happy. DO NOT USE.
+ bool operator==(const IntRectTyped<Units>& aRect) const {
+ return IntRectTyped<Units>::IsEqualEdges(aRect);
+ }
+
+ void InflateToMultiple(const IntSizeTyped<Units>& aTileSize) {
+ if (this->IsEmpty()) {
+ return;
+ }
+
+ int32_t yMost = this->YMost();
+ int32_t xMost = this->XMost();
+
+ this->x = mozilla::RoundDownToMultiple(this->x, aTileSize.width);
+ this->y = mozilla::RoundDownToMultiple(this->y, aTileSize.height);
+ xMost = mozilla::RoundUpToMultiple(xMost, aTileSize.width);
+ yMost = mozilla::RoundUpToMultiple(yMost, aTileSize.height);
+
+ this->SetWidth(xMost - this->x);
+ this->SetHeight(yMost - this->y);
+ }
+};
+typedef IntRectTyped<UnknownUnits> IntRect;
+
+template <class Units, class F = Float>
+struct MOZ_EMPTY_BASES RectTyped
+ : public BaseRect<F, RectTyped<Units, F>, PointTyped<Units, F>,
+ SizeTyped<Units, F>, MarginTyped<Units, F> >,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'Units' must be a coordinate system tag");
+
+ typedef BaseRect<F, RectTyped<Units, F>, PointTyped<Units, F>,
+ SizeTyped<Units, F>, MarginTyped<Units, F> >
+ Super;
+
+ RectTyped() : Super() {
+ static_assert(sizeof(RectTyped) == sizeof(F) * 4,
+ "Would be unfortunate otherwise!");
+ }
+ RectTyped(const PointTyped<Units, F>& aPos, const SizeTyped<Units, F>& aSize)
+ : Super(aPos, aSize) {}
+ RectTyped(F _x, F _y, F _width, F _height) : Super(_x, _y, _width, _height) {}
+ explicit RectTyped(const IntRectTyped<Units>& rect)
+ : Super(F(rect.X()), F(rect.Y()), F(rect.Width()), F(rect.Height())) {}
+
+ void NudgeToIntegers() {
+ NudgeToInteger(&(this->x));
+ NudgeToInteger(&(this->y));
+ NudgeToInteger(&(this->width));
+ NudgeToInteger(&(this->height));
+ }
+
+ bool ToIntRect(IntRectTyped<Units>* aOut) const {
+ *aOut =
+ IntRectTyped<Units>(int32_t(this->X()), int32_t(this->Y()),
+ int32_t(this->Width()), int32_t(this->Height()));
+ return RectTyped<Units, F>(F(aOut->X()), F(aOut->Y()), F(aOut->Width()),
+ F(aOut->Height()))
+ .IsEqualEdges(*this);
+ }
+
+ // XXX When all of the code is ported, the following functions to convert to
+ // and from unknown types should be removed.
+
+ static RectTyped<Units, F> FromUnknownRect(
+ const RectTyped<UnknownUnits, F>& rect) {
+ return RectTyped<Units, F>(rect.X(), rect.Y(), rect.Width(), rect.Height());
+ }
+
+ RectTyped<UnknownUnits, F> ToUnknownRect() const {
+ return RectTyped<UnknownUnits, F>(this->X(), this->Y(), this->Width(),
+ this->Height());
+ }
+
+ // This is here only to keep IPDL-generated code happy. DO NOT USE.
+ bool operator==(const RectTyped<Units, F>& aRect) const {
+ return RectTyped<Units, F>::IsEqualEdges(aRect);
+ }
+
+ bool WithinEpsilonOf(const RectTyped& aOther, F aEpsilon) const {
+ return fabs(this->x - aOther.x) < aEpsilon &&
+ fabs(this->y - aOther.y) < aEpsilon &&
+ fabs(this->width - aOther.width) < aEpsilon &&
+ fabs(this->height - aOther.height) < aEpsilon;
+ }
+};
+typedef RectTyped<UnknownUnits> Rect;
+typedef RectTyped<UnknownUnits, double> RectDouble;
+
+template <class Units>
+IntRectTyped<Units> RoundedToInt(const RectTyped<Units>& aRect) {
+ RectTyped<Units> copy(aRect);
+ copy.Round();
+ return IntRectTyped<Units>(int32_t(copy.X()), int32_t(copy.Y()),
+ int32_t(copy.Width()), int32_t(copy.Height()));
+}
+
+template <class Units>
+bool RectIsInt32Safe(const RectTyped<Units>& aRect) {
+ float min = (float)std::numeric_limits<std::int32_t>::min();
+ float max = (float)std::numeric_limits<std::int32_t>::max();
+ return aRect.x > min && aRect.y > min && aRect.width < max &&
+ aRect.height < max && aRect.XMost() < max && aRect.YMost() < max;
+}
+
+template <class Units>
+IntRectTyped<Units> RoundedIn(const RectTyped<Units>& aRect) {
+ return IntRectTyped<Units>::RoundIn(aRect);
+}
+
+template <class Units>
+IntRectTyped<Units> RoundedOut(const RectTyped<Units>& aRect) {
+ return IntRectTyped<Units>::RoundOut(aRect);
+}
+
+template <class Units>
+IntRectTyped<Units> TruncatedToInt(const RectTyped<Units>& aRect) {
+ return IntRectTyped<Units>::Truncate(aRect);
+}
+
+template <class Units>
+RectTyped<Units> IntRectToRect(const IntRectTyped<Units>& aRect) {
+ return RectTyped<Units>(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+}
+
+// Convenience functions for intersecting and unioning two rectangles wrapped in
+// Maybes.
+template <typename Rect>
+Maybe<Rect> IntersectMaybeRects(const Maybe<Rect>& a, const Maybe<Rect>& b) {
+ if (!a) {
+ return b;
+ } else if (!b) {
+ return a;
+ } else {
+ return Some(a->Intersect(*b));
+ }
+}
+template <typename Rect>
+Maybe<Rect> UnionMaybeRects(const Maybe<Rect>& a, const Maybe<Rect>& b) {
+ if (!a) {
+ return b;
+ } else if (!b) {
+ return a;
+ } else {
+ return Some(a->Union(*b));
+ }
+}
+
+struct RectCornerRadii final {
+ Size radii[eCornerCount];
+
+ RectCornerRadii() = default;
+
+ explicit RectCornerRadii(Float radius) {
+ for (const auto i : mozilla::AllPhysicalCorners()) {
+ radii[i].SizeTo(radius, radius);
+ }
+ }
+
+ RectCornerRadii(Float radiusX, Float radiusY) {
+ for (const auto i : mozilla::AllPhysicalCorners()) {
+ radii[i].SizeTo(radiusX, radiusY);
+ }
+ }
+
+ RectCornerRadii(Float tl, Float tr, Float br, Float bl) {
+ radii[eCornerTopLeft].SizeTo(tl, tl);
+ radii[eCornerTopRight].SizeTo(tr, tr);
+ radii[eCornerBottomRight].SizeTo(br, br);
+ radii[eCornerBottomLeft].SizeTo(bl, bl);
+ }
+
+ RectCornerRadii(const Size& tl, const Size& tr, const Size& br,
+ const Size& bl) {
+ radii[eCornerTopLeft] = tl;
+ radii[eCornerTopRight] = tr;
+ radii[eCornerBottomRight] = br;
+ radii[eCornerBottomLeft] = 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 {
+ return TopLeft() == aOther.TopLeft() && TopRight() == aOther.TopRight() &&
+ BottomRight() == aOther.BottomRight() &&
+ BottomLeft() == aOther.BottomLeft();
+ }
+
+ bool AreRadiiSame() const {
+ return TopLeft() == TopRight() && TopLeft() == BottomRight() &&
+ TopLeft() == BottomLeft();
+ }
+
+ void Scale(Float aXScale, Float aYScale) {
+ for (const auto i : mozilla::AllPhysicalCorners()) {
+ radii[i].Scale(aXScale, aYScale);
+ }
+ }
+
+ const Size TopLeft() const { return radii[eCornerTopLeft]; }
+ Size& TopLeft() { return radii[eCornerTopLeft]; }
+
+ const Size TopRight() const { return radii[eCornerTopRight]; }
+ Size& TopRight() { return radii[eCornerTopRight]; }
+
+ const Size BottomRight() const { return radii[eCornerBottomRight]; }
+ Size& BottomRight() { return radii[eCornerBottomRight]; }
+
+ const Size BottomLeft() const { return radii[eCornerBottomLeft]; }
+ Size& BottomLeft() { return radii[eCornerBottomLeft]; }
+
+ bool IsEmpty() const {
+ return TopLeft().IsEmpty() && TopRight().IsEmpty() &&
+ BottomRight().IsEmpty() && BottomLeft().IsEmpty();
+ }
+};
+
+/* A rounded rectangle abstraction.
+ *
+ * This can represent a rectangle with a different pair of radii on each corner.
+ *
+ * Note: CoreGraphics and Direct2D only support rounded rectangle with the same
+ * radii on all corners. However, supporting CSS's border-radius requires the
+ * extra flexibility. */
+struct RoundedRect {
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+
+ RoundedRect(const Rect& aRect, const RectCornerRadii& aCorners)
+ : rect(aRect), corners(aCorners) {}
+
+ void Deflate(Float aTopWidth, Float aBottomWidth, Float aLeftWidth,
+ Float aRightWidth) {
+ // deflate the internal rect
+ rect.SetRect(rect.X() + aLeftWidth, rect.Y() + aTopWidth,
+ std::max(0.f, rect.Width() - aLeftWidth - aRightWidth),
+ std::max(0.f, rect.Height() - aTopWidth - aBottomWidth));
+
+ corners.radii[mozilla::eCornerTopLeft].width = std::max(
+ 0.f, corners.radii[mozilla::eCornerTopLeft].width - aLeftWidth);
+ corners.radii[mozilla::eCornerTopLeft].height = std::max(
+ 0.f, corners.radii[mozilla::eCornerTopLeft].height - aTopWidth);
+
+ corners.radii[mozilla::eCornerTopRight].width = std::max(
+ 0.f, corners.radii[mozilla::eCornerTopRight].width - aRightWidth);
+ corners.radii[mozilla::eCornerTopRight].height = std::max(
+ 0.f, corners.radii[mozilla::eCornerTopRight].height - aTopWidth);
+
+ corners.radii[mozilla::eCornerBottomLeft].width = std::max(
+ 0.f, corners.radii[mozilla::eCornerBottomLeft].width - aLeftWidth);
+ corners.radii[mozilla::eCornerBottomLeft].height = std::max(
+ 0.f, corners.radii[mozilla::eCornerBottomLeft].height - aBottomWidth);
+
+ corners.radii[mozilla::eCornerBottomRight].width = std::max(
+ 0.f, corners.radii[mozilla::eCornerBottomRight].width - aRightWidth);
+ corners.radii[mozilla::eCornerBottomRight].height = std::max(
+ 0.f, corners.radii[mozilla::eCornerBottomRight].height - aBottomWidth);
+ }
+ Rect rect;
+ RectCornerRadii corners;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_RECT_H_ */
diff --git a/gfx/2d/RectAbsolute.h b/gfx/2d/RectAbsolute.h
new file mode 100644
index 0000000000..09da87c80f
--- /dev/null
+++ b/gfx/2d/RectAbsolute.h
@@ -0,0 +1,305 @@
+/* -*- 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_RECT_ABSOLUTE_H_
+#define MOZILLA_GFX_RECT_ABSOLUTE_H_
+
+#include <algorithm>
+#include <cstdint>
+
+#include "mozilla/Attributes.h"
+#include "Point.h"
+#include "Rect.h"
+#include "Types.h"
+
+namespace mozilla {
+
+template <typename>
+struct IsPixel;
+
+namespace gfx {
+
+/**
+ * A RectAbsolute is similar to a Rect (see BaseRect.h), but represented as
+ * (x1, y1, x2, y2) instead of (x, y, width, height).
+ *
+ * Unless otherwise indicated, methods on this class correspond
+ * to methods on BaseRect.
+ *
+ * The API is currently very bare-bones; it may be extended as needed.
+ *
+ * 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 Rect>
+struct BaseRectAbsolute {
+ protected:
+ T left, top, right, bottom;
+
+ public:
+ BaseRectAbsolute() : left(0), top(0), right(0), bottom(0) {}
+ BaseRectAbsolute(T aLeft, T aTop, T aRight, T aBottom)
+ : left(aLeft), top(aTop), right(aRight), bottom(aBottom) {}
+
+ MOZ_ALWAYS_INLINE T X() const { return left; }
+ MOZ_ALWAYS_INLINE T Y() const { return top; }
+ MOZ_ALWAYS_INLINE T Width() const { return right - left; }
+ MOZ_ALWAYS_INLINE T Height() const { return bottom - top; }
+ MOZ_ALWAYS_INLINE T XMost() const { return right; }
+ MOZ_ALWAYS_INLINE T YMost() const { return bottom; }
+ MOZ_ALWAYS_INLINE const T& Left() const { return left; }
+ MOZ_ALWAYS_INLINE const T& Right() const { return right; }
+ MOZ_ALWAYS_INLINE const T& Top() const { return top; }
+ MOZ_ALWAYS_INLINE const T& Bottom() const { return bottom; }
+ MOZ_ALWAYS_INLINE T& Left() { return left; }
+ MOZ_ALWAYS_INLINE T& Right() { return right; }
+ MOZ_ALWAYS_INLINE T& Top() { return top; }
+ MOZ_ALWAYS_INLINE T& Bottom() { return bottom; }
+ T Area() const { return Width() * Height(); }
+
+ void Inflate(T aD) { Inflate(aD, aD); }
+ void Inflate(T aDx, T aDy) {
+ left -= aDx;
+ top -= aDy;
+ right += aDx;
+ bottom += aDy;
+ }
+
+ MOZ_ALWAYS_INLINE void SetBox(T aLeft, T aTop, T aRight, T aBottom) {
+ left = aLeft;
+ top = aTop;
+ right = aRight;
+ bottom = aBottom;
+ }
+ void SetLeftEdge(T aLeft) { left = aLeft; }
+ void SetRightEdge(T aRight) { right = aRight; }
+ void SetTopEdge(T aTop) { top = aTop; }
+ void SetBottomEdge(T aBottom) { bottom = aBottom; }
+
+ static Sub FromRect(const Rect& aRect) {
+ if (aRect.Overflows()) {
+ return Sub();
+ }
+ return Sub(aRect.x, aRect.y, aRect.XMost(), aRect.YMost());
+ }
+
+ [[nodiscard]] Sub Intersect(const Sub& aOther) const {
+ Sub result;
+ result.left = std::max<T>(left, aOther.left);
+ result.top = std::max<T>(top, aOther.top);
+ result.right = std::min<T>(right, aOther.right);
+ result.bottom = std::min<T>(bottom, aOther.bottom);
+ if (result.right < result.left || result.bottom < result.top) {
+ result.SizeTo(0, 0);
+ }
+ return result;
+ }
+
+ bool IsEmpty() const { return right <= left || bottom <= top; }
+
+ bool IsEqualEdges(const Sub& aOther) const {
+ return left == aOther.left && top == aOther.top && right == aOther.right &&
+ bottom == aOther.bottom;
+ }
+
+ bool IsEqualInterior(const Sub& aRect) const {
+ return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty());
+ }
+
+ MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) {
+ left += aDx;
+ right += aDx;
+ top += aDy;
+ bottom += aDy;
+ }
+ MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) {
+ left += aPoint.x;
+ right += aPoint.x;
+ top += aPoint.y;
+ bottom += aPoint.y;
+ }
+ MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) {
+ right = left + aWidth;
+ bottom = top + aHeight;
+ }
+
+ bool Contains(const Sub& aRect) const {
+ return aRect.IsEmpty() || (left <= aRect.left && aRect.right <= right &&
+ top <= aRect.top && aRect.bottom <= bottom);
+ }
+ bool Contains(T aX, T aY) const {
+ return (left <= aX && aX < right && top <= aY && aY < bottom);
+ }
+
+ bool Intersects(const Sub& aRect) const {
+ return !IsEmpty() && !aRect.IsEmpty() && left < aRect.right &&
+ aRect.left < right && top < aRect.bottom && aRect.top < bottom;
+ }
+
+ void SetEmpty() { left = right = top = bottom = 0; }
+
+ // Returns the smallest rectangle that contains both the area of both
+ // this and aRect. Thus, empty input rectangles are ignored.
+ // Note: if both rectangles are empty, returns aRect.
+ // WARNING! This is not safe against overflow, prefer using SafeUnion instead
+ // when dealing with int-based rects.
+ [[nodiscard]] 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.
+ [[nodiscard]] Sub UnionEdges(const Sub& aRect) const {
+ Sub result;
+ result.left = std::min(left, aRect.left);
+ result.top = std::min(top, aRect.top);
+ result.right = std::max(XMost(), aRect.XMost());
+ result.bottom = std::max(YMost(), aRect.YMost());
+ return result;
+ }
+
+ // 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) {
+ right = XMost() * aXScale;
+ bottom = YMost() * aYScale;
+ left = left * aXScale;
+ top = top * aYScale;
+ }
+ // 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) {
+ right = static_cast<T>(ceil(double(XMost()) * aXScale));
+ bottom = static_cast<T>(ceil(double(YMost()) * aYScale));
+ left = static_cast<T>(floor(double(left) * aXScale));
+ top = static_cast<T>(floor(double(top) * aYScale));
+ }
+ // 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) {
+ right = static_cast<T>(floor(double(XMost()) * aXScale));
+ bottom = static_cast<T>(floor(double(YMost()) * aYScale));
+ left = static_cast<T>(ceil(double(left) * aXScale));
+ top = static_cast<T>(ceil(double(top) * aYScale));
+ }
+ // 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) {
+ right = static_cast<T>(ceil(double(XMost()) / aXScale));
+ bottom = static_cast<T>(ceil(double(YMost()) / aYScale));
+ left = static_cast<T>(floor(double(left) / aXScale));
+ top = static_cast<T>(floor(double(top) / aYScale));
+ }
+ // 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) {
+ right = static_cast<T>(floor(double(XMost()) / aXScale));
+ bottom = static_cast<T>(floor(double(YMost()) / aYScale));
+ left = static_cast<T>(ceil(double(left) / aXScale));
+ top = static_cast<T>(ceil(double(top) / aYScale));
+ }
+
+ /**
+ * 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.
+ */
+ [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const {
+ T newLeft = std::max(aRect.left, left);
+ T newTop = std::max(aRect.top, top);
+ T width = std::min(aRect.Width(), Width());
+ T height = std::min(aRect.Height(), Height());
+ Sub rect(newLeft, newTop, newLeft + width, newTop + height);
+ newLeft = std::min(rect.right, aRect.right) - width;
+ newTop = std::min(rect.bottom, aRect.bottom) - height;
+ rect.MoveBy(newLeft - rect.left, newTop - rect.top);
+ return rect;
+ }
+
+ friend std::ostream& operator<<(
+ std::ostream& stream,
+ const BaseRectAbsolute<T, Sub, Point, Rect>& aRect) {
+ return stream << "(l=" << aRect.left << ", t=" << aRect.top
+ << ", r=" << aRect.right << ", b=" << aRect.bottom << ')';
+ }
+};
+
+template <class Units>
+struct IntRectAbsoluteTyped
+ : public BaseRectAbsolute<int32_t, IntRectAbsoluteTyped<Units>,
+ IntPointTyped<Units>, IntRectTyped<Units>>,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'units' must be a coordinate system tag");
+ typedef BaseRectAbsolute<int32_t, IntRectAbsoluteTyped<Units>,
+ IntPointTyped<Units>, IntRectTyped<Units>>
+ Super;
+ typedef IntParam<int32_t> ToInt;
+
+ IntRectAbsoluteTyped() : Super() {}
+ IntRectAbsoluteTyped(ToInt aLeft, ToInt aTop, ToInt aRight, ToInt aBottom)
+ : Super(aLeft.value, aTop.value, aRight.value, aBottom.value) {}
+};
+
+template <class Units>
+struct RectAbsoluteTyped
+ : public BaseRectAbsolute<Float, RectAbsoluteTyped<Units>,
+ PointTyped<Units>, RectTyped<Units>>,
+ public Units {
+ static_assert(IsPixel<Units>::value,
+ "'units' must be a coordinate system tag");
+ typedef BaseRectAbsolute<Float, RectAbsoluteTyped<Units>, PointTyped<Units>,
+ RectTyped<Units>>
+ Super;
+
+ RectAbsoluteTyped() : Super() {}
+ RectAbsoluteTyped(Float aLeft, Float aTop, Float aRight, Float aBottom)
+ : Super(aLeft, aTop, aRight, aBottom) {}
+};
+
+typedef IntRectAbsoluteTyped<UnknownUnits> IntRectAbsolute;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_RECT_ABSOLUTE_H_ */
diff --git a/gfx/2d/SFNTData.cpp b/gfx/2d/SFNTData.cpp
new file mode 100644
index 0000000000..42cf95c33c
--- /dev/null
+++ b/gfx/2d/SFNTData.cpp
@@ -0,0 +1,206 @@
+/* -*- 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 "SFNTData.h"
+
+#include <algorithm>
+#include <numeric>
+
+#include "BigEndianInts.h"
+#include "Logging.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Span.h"
+
+namespace mozilla {
+namespace gfx {
+
+#define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
+
+#pragma pack(push, 1)
+
+struct TTCHeader {
+ BigEndianUint32 ttcTag; // Always 'ttcf'
+ BigEndianUint32 version; // Fixed, 0x00010000
+ BigEndianUint32 numFonts;
+};
+
+struct OffsetTable {
+ BigEndianUint32 sfntVersion; // Fixed, 0x00010000 for version 1.0.
+ BigEndianUint16 numTables;
+ BigEndianUint16 searchRange; // (Maximum power of 2 <= numTables) x 16.
+ BigEndianUint16 entrySelector; // Log2(maximum power of 2 <= numTables).
+ BigEndianUint16 rangeShift; // NumTables x 16-searchRange.
+};
+
+struct TableDirEntry {
+ BigEndianUint32 tag; // 4 -byte identifier.
+ BigEndianUint32 checkSum; // CheckSum for this table.
+ BigEndianUint32 offset; // Offset from beginning of TrueType font file.
+ BigEndianUint32 length; // Length of this table.
+
+ friend bool operator<(const TableDirEntry& lhs, const uint32_t aTag) {
+ return lhs.tag < aTag;
+ }
+};
+
+#pragma pack(pop)
+
+class SFNTData::Font {
+ public:
+ Font(const OffsetTable* aOffsetTable, const uint8_t* aFontData,
+ uint32_t aDataLength)
+ : mFontData(aFontData),
+ mFirstDirEntry(
+ reinterpret_cast<const TableDirEntry*>(aOffsetTable + 1)),
+ mEndOfDirEntries(mFirstDirEntry + aOffsetTable->numTables),
+ mDataLength(aDataLength) {}
+
+ Span<const uint8_t> GetHeadTableBytes() const {
+ const TableDirEntry* dirEntry =
+ GetDirEntry(TRUETYPE_TAG('h', 'e', 'a', 'd'));
+ if (!dirEntry) {
+ gfxWarning() << "Head table entry not found.";
+ return {};
+ }
+
+ return {mFontData + dirEntry->offset, dirEntry->length};
+ }
+
+ Span<const uint8_t> GetCmapTableBytes() const {
+ const TableDirEntry* dirEntry =
+ GetDirEntry(TRUETYPE_TAG('c', 'm', 'a', 'p'));
+ if (!dirEntry) {
+ gfxWarning() << "Cmap table entry not found.";
+ return {};
+ }
+
+ return {mFontData + dirEntry->offset, dirEntry->length};
+ }
+
+ private:
+ const TableDirEntry* GetDirEntry(const uint32_t aTag) const {
+ const TableDirEntry* foundDirEntry =
+ std::lower_bound(mFirstDirEntry, mEndOfDirEntries, aTag);
+
+ if (foundDirEntry == mEndOfDirEntries || foundDirEntry->tag != aTag) {
+ gfxWarning() << "Font data does not contain tag.";
+ return nullptr;
+ }
+
+ if (mDataLength < (foundDirEntry->offset + foundDirEntry->length)) {
+ gfxWarning() << "Font data too short to contain table.";
+ return nullptr;
+ }
+
+ return foundDirEntry;
+ }
+
+ const uint8_t* mFontData;
+ const TableDirEntry* mFirstDirEntry;
+ const TableDirEntry* mEndOfDirEntries;
+ uint32_t mDataLength;
+};
+
+/* static */
+UniquePtr<SFNTData> SFNTData::Create(const uint8_t* aFontData,
+ uint32_t aDataLength) {
+ MOZ_ASSERT(aFontData);
+
+ // Check to see if this is a font collection.
+ if (aDataLength < sizeof(TTCHeader)) {
+ gfxWarning() << "Font data too short.";
+ return nullptr;
+ }
+
+ const TTCHeader* ttcHeader = reinterpret_cast<const TTCHeader*>(aFontData);
+ if (ttcHeader->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) {
+ uint32_t numFonts = ttcHeader->numFonts;
+ if (aDataLength <
+ sizeof(TTCHeader) + (numFonts * sizeof(BigEndianUint32))) {
+ gfxWarning() << "Font data too short to contain full TTC Header.";
+ return nullptr;
+ }
+
+ UniquePtr<SFNTData> sfntData(new SFNTData);
+ const BigEndianUint32* offset =
+ reinterpret_cast<const BigEndianUint32*>(aFontData + sizeof(TTCHeader));
+ const BigEndianUint32* endOfOffsets = offset + numFonts;
+ while (offset != endOfOffsets) {
+ if (!sfntData->AddFont(aFontData, aDataLength, *offset)) {
+ return nullptr;
+ }
+ ++offset;
+ }
+
+ return sfntData;
+ }
+
+ UniquePtr<SFNTData> sfntData(new SFNTData);
+ if (!sfntData->AddFont(aFontData, aDataLength, 0)) {
+ return nullptr;
+ }
+
+ return sfntData;
+}
+
+/* static */
+uint64_t SFNTData::GetUniqueKey(const uint8_t* aFontData, uint32_t aDataLength,
+ uint32_t aVarDataSize, const void* aVarData) {
+ uint64_t hash = 0;
+ UniquePtr<SFNTData> sfntData = SFNTData::Create(aFontData, aDataLength);
+ if (sfntData) {
+ hash = sfntData->HashHeadAndCmapTables();
+ } else {
+ gfxWarning() << "Failed to create SFNTData from data, hashing whole font.";
+ hash = HashBytes(aFontData, aDataLength);
+ }
+
+ if (aVarDataSize) {
+ hash = AddToHash(hash, HashBytes(aVarData, aVarDataSize));
+ }
+
+ return hash << 32 | aDataLength;
+}
+
+SFNTData::~SFNTData() {
+ for (size_t i = 0; i < mFonts.length(); ++i) {
+ delete mFonts[i];
+ }
+}
+
+bool SFNTData::AddFont(const uint8_t* aFontData, uint32_t aDataLength,
+ uint32_t aOffset) {
+ uint32_t remainingLength = aDataLength - aOffset;
+ if (remainingLength < sizeof(OffsetTable)) {
+ gfxWarning() << "Font data too short to contain OffsetTable " << aOffset;
+ return false;
+ }
+
+ const OffsetTable* offsetTable =
+ reinterpret_cast<const OffsetTable*>(aFontData + aOffset);
+ if (remainingLength <
+ sizeof(OffsetTable) + (offsetTable->numTables * sizeof(TableDirEntry))) {
+ gfxWarning() << "Font data too short to contain tables.";
+ return false;
+ }
+
+ return mFonts.append(new Font(offsetTable, aFontData, aDataLength));
+}
+
+uint32_t SFNTData::HashHeadAndCmapTables() {
+ uint32_t tablesHash = std::accumulate(
+ mFonts.begin(), mFonts.end(), 0U, [](uint32_t hash, Font* font) {
+ Span<const uint8_t> headBytes = font->GetHeadTableBytes();
+ hash = AddToHash(hash, HashBytes(headBytes.data(), headBytes.size()));
+ Span<const uint8_t> cmapBytes = font->GetCmapTableBytes();
+ return AddToHash(hash, HashBytes(cmapBytes.data(), cmapBytes.size()));
+ });
+
+ return tablesHash;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/SFNTData.h b/gfx/2d/SFNTData.h
new file mode 100644
index 0000000000..334438efc0
--- /dev/null
+++ b/gfx/2d/SFNTData.h
@@ -0,0 +1,59 @@
+/* -*- 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_SFNTData_h
+#define mozilla_gfx_SFNTData_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+namespace gfx {
+
+class SFNTData final {
+ public:
+ /**
+ * Creates an SFNTData if the header is a format that we understand and
+ * aDataLength is sufficient for the length information in the header data.
+ * Note that the data is NOT copied, so must exist the SFNTData's lifetime.
+ *
+ * @param aFontData the SFNT data.
+ * @param aDataLength length
+ * @return UniquePtr to a SFNTData or nullptr if the header is invalid.
+ */
+ static UniquePtr<SFNTData> Create(const uint8_t* aFontData,
+ uint32_t aDataLength);
+
+ /**
+ * Creates a unique key for the given font data and variation settings.
+ *
+ * @param aFontData the SFNT data
+ * @param aDataLength length
+ * @return unique key to be used for caching
+ */
+ static uint64_t GetUniqueKey(const uint8_t* aFontData, uint32_t aDataLength,
+ uint32_t aVarDataSize, const void* aVarData);
+
+ ~SFNTData();
+
+ private:
+ SFNTData() = default;
+
+ bool AddFont(const uint8_t* aFontData, uint32_t aDataLength,
+ uint32_t aOffset);
+
+ uint32_t HashHeadAndCmapTables();
+
+ // Internal representation of single font in font file.
+ class Font;
+
+ Vector<Font*> mFonts;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_SFNTData_h
diff --git a/gfx/2d/SIMD.h b/gfx/2d/SIMD.h
new file mode 100644
index 0000000000..80aca407b4
--- /dev/null
+++ b/gfx/2d/SIMD.h
@@ -0,0 +1,1039 @@
+/* -*- 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_SIMD_H_
+#define _MOZILLA_GFX_SIMD_H_
+
+/**
+ * Consumers of this file need to #define SIMD_COMPILE_SSE2 before including it
+ * if they want access to the SSE2 functions.
+ */
+
+#ifdef SIMD_COMPILE_SSE2
+# include <xmmintrin.h>
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+namespace simd {
+
+template <typename u8x16_t>
+u8x16_t Load8(const uint8_t* aSource);
+
+template <typename u8x16_t>
+u8x16_t From8(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f,
+ uint8_t g, uint8_t h, uint8_t i, uint8_t j, uint8_t k, uint8_t l,
+ uint8_t m, uint8_t n, uint8_t o, uint8_t p);
+
+template <typename u8x16_t>
+u8x16_t FromZero8();
+
+template <typename i16x8_t>
+i16x8_t FromI16(int16_t a, int16_t b, int16_t c, int16_t d, int16_t e,
+ int16_t f, int16_t g, int16_t h);
+
+template <typename u16x8_t>
+u16x8_t FromU16(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e,
+ uint16_t f, uint16_t g, uint16_t h);
+
+template <typename i16x8_t>
+i16x8_t FromI16(int16_t a);
+
+template <typename u16x8_t>
+u16x8_t FromU16(uint16_t a);
+
+template <typename i32x4_t>
+i32x4_t From32(int32_t a, int32_t b, int32_t c, int32_t d);
+
+template <typename i32x4_t>
+i32x4_t From32(int32_t a);
+
+template <typename f32x4_t>
+f32x4_t FromF32(float a, float b, float c, float d);
+
+template <typename f32x4_t>
+f32x4_t FromF32(float a);
+
+// All SIMD backends overload these functions for their SIMD types:
+
+#if 0
+
+// Store 16 bytes to a 16-byte aligned address
+void Store8(uint8_t* aTarget, u8x16_t aM);
+
+// Fixed shifts
+template<int32_t aNumberOfBits> i16x8_t ShiftRight16(i16x8_t aM);
+template<int32_t aNumberOfBits> i32x4_t ShiftRight32(i32x4_t aM);
+
+i16x8_t Add16(i16x8_t aM1, i16x8_t aM2);
+i32x4_t Add32(i32x4_t aM1, i32x4_t aM2);
+i16x8_t Sub16(i16x8_t aM1, i16x8_t aM2);
+i32x4_t Sub32(i32x4_t aM1, i32x4_t aM2);
+u8x16_t Min8(u8x16_t aM1, iu8x16_t aM2);
+u8x16_t Max8(u8x16_t aM1, iu8x16_t aM2);
+i32x4_t Min32(i32x4_t aM1, i32x4_t aM2);
+i32x4_t Max32(i32x4_t aM1, i32x4_t aM2);
+
+// Truncating i16 -> i16 multiplication
+i16x8_t Mul16(i16x8_t aM1, i16x8_t aM2);
+
+// Long multiplication i16 -> i32
+// aFactorsA1B1 = (a1[4] b1[4])
+// aFactorsA2B2 = (a2[4] b2[4])
+// aProductA = a1 * a2, aProductB = b1 * b2
+void Mul16x4x2x2To32x4x2(i16x8_t aFactorsA1B1, i16x8_t aFactorsA2B2,
+ i32x4_t& aProductA, i32x4_t& aProductB);
+
+// Long multiplication + pairwise addition i16 -> i32
+// See the scalar implementation for specifics.
+i32x4_t MulAdd16x8x2To32x4(i16x8_t aFactorsA, i16x8_t aFactorsB);
+i32x4_t MulAdd16x8x2To32x4(u16x8_t aFactorsA, u16x8_t aFactorsB);
+
+// Set all four 32-bit components to the value of the component at aIndex.
+template<int8_t aIndex>
+i32x4_t Splat32(i32x4_t aM);
+
+// Interpret the input as four 32-bit values, apply Splat32<aIndex> on them,
+// re-interpret the result as sixteen 8-bit values.
+template<int8_t aIndex>
+u8x16_t Splat32On8(u8x16_t aM);
+
+template<int8_t i0, int8_t i1, int8_t i2, int8_t i3> i32x4 Shuffle32(i32x4 aM);
+template<int8_t i0, int8_t i1, int8_t i2, int8_t i3> i16x8 ShuffleLo16(i16x8 aM);
+template<int8_t i0, int8_t i1, int8_t i2, int8_t i3> i16x8 ShuffleHi16(i16x8 aM);
+
+u8x16_t InterleaveLo8(u8x16_t m1, u8x16_t m2);
+u8x16_t InterleaveHi8(u8x16_t m1, u8x16_t m2);
+i16x8_t InterleaveLo16(i16x8_t m1, i16x8_t m2);
+i16x8_t InterleaveHi16(i16x8_t m1, i16x8_t m2);
+i32x4_t InterleaveLo32(i32x4_t m1, i32x4_t m2);
+
+i16x8_t UnpackLo8x8ToI16x8(u8x16_t m);
+i16x8_t UnpackHi8x8ToI16x8(u8x16_t m);
+u16x8_t UnpackLo8x8ToU16x8(u8x16_t m);
+u16x8_t UnpackHi8x8ToU16x8(u8x16_t m);
+
+i16x8_t PackAndSaturate32To16(i32x4_t m1, i32x4_t m2);
+u8x16_t PackAndSaturate16To8(i16x8_t m1, i16x8_t m2);
+u8x16_t PackAndSaturate32To8(i32x4_t m1, i32x4_t m2, i32x4_t m3, const i32x4_t& m4);
+
+i32x4 FastDivideBy255(i32x4 m);
+i16x8 FastDivideBy255_16(i16x8 m);
+
+#endif
+
+// Scalar
+
+struct Scalaru8x16_t {
+ uint8_t u8[16];
+};
+
+union Scalari16x8_t {
+ int16_t i16[8];
+ uint16_t u16[8];
+};
+
+typedef Scalari16x8_t Scalaru16x8_t;
+
+struct Scalari32x4_t {
+ int32_t i32[4];
+};
+
+struct Scalarf32x4_t {
+ float f32[4];
+};
+
+template <>
+inline Scalaru8x16_t Load8<Scalaru8x16_t>(const uint8_t* aSource) {
+ return *(Scalaru8x16_t*)aSource;
+}
+
+inline void Store8(uint8_t* aTarget, Scalaru8x16_t aM) {
+ *(Scalaru8x16_t*)aTarget = aM;
+}
+
+template <>
+inline Scalaru8x16_t From8<Scalaru8x16_t>(uint8_t a, uint8_t b, uint8_t c,
+ uint8_t d, uint8_t e, uint8_t f,
+ uint8_t g, uint8_t h, uint8_t i,
+ uint8_t j, uint8_t k, uint8_t l,
+ uint8_t m, uint8_t n, uint8_t o,
+ uint8_t p) {
+ Scalaru8x16_t _m;
+ _m.u8[0] = a;
+ _m.u8[1] = b;
+ _m.u8[2] = c;
+ _m.u8[3] = d;
+ _m.u8[4] = e;
+ _m.u8[5] = f;
+ _m.u8[6] = g;
+ _m.u8[7] = h;
+ _m.u8[8 + 0] = i;
+ _m.u8[8 + 1] = j;
+ _m.u8[8 + 2] = k;
+ _m.u8[8 + 3] = l;
+ _m.u8[8 + 4] = m;
+ _m.u8[8 + 5] = n;
+ _m.u8[8 + 6] = o;
+ _m.u8[8 + 7] = p;
+ return _m;
+}
+
+template <>
+inline Scalaru8x16_t FromZero8<Scalaru8x16_t>() {
+ return From8<Scalaru8x16_t>(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+}
+
+template <>
+inline Scalari16x8_t FromI16<Scalari16x8_t>(int16_t a, int16_t b, int16_t c,
+ int16_t d, int16_t e, int16_t f,
+ int16_t g, int16_t h) {
+ Scalari16x8_t m;
+ m.i16[0] = a;
+ m.i16[1] = b;
+ m.i16[2] = c;
+ m.i16[3] = d;
+ m.i16[4] = e;
+ m.i16[5] = f;
+ m.i16[6] = g;
+ m.i16[7] = h;
+ return m;
+}
+
+template <>
+inline Scalaru16x8_t FromU16<Scalaru16x8_t>(uint16_t a, uint16_t b, uint16_t c,
+ uint16_t d, uint16_t e, uint16_t f,
+ uint16_t g, uint16_t h) {
+ Scalaru16x8_t m;
+ m.u16[0] = a;
+ m.u16[1] = b;
+ m.u16[2] = c;
+ m.u16[3] = d;
+ m.u16[4] = e;
+ m.u16[5] = f;
+ m.u16[6] = g;
+ m.u16[7] = h;
+ return m;
+}
+
+template <>
+inline Scalari16x8_t FromI16<Scalari16x8_t>(int16_t a) {
+ return FromI16<Scalari16x8_t>(a, a, a, a, a, a, a, a);
+}
+
+template <>
+inline Scalaru16x8_t FromU16<Scalaru16x8_t>(uint16_t a) {
+ return FromU16<Scalaru16x8_t>(a, a, a, a, a, a, a, a);
+}
+
+template <>
+inline Scalari32x4_t From32<Scalari32x4_t>(int32_t a, int32_t b, int32_t c,
+ int32_t d) {
+ Scalari32x4_t m;
+ m.i32[0] = a;
+ m.i32[1] = b;
+ m.i32[2] = c;
+ m.i32[3] = d;
+ return m;
+}
+
+template <>
+inline Scalarf32x4_t FromF32<Scalarf32x4_t>(float a, float b, float c,
+ float d) {
+ Scalarf32x4_t m;
+ m.f32[0] = a;
+ m.f32[1] = b;
+ m.f32[2] = c;
+ m.f32[3] = d;
+ return m;
+}
+
+template <>
+inline Scalarf32x4_t FromF32<Scalarf32x4_t>(float a) {
+ return FromF32<Scalarf32x4_t>(a, a, a, a);
+}
+
+template <>
+inline Scalari32x4_t From32<Scalari32x4_t>(int32_t a) {
+ return From32<Scalari32x4_t>(a, a, a, a);
+}
+
+template <int32_t aNumberOfBits>
+inline Scalari16x8_t ShiftRight16(Scalari16x8_t aM) {
+ return FromI16<Scalari16x8_t>(uint16_t(aM.i16[0]) >> aNumberOfBits,
+ uint16_t(aM.i16[1]) >> aNumberOfBits,
+ uint16_t(aM.i16[2]) >> aNumberOfBits,
+ uint16_t(aM.i16[3]) >> aNumberOfBits,
+ uint16_t(aM.i16[4]) >> aNumberOfBits,
+ uint16_t(aM.i16[5]) >> aNumberOfBits,
+ uint16_t(aM.i16[6]) >> aNumberOfBits,
+ uint16_t(aM.i16[7]) >> aNumberOfBits);
+}
+
+template <int32_t aNumberOfBits>
+inline Scalari32x4_t ShiftRight32(Scalari32x4_t aM) {
+ return From32<Scalari32x4_t>(
+ aM.i32[0] >> aNumberOfBits, aM.i32[1] >> aNumberOfBits,
+ aM.i32[2] >> aNumberOfBits, aM.i32[3] >> aNumberOfBits);
+}
+
+inline Scalaru16x8_t Add16(Scalaru16x8_t aM1, Scalaru16x8_t aM2) {
+ return FromU16<Scalaru16x8_t>(
+ aM1.u16[0] + aM2.u16[0], aM1.u16[1] + aM2.u16[1], aM1.u16[2] + aM2.u16[2],
+ aM1.u16[3] + aM2.u16[3], aM1.u16[4] + aM2.u16[4], aM1.u16[5] + aM2.u16[5],
+ aM1.u16[6] + aM2.u16[6], aM1.u16[7] + aM2.u16[7]);
+}
+
+inline Scalari32x4_t Add32(Scalari32x4_t aM1, Scalari32x4_t aM2) {
+ return From32<Scalari32x4_t>(aM1.i32[0] + aM2.i32[0], aM1.i32[1] + aM2.i32[1],
+ aM1.i32[2] + aM2.i32[2],
+ aM1.i32[3] + aM2.i32[3]);
+}
+
+inline Scalaru16x8_t Sub16(Scalaru16x8_t aM1, Scalaru16x8_t aM2) {
+ return FromU16<Scalaru16x8_t>(
+ aM1.u16[0] - aM2.u16[0], aM1.u16[1] - aM2.u16[1], aM1.u16[2] - aM2.u16[2],
+ aM1.u16[3] - aM2.u16[3], aM1.u16[4] - aM2.u16[4], aM1.u16[5] - aM2.u16[5],
+ aM1.u16[6] - aM2.u16[6], aM1.u16[7] - aM2.u16[7]);
+}
+
+inline Scalari32x4_t Sub32(Scalari32x4_t aM1, Scalari32x4_t aM2) {
+ return From32<Scalari32x4_t>(aM1.i32[0] - aM2.i32[0], aM1.i32[1] - aM2.i32[1],
+ aM1.i32[2] - aM2.i32[2],
+ aM1.i32[3] - aM2.i32[3]);
+}
+
+inline int32_t umin(int32_t a, int32_t b) { return a - ((a - b) & -(a > b)); }
+
+inline int32_t umax(int32_t a, int32_t b) { return a - ((a - b) & -(a < b)); }
+
+inline Scalaru8x16_t Min8(Scalaru8x16_t aM1, Scalaru8x16_t aM2) {
+ return From8<Scalaru8x16_t>(
+ umin(aM1.u8[0], aM2.u8[0]), umin(aM1.u8[1], aM2.u8[1]),
+ umin(aM1.u8[2], aM2.u8[2]), umin(aM1.u8[3], aM2.u8[3]),
+ umin(aM1.u8[4], aM2.u8[4]), umin(aM1.u8[5], aM2.u8[5]),
+ umin(aM1.u8[6], aM2.u8[6]), umin(aM1.u8[7], aM2.u8[7]),
+ umin(aM1.u8[8 + 0], aM2.u8[8 + 0]), umin(aM1.u8[8 + 1], aM2.u8[8 + 1]),
+ umin(aM1.u8[8 + 2], aM2.u8[8 + 2]), umin(aM1.u8[8 + 3], aM2.u8[8 + 3]),
+ umin(aM1.u8[8 + 4], aM2.u8[8 + 4]), umin(aM1.u8[8 + 5], aM2.u8[8 + 5]),
+ umin(aM1.u8[8 + 6], aM2.u8[8 + 6]), umin(aM1.u8[8 + 7], aM2.u8[8 + 7]));
+}
+
+inline Scalaru8x16_t Max8(Scalaru8x16_t aM1, Scalaru8x16_t aM2) {
+ return From8<Scalaru8x16_t>(
+ umax(aM1.u8[0], aM2.u8[0]), umax(aM1.u8[1], aM2.u8[1]),
+ umax(aM1.u8[2], aM2.u8[2]), umax(aM1.u8[3], aM2.u8[3]),
+ umax(aM1.u8[4], aM2.u8[4]), umax(aM1.u8[5], aM2.u8[5]),
+ umax(aM1.u8[6], aM2.u8[6]), umax(aM1.u8[7], aM2.u8[7]),
+ umax(aM1.u8[8 + 0], aM2.u8[8 + 0]), umax(aM1.u8[8 + 1], aM2.u8[8 + 1]),
+ umax(aM1.u8[8 + 2], aM2.u8[8 + 2]), umax(aM1.u8[8 + 3], aM2.u8[8 + 3]),
+ umax(aM1.u8[8 + 4], aM2.u8[8 + 4]), umax(aM1.u8[8 + 5], aM2.u8[8 + 5]),
+ umax(aM1.u8[8 + 6], aM2.u8[8 + 6]), umax(aM1.u8[8 + 7], aM2.u8[8 + 7]));
+}
+
+inline Scalari32x4_t Min32(Scalari32x4_t aM1, Scalari32x4_t aM2) {
+ return From32<Scalari32x4_t>(
+ umin(aM1.i32[0], aM2.i32[0]), umin(aM1.i32[1], aM2.i32[1]),
+ umin(aM1.i32[2], aM2.i32[2]), umin(aM1.i32[3], aM2.i32[3]));
+}
+
+inline Scalari32x4_t Max32(Scalari32x4_t aM1, Scalari32x4_t aM2) {
+ return From32<Scalari32x4_t>(
+ umax(aM1.i32[0], aM2.i32[0]), umax(aM1.i32[1], aM2.i32[1]),
+ umax(aM1.i32[2], aM2.i32[2]), umax(aM1.i32[3], aM2.i32[3]));
+}
+
+inline Scalaru16x8_t Mul16(Scalaru16x8_t aM1, Scalaru16x8_t aM2) {
+ return FromU16<Scalaru16x8_t>(
+ uint16_t(int32_t(aM1.u16[0]) * int32_t(aM2.u16[0])),
+ uint16_t(int32_t(aM1.u16[1]) * int32_t(aM2.u16[1])),
+ uint16_t(int32_t(aM1.u16[2]) * int32_t(aM2.u16[2])),
+ uint16_t(int32_t(aM1.u16[3]) * int32_t(aM2.u16[3])),
+ uint16_t(int32_t(aM1.u16[4]) * int32_t(aM2.u16[4])),
+ uint16_t(int32_t(aM1.u16[5]) * int32_t(aM2.u16[5])),
+ uint16_t(int32_t(aM1.u16[6]) * int32_t(aM2.u16[6])),
+ uint16_t(int32_t(aM1.u16[7]) * int32_t(aM2.u16[7])));
+}
+
+inline void Mul16x4x2x2To32x4x2(Scalari16x8_t aFactorsA1B1,
+ Scalari16x8_t aFactorsA2B2,
+ Scalari32x4_t& aProductA,
+ Scalari32x4_t& aProductB) {
+ aProductA = From32<Scalari32x4_t>(aFactorsA1B1.i16[0] * aFactorsA2B2.i16[0],
+ aFactorsA1B1.i16[1] * aFactorsA2B2.i16[1],
+ aFactorsA1B1.i16[2] * aFactorsA2B2.i16[2],
+ aFactorsA1B1.i16[3] * aFactorsA2B2.i16[3]);
+ aProductB = From32<Scalari32x4_t>(aFactorsA1B1.i16[4] * aFactorsA2B2.i16[4],
+ aFactorsA1B1.i16[5] * aFactorsA2B2.i16[5],
+ aFactorsA1B1.i16[6] * aFactorsA2B2.i16[6],
+ aFactorsA1B1.i16[7] * aFactorsA2B2.i16[7]);
+}
+
+inline Scalari32x4_t MulAdd16x8x2To32x4(Scalari16x8_t aFactorsA,
+ Scalari16x8_t aFactorsB) {
+ return From32<Scalari32x4_t>(
+ aFactorsA.i16[0] * aFactorsB.i16[0] + aFactorsA.i16[1] * aFactorsB.i16[1],
+ aFactorsA.i16[2] * aFactorsB.i16[2] + aFactorsA.i16[3] * aFactorsB.i16[3],
+ aFactorsA.i16[4] * aFactorsB.i16[4] + aFactorsA.i16[5] * aFactorsB.i16[5],
+ aFactorsA.i16[6] * aFactorsB.i16[6] +
+ aFactorsA.i16[7] * aFactorsB.i16[7]);
+}
+
+template <int8_t aIndex>
+inline void AssertIndex() {
+ static_assert(aIndex == 0 || aIndex == 1 || aIndex == 2 || aIndex == 3,
+ "Invalid splat index");
+}
+
+template <int8_t aIndex>
+inline Scalari32x4_t Splat32(Scalari32x4_t aM) {
+ AssertIndex<aIndex>();
+ return From32<Scalari32x4_t>(aM.i32[aIndex], aM.i32[aIndex], aM.i32[aIndex],
+ aM.i32[aIndex]);
+}
+
+template <int8_t i>
+inline Scalaru8x16_t Splat32On8(Scalaru8x16_t aM) {
+ AssertIndex<i>();
+ return From8<Scalaru8x16_t>(
+ aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3],
+ aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3],
+ aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3],
+ aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3]);
+}
+
+template <int8_t i0, int8_t i1, int8_t i2, int8_t i3>
+inline Scalari32x4_t Shuffle32(Scalari32x4_t aM) {
+ AssertIndex<i0>();
+ AssertIndex<i1>();
+ AssertIndex<i2>();
+ AssertIndex<i3>();
+ Scalari32x4_t m = aM;
+ m.i32[0] = aM.i32[i3];
+ m.i32[1] = aM.i32[i2];
+ m.i32[2] = aM.i32[i1];
+ m.i32[3] = aM.i32[i0];
+ return m;
+}
+
+template <int8_t i0, int8_t i1, int8_t i2, int8_t i3>
+inline Scalari16x8_t ShuffleLo16(Scalari16x8_t aM) {
+ AssertIndex<i0>();
+ AssertIndex<i1>();
+ AssertIndex<i2>();
+ AssertIndex<i3>();
+ Scalari16x8_t m = aM;
+ m.i16[0] = aM.i16[i3];
+ m.i16[1] = aM.i16[i2];
+ m.i16[2] = aM.i16[i1];
+ m.i16[3] = aM.i16[i0];
+ return m;
+}
+
+template <int8_t i0, int8_t i1, int8_t i2, int8_t i3>
+inline Scalari16x8_t ShuffleHi16(Scalari16x8_t aM) {
+ AssertIndex<i0>();
+ AssertIndex<i1>();
+ AssertIndex<i2>();
+ AssertIndex<i3>();
+ Scalari16x8_t m = aM;
+ m.i16[4 + 0] = aM.i16[4 + i3];
+ m.i16[4 + 1] = aM.i16[4 + i2];
+ m.i16[4 + 2] = aM.i16[4 + i1];
+ m.i16[4 + 3] = aM.i16[4 + i0];
+ return m;
+}
+
+template <int8_t aIndexLo, int8_t aIndexHi>
+inline Scalaru16x8_t Splat16(Scalaru16x8_t aM) {
+ AssertIndex<aIndexLo>();
+ AssertIndex<aIndexHi>();
+ Scalaru16x8_t m;
+ int16_t chosenValueLo = aM.u16[aIndexLo];
+ m.u16[0] = chosenValueLo;
+ m.u16[1] = chosenValueLo;
+ m.u16[2] = chosenValueLo;
+ m.u16[3] = chosenValueLo;
+ int16_t chosenValueHi = aM.u16[4 + aIndexHi];
+ m.u16[4] = chosenValueHi;
+ m.u16[5] = chosenValueHi;
+ m.u16[6] = chosenValueHi;
+ m.u16[7] = chosenValueHi;
+ return m;
+}
+
+inline Scalaru8x16_t InterleaveLo8(Scalaru8x16_t m1, Scalaru8x16_t m2) {
+ return From8<Scalaru8x16_t>(m1.u8[0], m2.u8[0], m1.u8[1], m2.u8[1], m1.u8[2],
+ m2.u8[2], m1.u8[3], m2.u8[3], m1.u8[4], m2.u8[4],
+ m1.u8[5], m2.u8[5], m1.u8[6], m2.u8[6], m1.u8[7],
+ m2.u8[7]);
+}
+
+inline Scalaru8x16_t InterleaveHi8(Scalaru8x16_t m1, Scalaru8x16_t m2) {
+ return From8<Scalaru8x16_t>(
+ m1.u8[8 + 0], m2.u8[8 + 0], m1.u8[8 + 1], m2.u8[8 + 1], m1.u8[8 + 2],
+ m2.u8[8 + 2], m1.u8[8 + 3], m2.u8[8 + 3], m1.u8[8 + 4], m2.u8[8 + 4],
+ m1.u8[8 + 5], m2.u8[8 + 5], m1.u8[8 + 6], m2.u8[8 + 6], m1.u8[8 + 7],
+ m2.u8[8 + 7]);
+}
+
+inline Scalaru16x8_t InterleaveLo16(Scalaru16x8_t m1, Scalaru16x8_t m2) {
+ return FromU16<Scalaru16x8_t>(m1.u16[0], m2.u16[0], m1.u16[1], m2.u16[1],
+ m1.u16[2], m2.u16[2], m1.u16[3], m2.u16[3]);
+}
+
+inline Scalaru16x8_t InterleaveHi16(Scalaru16x8_t m1, Scalaru16x8_t m2) {
+ return FromU16<Scalaru16x8_t>(m1.u16[4], m2.u16[4], m1.u16[5], m2.u16[5],
+ m1.u16[6], m2.u16[6], m1.u16[7], m2.u16[7]);
+}
+
+inline Scalari32x4_t InterleaveLo32(Scalari32x4_t m1, Scalari32x4_t m2) {
+ return From32<Scalari32x4_t>(m1.i32[0], m2.i32[0], m1.i32[1], m2.i32[1]);
+}
+
+inline Scalari16x8_t UnpackLo8x8ToI16x8(Scalaru8x16_t aM) {
+ Scalari16x8_t m;
+ m.i16[0] = aM.u8[0];
+ m.i16[1] = aM.u8[1];
+ m.i16[2] = aM.u8[2];
+ m.i16[3] = aM.u8[3];
+ m.i16[4] = aM.u8[4];
+ m.i16[5] = aM.u8[5];
+ m.i16[6] = aM.u8[6];
+ m.i16[7] = aM.u8[7];
+ return m;
+}
+
+inline Scalari16x8_t UnpackHi8x8ToI16x8(Scalaru8x16_t aM) {
+ Scalari16x8_t m;
+ m.i16[0] = aM.u8[8 + 0];
+ m.i16[1] = aM.u8[8 + 1];
+ m.i16[2] = aM.u8[8 + 2];
+ m.i16[3] = aM.u8[8 + 3];
+ m.i16[4] = aM.u8[8 + 4];
+ m.i16[5] = aM.u8[8 + 5];
+ m.i16[6] = aM.u8[8 + 6];
+ m.i16[7] = aM.u8[8 + 7];
+ return m;
+}
+
+inline Scalaru16x8_t UnpackLo8x8ToU16x8(Scalaru8x16_t aM) {
+ return FromU16<Scalaru16x8_t>(uint16_t(aM.u8[0]), uint16_t(aM.u8[1]),
+ uint16_t(aM.u8[2]), uint16_t(aM.u8[3]),
+ uint16_t(aM.u8[4]), uint16_t(aM.u8[5]),
+ uint16_t(aM.u8[6]), uint16_t(aM.u8[7]));
+}
+
+inline Scalaru16x8_t UnpackHi8x8ToU16x8(Scalaru8x16_t aM) {
+ return FromU16<Scalaru16x8_t>(aM.u8[8 + 0], aM.u8[8 + 1], aM.u8[8 + 2],
+ aM.u8[8 + 3], aM.u8[8 + 4], aM.u8[8 + 5],
+ aM.u8[8 + 6], aM.u8[8 + 7]);
+}
+
+template <uint8_t aNumBytes>
+inline Scalaru8x16_t Rotate8(Scalaru8x16_t a1234, Scalaru8x16_t a5678) {
+ Scalaru8x16_t m;
+ for (uint8_t i = 0; i < 16; i++) {
+ uint8_t sourceByte = i + aNumBytes;
+ m.u8[i] =
+ sourceByte < 16 ? a1234.u8[sourceByte] : a5678.u8[sourceByte - 16];
+ }
+ return m;
+}
+
+template <typename T>
+inline int16_t SaturateTo16(T a) {
+ return int16_t(a >= INT16_MIN ? (a <= INT16_MAX ? a : INT16_MAX) : INT16_MIN);
+}
+
+inline Scalari16x8_t PackAndSaturate32To16(Scalari32x4_t m1, Scalari32x4_t m2) {
+ Scalari16x8_t m;
+ m.i16[0] = SaturateTo16(m1.i32[0]);
+ m.i16[1] = SaturateTo16(m1.i32[1]);
+ m.i16[2] = SaturateTo16(m1.i32[2]);
+ m.i16[3] = SaturateTo16(m1.i32[3]);
+ m.i16[4] = SaturateTo16(m2.i32[0]);
+ m.i16[5] = SaturateTo16(m2.i32[1]);
+ m.i16[6] = SaturateTo16(m2.i32[2]);
+ m.i16[7] = SaturateTo16(m2.i32[3]);
+ return m;
+}
+
+template <typename T>
+inline uint16_t SaturateToU16(T a) {
+ return uint16_t(umin(a & -(a >= 0), INT16_MAX));
+}
+
+inline Scalaru16x8_t PackAndSaturate32ToU16(Scalari32x4_t m1,
+ Scalari32x4_t m2) {
+ Scalaru16x8_t m;
+ m.u16[0] = SaturateToU16(m1.i32[0]);
+ m.u16[1] = SaturateToU16(m1.i32[1]);
+ m.u16[2] = SaturateToU16(m1.i32[2]);
+ m.u16[3] = SaturateToU16(m1.i32[3]);
+ m.u16[4] = SaturateToU16(m2.i32[0]);
+ m.u16[5] = SaturateToU16(m2.i32[1]);
+ m.u16[6] = SaturateToU16(m2.i32[2]);
+ m.u16[7] = SaturateToU16(m2.i32[3]);
+ return m;
+}
+
+template <typename T>
+inline uint8_t SaturateTo8(T a) {
+ return uint8_t(umin(a & -(a >= 0), 255));
+}
+
+inline Scalaru8x16_t PackAndSaturate32To8(Scalari32x4_t m1, Scalari32x4_t m2,
+ Scalari32x4_t m3,
+ const Scalari32x4_t& m4) {
+ Scalaru8x16_t m;
+ m.u8[0] = SaturateTo8(m1.i32[0]);
+ m.u8[1] = SaturateTo8(m1.i32[1]);
+ m.u8[2] = SaturateTo8(m1.i32[2]);
+ m.u8[3] = SaturateTo8(m1.i32[3]);
+ m.u8[4] = SaturateTo8(m2.i32[0]);
+ m.u8[5] = SaturateTo8(m2.i32[1]);
+ m.u8[6] = SaturateTo8(m2.i32[2]);
+ m.u8[7] = SaturateTo8(m2.i32[3]);
+ m.u8[8] = SaturateTo8(m3.i32[0]);
+ m.u8[9] = SaturateTo8(m3.i32[1]);
+ m.u8[10] = SaturateTo8(m3.i32[2]);
+ m.u8[11] = SaturateTo8(m3.i32[3]);
+ m.u8[12] = SaturateTo8(m4.i32[0]);
+ m.u8[13] = SaturateTo8(m4.i32[1]);
+ m.u8[14] = SaturateTo8(m4.i32[2]);
+ m.u8[15] = SaturateTo8(m4.i32[3]);
+ return m;
+}
+
+inline Scalaru8x16_t PackAndSaturate16To8(Scalari16x8_t m1, Scalari16x8_t m2) {
+ Scalaru8x16_t m;
+ m.u8[0] = SaturateTo8(m1.i16[0]);
+ m.u8[1] = SaturateTo8(m1.i16[1]);
+ m.u8[2] = SaturateTo8(m1.i16[2]);
+ m.u8[3] = SaturateTo8(m1.i16[3]);
+ m.u8[4] = SaturateTo8(m1.i16[4]);
+ m.u8[5] = SaturateTo8(m1.i16[5]);
+ m.u8[6] = SaturateTo8(m1.i16[6]);
+ m.u8[7] = SaturateTo8(m1.i16[7]);
+ m.u8[8] = SaturateTo8(m2.i16[0]);
+ m.u8[9] = SaturateTo8(m2.i16[1]);
+ m.u8[10] = SaturateTo8(m2.i16[2]);
+ m.u8[11] = SaturateTo8(m2.i16[3]);
+ m.u8[12] = SaturateTo8(m2.i16[4]);
+ m.u8[13] = SaturateTo8(m2.i16[5]);
+ m.u8[14] = SaturateTo8(m2.i16[6]);
+ m.u8[15] = SaturateTo8(m2.i16[7]);
+ return m;
+}
+
+// Fast approximate division by 255. It has the property that
+// for all 0 <= n <= 255*255, FAST_DIVIDE_BY_255(n) == n/255.
+// But it only uses two adds and two shifts instead of an
+// integer division (which is expensive on many processors).
+//
+// equivalent to v/255
+template <class B, class A>
+inline B FastDivideBy255(A v) {
+ return ((v << 8) + v + 255) >> 16;
+}
+
+inline Scalaru16x8_t FastDivideBy255_16(Scalaru16x8_t m) {
+ return FromU16<Scalaru16x8_t>(FastDivideBy255<uint16_t>(int32_t(m.u16[0])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[1])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[2])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[3])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[4])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[5])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[6])),
+ FastDivideBy255<uint16_t>(int32_t(m.u16[7])));
+}
+
+inline Scalari32x4_t FastDivideBy255(Scalari32x4_t m) {
+ return From32<Scalari32x4_t>(
+ FastDivideBy255<int32_t>(m.i32[0]), FastDivideBy255<int32_t>(m.i32[1]),
+ FastDivideBy255<int32_t>(m.i32[2]), FastDivideBy255<int32_t>(m.i32[3]));
+}
+
+inline Scalaru8x16_t Pick(Scalaru8x16_t mask, Scalaru8x16_t a,
+ Scalaru8x16_t b) {
+ return From8<Scalaru8x16_t>(
+ (a.u8[0] & (~mask.u8[0])) | (b.u8[0] & mask.u8[0]),
+ (a.u8[1] & (~mask.u8[1])) | (b.u8[1] & mask.u8[1]),
+ (a.u8[2] & (~mask.u8[2])) | (b.u8[2] & mask.u8[2]),
+ (a.u8[3] & (~mask.u8[3])) | (b.u8[3] & mask.u8[3]),
+ (a.u8[4] & (~mask.u8[4])) | (b.u8[4] & mask.u8[4]),
+ (a.u8[5] & (~mask.u8[5])) | (b.u8[5] & mask.u8[5]),
+ (a.u8[6] & (~mask.u8[6])) | (b.u8[6] & mask.u8[6]),
+ (a.u8[7] & (~mask.u8[7])) | (b.u8[7] & mask.u8[7]),
+ (a.u8[8 + 0] & (~mask.u8[8 + 0])) | (b.u8[8 + 0] & mask.u8[8 + 0]),
+ (a.u8[8 + 1] & (~mask.u8[8 + 1])) | (b.u8[8 + 1] & mask.u8[8 + 1]),
+ (a.u8[8 + 2] & (~mask.u8[8 + 2])) | (b.u8[8 + 2] & mask.u8[8 + 2]),
+ (a.u8[8 + 3] & (~mask.u8[8 + 3])) | (b.u8[8 + 3] & mask.u8[8 + 3]),
+ (a.u8[8 + 4] & (~mask.u8[8 + 4])) | (b.u8[8 + 4] & mask.u8[8 + 4]),
+ (a.u8[8 + 5] & (~mask.u8[8 + 5])) | (b.u8[8 + 5] & mask.u8[8 + 5]),
+ (a.u8[8 + 6] & (~mask.u8[8 + 6])) | (b.u8[8 + 6] & mask.u8[8 + 6]),
+ (a.u8[8 + 7] & (~mask.u8[8 + 7])) | (b.u8[8 + 7] & mask.u8[8 + 7]));
+}
+
+inline Scalari32x4_t Pick(Scalari32x4_t mask, Scalari32x4_t a,
+ Scalari32x4_t b) {
+ return From32<Scalari32x4_t>(
+ (a.i32[0] & (~mask.i32[0])) | (b.i32[0] & mask.i32[0]),
+ (a.i32[1] & (~mask.i32[1])) | (b.i32[1] & mask.i32[1]),
+ (a.i32[2] & (~mask.i32[2])) | (b.i32[2] & mask.i32[2]),
+ (a.i32[3] & (~mask.i32[3])) | (b.i32[3] & mask.i32[3]));
+}
+
+inline Scalarf32x4_t MixF32(Scalarf32x4_t a, Scalarf32x4_t b, float t) {
+ return FromF32<Scalarf32x4_t>(a.f32[0] + (b.f32[0] - a.f32[0]) * t,
+ a.f32[1] + (b.f32[1] - a.f32[1]) * t,
+ a.f32[2] + (b.f32[2] - a.f32[2]) * t,
+ a.f32[3] + (b.f32[3] - a.f32[3]) * t);
+}
+
+inline Scalarf32x4_t WSumF32(Scalarf32x4_t a, Scalarf32x4_t b, float wa,
+ float wb) {
+ return FromF32<Scalarf32x4_t>(
+ a.f32[0] * wa + b.f32[0] * wb, a.f32[1] * wa + b.f32[1] * wb,
+ a.f32[2] * wa + b.f32[2] * wb, a.f32[3] * wa + b.f32[3] * wb);
+}
+
+inline Scalarf32x4_t AbsF32(Scalarf32x4_t a) {
+ return FromF32<Scalarf32x4_t>(fabs(a.f32[0]), fabs(a.f32[1]), fabs(a.f32[2]),
+ fabs(a.f32[3]));
+}
+
+inline Scalarf32x4_t AddF32(Scalarf32x4_t a, Scalarf32x4_t b) {
+ return FromF32<Scalarf32x4_t>(a.f32[0] + b.f32[0], a.f32[1] + b.f32[1],
+ a.f32[2] + b.f32[2], a.f32[3] + b.f32[3]);
+}
+
+inline Scalarf32x4_t MulF32(Scalarf32x4_t a, Scalarf32x4_t b) {
+ return FromF32<Scalarf32x4_t>(a.f32[0] * b.f32[0], a.f32[1] * b.f32[1],
+ a.f32[2] * b.f32[2], a.f32[3] * b.f32[3]);
+}
+
+inline Scalarf32x4_t DivF32(Scalarf32x4_t a, Scalarf32x4_t b) {
+ return FromF32<Scalarf32x4_t>(a.f32[0] / b.f32[0], a.f32[1] / b.f32[1],
+ a.f32[2] / b.f32[2], a.f32[3] / b.f32[3]);
+}
+
+template <uint8_t aIndex>
+inline Scalarf32x4_t SplatF32(Scalarf32x4_t m) {
+ AssertIndex<aIndex>();
+ return FromF32<Scalarf32x4_t>(m.f32[aIndex], m.f32[aIndex], m.f32[aIndex],
+ m.f32[aIndex]);
+}
+
+inline Scalari32x4_t F32ToI32(Scalarf32x4_t m) {
+ return From32<Scalari32x4_t>(
+ int32_t(floor(m.f32[0] + 0.5f)), int32_t(floor(m.f32[1] + 0.5f)),
+ int32_t(floor(m.f32[2] + 0.5f)), int32_t(floor(m.f32[3] + 0.5f)));
+}
+
+#ifdef SIMD_COMPILE_SSE2
+
+// SSE2
+
+template <>
+inline __m128i Load8<__m128i>(const uint8_t* aSource) {
+ return _mm_load_si128((const __m128i*)aSource);
+}
+
+inline void Store8(uint8_t* aTarget, __m128i aM) {
+ _mm_store_si128((__m128i*)aTarget, aM);
+}
+
+template <>
+inline __m128i FromZero8<__m128i>() {
+ return _mm_setzero_si128();
+}
+
+template <>
+inline __m128i From8<__m128i>(uint8_t a, uint8_t b, uint8_t c, uint8_t d,
+ uint8_t e, uint8_t f, uint8_t g, uint8_t h,
+ uint8_t i, uint8_t j, uint8_t k, uint8_t l,
+ uint8_t m, uint8_t n, uint8_t o, uint8_t p) {
+ return _mm_setr_epi16((b << 8) + a, (d << 8) + c, (e << 8) + f, (h << 8) + g,
+ (j << 8) + i, (l << 8) + k, (m << 8) + n, (p << 8) + o);
+}
+
+template <>
+inline __m128i FromI16<__m128i>(int16_t a, int16_t b, int16_t c, int16_t d,
+ int16_t e, int16_t f, int16_t g, int16_t h) {
+ return _mm_setr_epi16(a, b, c, d, e, f, g, h);
+}
+
+template <>
+inline __m128i FromU16<__m128i>(uint16_t a, uint16_t b, uint16_t c, uint16_t d,
+ uint16_t e, uint16_t f, uint16_t g,
+ uint16_t h) {
+ return _mm_setr_epi16(a, b, c, d, e, f, g, h);
+}
+
+template <>
+inline __m128i FromI16<__m128i>(int16_t a) {
+ return _mm_set1_epi16(a);
+}
+
+template <>
+inline __m128i FromU16<__m128i>(uint16_t a) {
+ return _mm_set1_epi16((int16_t)a);
+}
+
+template <>
+inline __m128i From32<__m128i>(int32_t a, int32_t b, int32_t c, int32_t d) {
+ return _mm_setr_epi32(a, b, c, d);
+}
+
+template <>
+inline __m128i From32<__m128i>(int32_t a) {
+ return _mm_set1_epi32(a);
+}
+
+template <>
+inline __m128 FromF32<__m128>(float a, float b, float c, float d) {
+ return _mm_setr_ps(a, b, c, d);
+}
+
+template <>
+inline __m128 FromF32<__m128>(float a) {
+ return _mm_set1_ps(a);
+}
+
+template <int32_t aNumberOfBits>
+inline __m128i ShiftRight16(__m128i aM) {
+ return _mm_srli_epi16(aM, aNumberOfBits);
+}
+
+template <int32_t aNumberOfBits>
+inline __m128i ShiftRight32(__m128i aM) {
+ return _mm_srai_epi32(aM, aNumberOfBits);
+}
+
+inline __m128i Add16(__m128i aM1, __m128i aM2) {
+ return _mm_add_epi16(aM1, aM2);
+}
+
+inline __m128i Add32(__m128i aM1, __m128i aM2) {
+ return _mm_add_epi32(aM1, aM2);
+}
+
+inline __m128i Sub16(__m128i aM1, __m128i aM2) {
+ return _mm_sub_epi16(aM1, aM2);
+}
+
+inline __m128i Sub32(__m128i aM1, __m128i aM2) {
+ return _mm_sub_epi32(aM1, aM2);
+}
+
+inline __m128i Min8(__m128i aM1, __m128i aM2) { return _mm_min_epu8(aM1, aM2); }
+
+inline __m128i Max8(__m128i aM1, __m128i aM2) { return _mm_max_epu8(aM1, aM2); }
+
+inline __m128i Min32(__m128i aM1, __m128i aM2) {
+ __m128i m1_minus_m2 = _mm_sub_epi32(aM1, aM2);
+ __m128i m1_greater_than_m2 = _mm_cmpgt_epi32(aM1, aM2);
+ return _mm_sub_epi32(aM1, _mm_and_si128(m1_minus_m2, m1_greater_than_m2));
+}
+
+inline __m128i Max32(__m128i aM1, __m128i aM2) {
+ __m128i m1_minus_m2 = _mm_sub_epi32(aM1, aM2);
+ __m128i m2_greater_than_m1 = _mm_cmpgt_epi32(aM2, aM1);
+ return _mm_sub_epi32(aM1, _mm_and_si128(m1_minus_m2, m2_greater_than_m1));
+}
+
+inline __m128i Mul16(__m128i aM1, __m128i aM2) {
+ return _mm_mullo_epi16(aM1, aM2);
+}
+
+inline __m128i MulU16(__m128i aM1, __m128i aM2) {
+ return _mm_mullo_epi16(aM1, aM2);
+}
+
+inline void Mul16x4x2x2To32x4x2(__m128i aFactorsA1B1, __m128i aFactorsA2B2,
+ __m128i& aProductA, __m128i& aProductB) {
+ __m128i prodAB_lo = _mm_mullo_epi16(aFactorsA1B1, aFactorsA2B2);
+ __m128i prodAB_hi = _mm_mulhi_epi16(aFactorsA1B1, aFactorsA2B2);
+ aProductA = _mm_unpacklo_epi16(prodAB_lo, prodAB_hi);
+ aProductB = _mm_unpackhi_epi16(prodAB_lo, prodAB_hi);
+}
+
+inline __m128i MulAdd16x8x2To32x4(__m128i aFactorsA, __m128i aFactorsB) {
+ return _mm_madd_epi16(aFactorsA, aFactorsB);
+}
+
+template <int8_t i0, int8_t i1, int8_t i2, int8_t i3>
+inline __m128i Shuffle32(__m128i aM) {
+ AssertIndex<i0>();
+ AssertIndex<i1>();
+ AssertIndex<i2>();
+ AssertIndex<i3>();
+ return _mm_shuffle_epi32(aM, _MM_SHUFFLE(i0, i1, i2, i3));
+}
+
+template <int8_t i0, int8_t i1, int8_t i2, int8_t i3>
+inline __m128i ShuffleLo16(__m128i aM) {
+ AssertIndex<i0>();
+ AssertIndex<i1>();
+ AssertIndex<i2>();
+ AssertIndex<i3>();
+ return _mm_shufflelo_epi16(aM, _MM_SHUFFLE(i0, i1, i2, i3));
+}
+
+template <int8_t i0, int8_t i1, int8_t i2, int8_t i3>
+inline __m128i ShuffleHi16(__m128i aM) {
+ AssertIndex<i0>();
+ AssertIndex<i1>();
+ AssertIndex<i2>();
+ AssertIndex<i3>();
+ return _mm_shufflehi_epi16(aM, _MM_SHUFFLE(i0, i1, i2, i3));
+}
+
+template <int8_t aIndex>
+inline __m128i Splat32(__m128i aM) {
+ return Shuffle32<aIndex, aIndex, aIndex, aIndex>(aM);
+}
+
+template <int8_t aIndex>
+inline __m128i Splat32On8(__m128i aM) {
+ return Shuffle32<aIndex, aIndex, aIndex, aIndex>(aM);
+}
+
+template <int8_t aIndexLo, int8_t aIndexHi>
+inline __m128i Splat16(__m128i aM) {
+ AssertIndex<aIndexLo>();
+ AssertIndex<aIndexHi>();
+ return ShuffleHi16<aIndexHi, aIndexHi, aIndexHi, aIndexHi>(
+ ShuffleLo16<aIndexLo, aIndexLo, aIndexLo, aIndexLo>(aM));
+}
+
+inline __m128i UnpackLo8x8ToI16x8(__m128i m) {
+ __m128i zero = _mm_set1_epi8(0);
+ return _mm_unpacklo_epi8(m, zero);
+}
+
+inline __m128i UnpackHi8x8ToI16x8(__m128i m) {
+ __m128i zero = _mm_set1_epi8(0);
+ return _mm_unpackhi_epi8(m, zero);
+}
+
+inline __m128i UnpackLo8x8ToU16x8(__m128i m) {
+ __m128i zero = _mm_set1_epi8(0);
+ return _mm_unpacklo_epi8(m, zero);
+}
+
+inline __m128i UnpackHi8x8ToU16x8(__m128i m) {
+ __m128i zero = _mm_set1_epi8(0);
+ return _mm_unpackhi_epi8(m, zero);
+}
+
+inline __m128i InterleaveLo8(__m128i m1, __m128i m2) {
+ return _mm_unpacklo_epi8(m1, m2);
+}
+
+inline __m128i InterleaveHi8(__m128i m1, __m128i m2) {
+ return _mm_unpackhi_epi8(m1, m2);
+}
+
+inline __m128i InterleaveLo16(__m128i m1, __m128i m2) {
+ return _mm_unpacklo_epi16(m1, m2);
+}
+
+inline __m128i InterleaveHi16(__m128i m1, __m128i m2) {
+ return _mm_unpackhi_epi16(m1, m2);
+}
+
+inline __m128i InterleaveLo32(__m128i m1, __m128i m2) {
+ return _mm_unpacklo_epi32(m1, m2);
+}
+
+template <uint8_t aNumBytes>
+inline __m128i Rotate8(__m128i a1234, __m128i a5678) {
+ return _mm_or_si128(_mm_srli_si128(a1234, aNumBytes),
+ _mm_slli_si128(a5678, 16 - aNumBytes));
+}
+
+inline __m128i PackAndSaturate32To16(__m128i m1, __m128i m2) {
+ return _mm_packs_epi32(m1, m2);
+}
+
+inline __m128i PackAndSaturate32ToU16(__m128i m1, __m128i m2) {
+ return _mm_packs_epi32(m1, m2);
+}
+
+inline __m128i PackAndSaturate32To8(__m128i m1, __m128i m2, __m128i m3,
+ const __m128i& m4) {
+ // Pack into 8 16bit signed integers (saturating).
+ __m128i m12 = _mm_packs_epi32(m1, m2);
+ __m128i m34 = _mm_packs_epi32(m3, m4);
+
+ // Pack into 16 8bit unsigned integers (saturating).
+ return _mm_packus_epi16(m12, m34);
+}
+
+inline __m128i PackAndSaturate16To8(__m128i m1, __m128i m2) {
+ // Pack into 16 8bit unsigned integers (saturating).
+ return _mm_packus_epi16(m1, m2);
+}
+
+inline __m128i FastDivideBy255(__m128i m) {
+ // v = m << 8
+ __m128i v = _mm_slli_epi32(m, 8);
+ // v = v + (m + (255,255,255,255))
+ v = _mm_add_epi32(v, _mm_add_epi32(m, _mm_set1_epi32(255)));
+ // v = v >> 16
+ return _mm_srai_epi32(v, 16);
+}
+
+inline __m128i FastDivideBy255_16(__m128i m) {
+ __m128i zero = _mm_set1_epi16(0);
+ __m128i lo = _mm_unpacklo_epi16(m, zero);
+ __m128i hi = _mm_unpackhi_epi16(m, zero);
+ return _mm_packs_epi32(FastDivideBy255(lo), FastDivideBy255(hi));
+}
+
+inline __m128i Pick(__m128i mask, __m128i a, __m128i b) {
+ return _mm_or_si128(_mm_andnot_si128(mask, a), _mm_and_si128(mask, b));
+}
+
+inline __m128 MixF32(__m128 a, __m128 b, float t) {
+ return _mm_add_ps(a, _mm_mul_ps(_mm_sub_ps(b, a), _mm_set1_ps(t)));
+}
+
+inline __m128 WSumF32(__m128 a, __m128 b, float wa, float wb) {
+ return _mm_add_ps(_mm_mul_ps(a, _mm_set1_ps(wa)),
+ _mm_mul_ps(b, _mm_set1_ps(wb)));
+}
+
+inline __m128 AbsF32(__m128 a) {
+ return _mm_max_ps(_mm_sub_ps(_mm_setzero_ps(), a), a);
+}
+
+inline __m128 AddF32(__m128 a, __m128 b) { return _mm_add_ps(a, b); }
+
+inline __m128 MulF32(__m128 a, __m128 b) { return _mm_mul_ps(a, b); }
+
+inline __m128 DivF32(__m128 a, __m128 b) { return _mm_div_ps(a, b); }
+
+template <uint8_t aIndex>
+inline __m128 SplatF32(__m128 m) {
+ AssertIndex<aIndex>();
+ return _mm_shuffle_ps(m, m, _MM_SHUFFLE(aIndex, aIndex, aIndex, aIndex));
+}
+
+inline __m128i F32ToI32(__m128 m) { return _mm_cvtps_epi32(m); }
+
+#endif // SIMD_COMPILE_SSE2
+
+} // namespace simd
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_SIMD_H_
diff --git a/gfx/2d/SSEHelpers.h b/gfx/2d/SSEHelpers.h
new file mode 100644
index 0000000000..1b32024009
--- /dev/null
+++ b/gfx/2d/SSEHelpers.h
@@ -0,0 +1,18 @@
+/* -*- 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 <xmmintrin.h>
+#include <emmintrin.h>
+
+/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little
+ * faster. Once enough people are on architectures where _mm_loadu_si128 is
+ * fast we can migrate to it.
+ */
+MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i* aSource) {
+ // Yes! We use uninitialized memory here, we'll overwrite it though!
+ __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource);
+ return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1));
+}
diff --git a/gfx/2d/SVGTurbulenceRenderer-inl.h b/gfx/2d/SVGTurbulenceRenderer-inl.h
new file mode 100644
index 0000000000..27448befe1
--- /dev/null
+++ b/gfx/2d/SVGTurbulenceRenderer-inl.h
@@ -0,0 +1,362 @@
+/* -*- 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 "2D.h"
+#include "Filters.h"
+#include "SIMD.h"
+
+namespace mozilla {
+namespace gfx {
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+class SVGTurbulenceRenderer {
+ public:
+ SVGTurbulenceRenderer(const Size& aBaseFrequency, int32_t aSeed,
+ int aNumOctaves, const Rect& aTileRect);
+
+ already_AddRefed<DataSourceSurface> Render(const IntSize& aSize,
+ const Point& aOffset) const;
+
+ private:
+ /* The turbulence calculation code is an adapted version of what
+ appears in the SVG 1.1 specification:
+ http://www.w3.org/TR/SVG11/filters.html#feTurbulence
+ */
+
+ struct StitchInfo {
+ int32_t width; // How much to subtract to wrap for stitching.
+ int32_t height;
+ int32_t wrapX; // Minimum value to wrap.
+ int32_t wrapY;
+ };
+
+ const static int sBSize = 0x100;
+ const static int sBM = 0xff;
+ void InitFromSeed(int32_t aSeed);
+ void AdjustBaseFrequencyForStitch(const Rect& aTileRect);
+ IntPoint AdjustForStitch(IntPoint aLatticePoint,
+ const StitchInfo& aStitchInfo) const;
+ StitchInfo CreateStitchInfo(const Rect& aTileRect) const;
+ f32x4_t Noise2(Point aVec, const StitchInfo& aStitchInfo) const;
+ i32x4_t Turbulence(const Point& aPoint) const;
+ Point EquivalentNonNegativeOffset(const Point& aOffset) const;
+
+ Size mBaseFrequency;
+ int32_t mNumOctaves;
+ StitchInfo mStitchInfo;
+ bool mStitchable;
+ TurbulenceType mType;
+ uint8_t mLatticeSelector[sBSize];
+ f32x4_t mGradient[sBSize][2];
+};
+
+namespace {
+
+struct RandomNumberSource {
+ explicit RandomNumberSource(int32_t aSeed) : mLast(SetupSeed(aSeed)) {}
+ int32_t Next() {
+ mLast = Random(mLast);
+ return mLast;
+ }
+
+ private:
+ static const int32_t RAND_M = 2147483647; /* 2**31 - 1 */
+ static const int32_t RAND_A = 16807; /* 7**5; primitive root of m */
+ static const int32_t RAND_Q = 127773; /* m / a */
+ static const int32_t RAND_R = 2836; /* m % a */
+
+ /* Produces results in the range [1, 2**31 - 2].
+ Algorithm is: r = (a * r) mod m
+ where a = 16807 and m = 2**31 - 1 = 2147483647
+ See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
+ To test: the algorithm should produce the result 1043618065
+ as the 10,000th generated number if the original seed is 1.
+ */
+
+ static int32_t SetupSeed(int32_t aSeed) {
+ if (aSeed <= 0) aSeed = -(aSeed % (RAND_M - 1)) + 1;
+ if (aSeed > RAND_M - 1) aSeed = RAND_M - 1;
+ return aSeed;
+ }
+
+ static int32_t Random(int32_t aSeed) {
+ int32_t result = RAND_A * (aSeed % RAND_Q) - RAND_R * (aSeed / RAND_Q);
+ if (result <= 0) result += RAND_M;
+ return result;
+ }
+
+ int32_t mLast;
+};
+
+} // unnamed namespace
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::
+ SVGTurbulenceRenderer(const Size& aBaseFrequency, int32_t aSeed,
+ int aNumOctaves, const Rect& aTileRect)
+ : mBaseFrequency(aBaseFrequency),
+ mNumOctaves(aNumOctaves),
+ mStitchInfo(),
+ mStitchable(false),
+ mType(TURBULENCE_TYPE_TURBULENCE) {
+ InitFromSeed(aSeed);
+ if (Stitch) {
+ AdjustBaseFrequencyForStitch(aTileRect);
+ mStitchInfo = CreateStitchInfo(aTileRect);
+ }
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+void SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
+ u8x16_t>::InitFromSeed(int32_t aSeed) {
+ RandomNumberSource rand(aSeed);
+
+ float gradient[4][sBSize][2];
+ for (int32_t k = 0; k < 4; k++) {
+ for (int32_t i = 0; i < sBSize; i++) {
+ float a, b;
+ do {
+ a = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
+ b = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
+ } while (a == 0 && b == 0);
+ float s = sqrt(a * a + b * b);
+ gradient[k][i][0] = a / s;
+ gradient[k][i][1] = b / s;
+ }
+ }
+
+ for (int32_t i = 0; i < sBSize; i++) {
+ mLatticeSelector[i] = i;
+ }
+ for (int32_t i1 = sBSize - 1; i1 > 0; i1--) {
+ int32_t i2 = rand.Next() % sBSize;
+ std::swap(mLatticeSelector[i1], mLatticeSelector[i2]);
+ }
+
+ for (int32_t i = 0; i < sBSize; i++) {
+ // Contrary to the code in the spec, we build the first lattice selector
+ // lookup into mGradient so that we don't need to do it again for every
+ // pixel.
+ // We also change the order of the gradient indexing so that we can process
+ // all four color channels at the same time.
+ uint8_t j = mLatticeSelector[i];
+ mGradient[i][0] =
+ simd::FromF32<f32x4_t>(gradient[2][j][0], gradient[1][j][0],
+ gradient[0][j][0], gradient[3][j][0]);
+ mGradient[i][1] =
+ simd::FromF32<f32x4_t>(gradient[2][j][1], gradient[1][j][1],
+ gradient[0][j][1], gradient[3][j][1]);
+ }
+}
+
+// Adjust aFreq such that aLength * AdjustForLength(aFreq, aLength) is integer
+// and as close to aLength * aFreq as possible.
+static inline float AdjustForLength(float aFreq, float aLength) {
+ float lowFreq = floor(aLength * aFreq) / aLength;
+ float hiFreq = ceil(aLength * aFreq) / aLength;
+ if (aFreq / lowFreq < hiFreq / aFreq) {
+ return lowFreq;
+ }
+ return hiFreq;
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+void SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::
+ AdjustBaseFrequencyForStitch(const Rect& aTileRect) {
+ mBaseFrequency =
+ Size(AdjustForLength(mBaseFrequency.width, aTileRect.Width()),
+ AdjustForLength(mBaseFrequency.height, aTileRect.Height()));
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+typename SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
+ u8x16_t>::StitchInfo
+SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
+ u8x16_t>::CreateStitchInfo(const Rect& aTileRect) const {
+ StitchInfo stitch;
+ stitch.width =
+ int32_t(floorf(aTileRect.Width() * mBaseFrequency.width + 0.5f));
+ stitch.height =
+ int32_t(floorf(aTileRect.Height() * mBaseFrequency.height + 0.5f));
+ stitch.wrapX = int32_t(aTileRect.X() * mBaseFrequency.width) + stitch.width;
+ stitch.wrapY = int32_t(aTileRect.Y() * mBaseFrequency.height) + stitch.height;
+ return stitch;
+}
+
+static MOZ_ALWAYS_INLINE Float SCurve(Float t) { return t * t * (3 - 2 * t); }
+
+static MOZ_ALWAYS_INLINE Point SCurve(Point t) {
+ return Point(SCurve(t.x), SCurve(t.y));
+}
+
+template <typename f32x4_t>
+static MOZ_ALWAYS_INLINE f32x4_t BiMix(const f32x4_t& aa, const f32x4_t& ab,
+ const f32x4_t& ba, const f32x4_t& bb,
+ Point s) {
+ return simd::MixF32(simd::MixF32(aa, ab, s.x), simd::MixF32(ba, bb, s.x),
+ s.y);
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+IntPoint
+SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::AdjustForStitch(
+ IntPoint aLatticePoint, const StitchInfo& aStitchInfo) const {
+ if (Stitch) {
+ if (aLatticePoint.x >= aStitchInfo.wrapX) {
+ aLatticePoint.x -= aStitchInfo.width;
+ }
+ if (aLatticePoint.y >= aStitchInfo.wrapY) {
+ aLatticePoint.y -= aStitchInfo.height;
+ }
+ }
+ return aLatticePoint;
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+f32x4_t SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::Noise2(
+ Point aVec, const StitchInfo& aStitchInfo) const {
+ // aVec is guaranteed to be non-negative, so casting to int32_t always
+ // rounds towards negative infinity.
+ IntPoint topLeftLatticePoint(int32_t(aVec.x), int32_t(aVec.y));
+ Point r = aVec - topLeftLatticePoint; // fractional offset
+
+ IntPoint b0 = AdjustForStitch(topLeftLatticePoint, aStitchInfo);
+ IntPoint b1 = AdjustForStitch(b0 + IntPoint(1, 1), aStitchInfo);
+
+ uint8_t i = mLatticeSelector[b0.x & sBM];
+ uint8_t j = mLatticeSelector[b1.x & sBM];
+
+ const f32x4_t* qua = mGradient[(i + b0.y) & sBM];
+ const f32x4_t* qub = mGradient[(i + b1.y) & sBM];
+ const f32x4_t* qva = mGradient[(j + b0.y) & sBM];
+ const f32x4_t* qvb = mGradient[(j + b1.y) & sBM];
+
+ return BiMix(simd::WSumF32(qua[0], qua[1], r.x, r.y),
+ simd::WSumF32(qva[0], qva[1], r.x - 1.f, r.y),
+ simd::WSumF32(qub[0], qub[1], r.x, r.y - 1.f),
+ simd::WSumF32(qvb[0], qvb[1], r.x - 1.f, r.y - 1.f), SCurve(r));
+}
+
+template <typename f32x4_t, typename i32x4_t, typename u8x16_t>
+static inline i32x4_t ColorToBGRA(f32x4_t aUnscaledUnpreFloat) {
+ // Color is an unpremultiplied float vector where 1.0f means white. We will
+ // convert it into an integer vector where 255 means white.
+ f32x4_t alpha = simd::SplatF32<3>(aUnscaledUnpreFloat);
+ f32x4_t scaledUnpreFloat =
+ simd::MulF32(aUnscaledUnpreFloat, simd::FromF32<f32x4_t>(255));
+ i32x4_t scaledUnpreInt = simd::F32ToI32(scaledUnpreFloat);
+
+ // Multiply all channels with alpha.
+ i32x4_t scaledPreInt = simd::F32ToI32(simd::MulF32(scaledUnpreFloat, alpha));
+
+ // Use the premultiplied color channels and the unpremultiplied alpha channel.
+ i32x4_t alphaMask = simd::From32<i32x4_t>(0, 0, 0, -1);
+ return simd::Pick(alphaMask, scaledPreInt, scaledUnpreInt);
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+i32x4_t SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
+ u8x16_t>::Turbulence(const Point& aPoint) const {
+ StitchInfo stitchInfo = mStitchInfo;
+ f32x4_t sum = simd::FromF32<f32x4_t>(0);
+ Point vec(aPoint.x * mBaseFrequency.width, aPoint.y * mBaseFrequency.height);
+ f32x4_t ratio = simd::FromF32<f32x4_t>(1);
+
+ for (int octave = 0; octave < mNumOctaves; octave++) {
+ f32x4_t thisOctave = Noise2(vec, stitchInfo);
+ if (Type == TURBULENCE_TYPE_TURBULENCE) {
+ thisOctave = simd::AbsF32(thisOctave);
+ }
+ sum = simd::AddF32(sum, simd::DivF32(thisOctave, ratio));
+ vec = vec * 2;
+ ratio = simd::MulF32(ratio, simd::FromF32<f32x4_t>(2));
+
+ if (Stitch) {
+ stitchInfo.width *= 2;
+ stitchInfo.wrapX *= 2;
+ stitchInfo.height *= 2;
+ stitchInfo.wrapY *= 2;
+ }
+ }
+
+ if (Type == TURBULENCE_TYPE_FRACTAL_NOISE) {
+ sum = simd::DivF32(simd::AddF32(sum, simd::FromF32<f32x4_t>(1)),
+ simd::FromF32<f32x4_t>(2));
+ }
+ return ColorToBGRA<f32x4_t, i32x4_t, u8x16_t>(sum);
+}
+
+static inline Float MakeNonNegative(Float aValue, Float aIncrementSize) {
+ if (aIncrementSize == 0) {
+ return 0;
+ }
+ if (aValue >= 0) {
+ return aValue;
+ }
+ return aValue + ceilf(-aValue / aIncrementSize) * aIncrementSize;
+}
+
+static inline Float FiniteDivide(Float aValue, Float aDivisor) {
+ if (aDivisor == 0) {
+ return 0;
+ }
+ return aValue / aDivisor;
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+Point SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::
+ EquivalentNonNegativeOffset(const Point& aOffset) const {
+ Size basePeriod = Stitch ? Size(mStitchInfo.width, mStitchInfo.height)
+ : Size(sBSize, sBSize);
+ Size repeatingSize(FiniteDivide(basePeriod.width, mBaseFrequency.width),
+ FiniteDivide(basePeriod.height, mBaseFrequency.height));
+ return Point(MakeNonNegative(aOffset.x, repeatingSize.width),
+ MakeNonNegative(aOffset.y, repeatingSize.height));
+}
+
+template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
+ typename u8x16_t>
+already_AddRefed<DataSourceSurface>
+SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::Render(
+ const IntSize& aSize, const Point& aOffset) const {
+ RefPtr<DataSourceSurface> target =
+ Factory::CreateDataSourceSurface(aSize, SurfaceFormat::B8G8R8A8);
+ if (!target) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap map(target, DataSourceSurface::READ_WRITE);
+ uint8_t* targetData = map.GetData();
+ uint32_t stride = map.GetStride();
+
+ Point startOffset = EquivalentNonNegativeOffset(aOffset);
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x += 4) {
+ int32_t targIndex = y * stride + x * 4;
+ i32x4_t a = Turbulence(startOffset + Point(x, y));
+ i32x4_t b = Turbulence(startOffset + Point(x + 1, y));
+ i32x4_t c = Turbulence(startOffset + Point(x + 2, y));
+ i32x4_t d = Turbulence(startOffset + Point(x + 3, y));
+ u8x16_t result1234 = simd::PackAndSaturate32To8(a, b, c, d);
+ simd::Store8(&targetData[targIndex], result1234);
+ }
+ }
+
+ return target.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/Scale.h b/gfx/2d/Scale.h
new file mode 100644
index 0000000000..673bb88233
--- /dev/null
+++ b/gfx/2d/Scale.h
@@ -0,0 +1,39 @@
+/* -*- 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_SCALE_H_
+#define MOZILLA_GFX_SCALE_H_
+
+#include "Types.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Scale an image using a high-quality filter.
+ *
+ * Synchronously scales an image and writes the output to the destination in
+ * 32-bit format. The destination must be pre-allocated by the caller.
+ *
+ * Returns true if scaling was successful, and false otherwise. Currently, this
+ * function is implemented using Skia. If Skia is not enabled when building,
+ * calling this function will always return false.
+ *
+ * IMPLEMTATION NOTES:
+ * This API is not currently easily hardware acceleratable. A better API might
+ * take a SourceSurface and return a SourceSurface; the Direct2D backend, for
+ * example, could simply set a status bit on a copy of the image, and use
+ * Direct2D's high-quality scaler at draw time.
+ */
+GFX2D_API bool Scale(uint8_t* srcData, int32_t srcWidth, int32_t srcHeight,
+ int32_t srcStride, uint8_t* dstData, int32_t dstWidth,
+ int32_t dstHeight, int32_t dstStride,
+ SurfaceFormat format);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_BLUR_H_ */
diff --git a/gfx/2d/ScaleFactor.h b/gfx/2d/ScaleFactor.h
new file mode 100644
index 0000000000..4d7346d200
--- /dev/null
+++ b/gfx/2d/ScaleFactor.h
@@ -0,0 +1,98 @@
+/* -*- 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_SCALEFACTOR_H_
+#define MOZILLA_GFX_SCALEFACTOR_H_
+
+#include <ostream>
+
+#include "mozilla/Attributes.h"
+
+#include "gfxPoint.h"
+
+namespace mozilla {
+namespace gfx {
+
+/*
+ * This class represents a scaling factor between two different pixel unit
+ * systems. This is effectively a type-safe float, intended to be used in
+ * combination with the known-type instances of gfx::Point, gfx::Rect, etc.
+ *
+ * This class is meant to be used in cases where a single scale applies to
+ * both the x and y axes. For cases where two diferent scales apply, use
+ * ScaleFactors2D.
+ */
+template <class Src, class Dst>
+struct ScaleFactor {
+ float scale;
+
+ constexpr ScaleFactor() : scale(1.0) {}
+ constexpr ScaleFactor(const ScaleFactor<Src, Dst>& aCopy)
+ : scale(aCopy.scale) {}
+ explicit constexpr ScaleFactor(float aScale) : scale(aScale) {}
+
+ ScaleFactor<Dst, Src> Inverse() { return ScaleFactor<Dst, Src>(1 / scale); }
+
+ ScaleFactor<Src, Dst>& operator=(const ScaleFactor<Src, Dst>&) = default;
+
+ bool operator==(const ScaleFactor<Src, Dst>& aOther) const {
+ return scale == aOther.scale;
+ }
+
+ bool operator!=(const ScaleFactor<Src, Dst>& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool operator<(const ScaleFactor<Src, Dst>& aOther) const {
+ return scale < aOther.scale;
+ }
+
+ bool operator<=(const ScaleFactor<Src, Dst>& aOther) const {
+ return scale <= aOther.scale;
+ }
+
+ bool operator>(const ScaleFactor<Src, Dst>& aOther) const {
+ return scale > aOther.scale;
+ }
+
+ bool operator>=(const ScaleFactor<Src, Dst>& aOther) const {
+ return scale >= aOther.scale;
+ }
+
+ template <class Other>
+ ScaleFactor<Other, Dst> operator/(
+ const ScaleFactor<Src, Other>& aOther) const {
+ return ScaleFactor<Other, Dst>(scale / aOther.scale);
+ }
+
+ template <class Other>
+ ScaleFactor<Src, Other> operator/(
+ const ScaleFactor<Other, Dst>& aOther) const {
+ return ScaleFactor<Src, Other>(scale / aOther.scale);
+ }
+
+ template <class Other>
+ ScaleFactor<Src, Other> operator*(
+ const ScaleFactor<Dst, Other>& aOther) const {
+ return ScaleFactor<Src, Other>(scale * aOther.scale);
+ }
+
+ template <class Other>
+ ScaleFactor<Other, Dst> operator*(
+ const ScaleFactor<Other, Src>& aOther) const {
+ return ScaleFactor<Other, Dst>(scale * aOther.scale);
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const ScaleFactor<Src, Dst>& aSF) {
+ return aStream << aSF.scale;
+ }
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEFACTOR_H_ */
diff --git a/gfx/2d/ScaleFactors2D.h b/gfx/2d/ScaleFactors2D.h
new file mode 100644
index 0000000000..c557e3cec6
--- /dev/null
+++ b/gfx/2d/ScaleFactors2D.h
@@ -0,0 +1,198 @@
+/* -*- 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_SCALEFACTORS2D_H_
+#define MOZILLA_GFX_SCALEFACTORS2D_H_
+
+#include <ostream>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/gfx/ScaleFactor.h"
+#include "mozilla/gfx/Point.h"
+
+#include "gfxPoint.h"
+
+namespace mozilla {
+namespace gfx {
+
+/*
+ * This class is like ScaleFactor, but allows different scales on the x and
+ * y axes.
+ */
+template <class Src, class Dst, class T>
+struct BaseScaleFactors2D {
+ T xScale;
+ T yScale;
+
+ constexpr BaseScaleFactors2D() : xScale(1.0), yScale(1.0) {}
+ constexpr BaseScaleFactors2D(const BaseScaleFactors2D& aCopy)
+ : xScale(aCopy.xScale), yScale(aCopy.yScale) {}
+ constexpr BaseScaleFactors2D(T aXScale, T aYScale)
+ : xScale(aXScale), yScale(aYScale) {}
+ // Layout code often uses gfxSize to represent a pair of x/y scales.
+ explicit constexpr BaseScaleFactors2D(const gfxSize& aSize)
+ : xScale(aSize.width), yScale(aSize.height) {}
+
+ // "Upgrade" from a ScaleFactor.
+ // This is deliberately 'explicit' so that the treatment of a single scale
+ // number as both the x- and y-scale in a context where they are allowed to
+ // be different, is more visible.
+ explicit constexpr BaseScaleFactors2D(const ScaleFactor<Src, Dst>& aScale)
+ : xScale(aScale.scale), yScale(aScale.scale) {}
+
+ bool AreScalesSame() const {
+ return FuzzyEqualsMultiplicative(xScale, yScale);
+ }
+
+ // Convert the underlying floating point type storing the scale factors
+ // to that of NewT.
+ template <typename NewT>
+ BaseScaleFactors2D<Src, Dst, NewT> ConvertTo() const {
+ return BaseScaleFactors2D<Src, Dst, NewT>(NewT(xScale), NewT(yScale));
+ }
+
+ // Convert to a ScaleFactor. Asserts that the scales are, in fact, equal.
+ ScaleFactor<Src, Dst> ToScaleFactor() const {
+ // Avoid implicit narrowing from double to float. An explicit conversion
+ // may be done with `scales.ConvertTo<float>().ToScaleFactor()` if desired.
+ static_assert(std::is_same_v<T, float>);
+ MOZ_ASSERT(AreScalesSame());
+ return ScaleFactor<Src, Dst>(xScale);
+ }
+
+ // Convert to a SizeTyped. Eventually, we should replace all uses of SizeTyped
+ // to represent scales with ScaleFactors2D, and remove this function.
+ SizeTyped<UnknownUnits, T> ToSize() const {
+ return SizeTyped<UnknownUnits, T>(xScale, yScale);
+ }
+
+ BaseScaleFactors2D& operator=(const BaseScaleFactors2D&) = default;
+
+ bool operator==(const BaseScaleFactors2D& aOther) const {
+ return xScale == aOther.xScale && yScale == aOther.yScale;
+ }
+
+ bool operator!=(const BaseScaleFactors2D& aOther) const {
+ return !(*this == aOther);
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseScaleFactors2D& aScale) {
+ if (aScale.AreScalesSame()) {
+ return aStream << aScale.xScale;
+ } else {
+ return aStream << '(' << aScale.xScale << ',' << aScale.yScale << ')';
+ }
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Other, Dst, T> operator/(
+ const BaseScaleFactors2D<Src, Other, T>& aOther) const {
+ return BaseScaleFactors2D<Other, Dst, T>(xScale / aOther.xScale,
+ yScale / aOther.yScale);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Src, Other, T> operator/(
+ const BaseScaleFactors2D<Other, Dst, T>& aOther) const {
+ return BaseScaleFactors2D<Src, Other, T>(xScale / aOther.xScale,
+ yScale / aOther.yScale);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Src, Other, T> operator*(
+ const BaseScaleFactors2D<Dst, Other, T>& aOther) const {
+ return BaseScaleFactors2D<Src, Other, T>(xScale * aOther.xScale,
+ yScale * aOther.yScale);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Other, Dst, T> operator*(
+ const BaseScaleFactors2D<Other, Src, T>& aOther) const {
+ return BaseScaleFactors2D<Other, Dst, T>(xScale * aOther.xScale,
+ yScale * aOther.yScale);
+ }
+
+ BaseScaleFactors2D<Src, Src, T> operator*(
+ const BaseScaleFactors2D<Dst, Src, T>& aOther) const {
+ return BaseScaleFactors2D<Src, Src, T>(xScale * aOther.xScale,
+ yScale * aOther.yScale);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Src, Other, T> operator*(
+ const ScaleFactor<Dst, Other>& aOther) const {
+ return *this * BaseScaleFactors2D<Dst, Other, T>(aOther);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Other, Dst, T> operator*(
+ const ScaleFactor<Other, Src>& aOther) const {
+ return *this * BaseScaleFactors2D<Other, Src, T>(aOther);
+ }
+
+ BaseScaleFactors2D<Src, Src, T> operator*(
+ const ScaleFactor<Dst, Src>& aOther) const {
+ return *this * BaseScaleFactors2D<Dst, Src, T>(aOther);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Src, Other, T> operator/(
+ const ScaleFactor<Other, Dst>& aOther) const {
+ return *this / BaseScaleFactors2D<Other, Dst, T>(aOther);
+ }
+
+ template <class Other>
+ BaseScaleFactors2D<Other, Dst, T> operator/(
+ const ScaleFactor<Src, Other>& aOther) const {
+ return *this / BaseScaleFactors2D<Src, Other, T>(aOther);
+ }
+
+ template <class Other>
+ friend BaseScaleFactors2D<Other, Dst, T> operator*(
+ const ScaleFactor<Other, Src>& aA, const BaseScaleFactors2D& aB) {
+ return BaseScaleFactors2D<Other, Src, T>(aA) * aB;
+ }
+
+ template <class Other>
+ friend BaseScaleFactors2D<Other, Src, T> operator/(
+ const ScaleFactor<Other, Dst>& aA, const BaseScaleFactors2D& aB) {
+ return BaseScaleFactors2D<Other, Src, T>(aA) / aB;
+ }
+
+ static BaseScaleFactors2D<Src, Dst, T> FromUnknownScale(
+ const BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>& scale) {
+ return BaseScaleFactors2D<Src, Dst, T>(scale.xScale, scale.yScale);
+ }
+
+ BaseScaleFactors2D<UnknownUnits, UnknownUnits, T> ToUnknownScale() const {
+ return BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>(xScale, yScale);
+ }
+
+ friend BaseScaleFactors2D Min(const BaseScaleFactors2D& aA,
+ const BaseScaleFactors2D& aB) {
+ return BaseScaleFactors2D(std::min(aA.xScale, aB.xScale),
+ std::min(aA.yScale, aB.yScale));
+ }
+
+ friend BaseScaleFactors2D Max(const BaseScaleFactors2D& aA,
+ const BaseScaleFactors2D& aB) {
+ return BaseScaleFactors2D(std::max(aA.xScale, aB.xScale),
+ std::max(aA.yScale, aB.yScale));
+ }
+};
+
+template <class Src, class Dst>
+using ScaleFactors2D = BaseScaleFactors2D<Src, Dst, float>;
+
+template <class Src, class Dst>
+using ScaleFactors2DDouble = BaseScaleFactors2D<Src, Dst, double>;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEFACTORS2D_H_ */
diff --git a/gfx/2d/ScaledFontBase.cpp b/gfx/2d/ScaledFontBase.cpp
new file mode 100644
index 0000000000..d62dcb0127
--- /dev/null
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -0,0 +1,223 @@
+/* -*- 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 "ScaledFontBase.h"
+
+#include "PathSkia.h"
+#include "skia/include/core/SkFont.h"
+
+#ifdef USE_CAIRO
+# include "PathCairo.h"
+# include "DrawTargetCairo.h"
+# include "HelpersCairo.h"
+#endif
+
+#include <vector>
+#include <cmath>
+
+namespace mozilla {
+namespace gfx {
+
+Atomic<uint32_t> UnscaledFont::sDeletionCounter(0);
+
+UnscaledFont::~UnscaledFont() { sDeletionCounter++; }
+
+Atomic<uint32_t> ScaledFont::sDeletionCounter(0);
+
+ScaledFont::~ScaledFont() { sDeletionCounter++; }
+
+ScaledFontBase::~ScaledFontBase() {
+ SkSafeUnref<SkTypeface>(mTypeface);
+ cairo_scaled_font_destroy(mScaledFont);
+}
+
+ScaledFontBase::ScaledFontBase(const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize)
+ : ScaledFont(aUnscaledFont),
+ mTypeface(nullptr),
+ mScaledFont(nullptr),
+ mSize(aSize) {}
+
+SkTypeface* ScaledFontBase::GetSkTypeface() {
+ if (!mTypeface) {
+ SkTypeface* typeface = CreateSkTypeface();
+ if (!mTypeface.compareExchange(nullptr, typeface)) {
+ SkSafeUnref(typeface);
+ }
+ }
+ return mTypeface;
+}
+
+cairo_scaled_font_t* ScaledFontBase::GetCairoScaledFont() {
+ if (mScaledFont) {
+ return mScaledFont;
+ }
+
+ cairo_font_options_t* fontOptions = cairo_font_options_create();
+ cairo_font_face_t* fontFace = CreateCairoFontFace(fontOptions);
+ if (!fontFace) {
+ cairo_font_options_destroy(fontOptions);
+ return nullptr;
+ }
+
+ cairo_matrix_t sizeMatrix;
+ cairo_matrix_t identityMatrix;
+
+ cairo_matrix_init_scale(&sizeMatrix, mSize, mSize);
+ cairo_matrix_init_identity(&identityMatrix);
+
+ cairo_scaled_font_t* scaledFont = cairo_scaled_font_create(
+ fontFace, &sizeMatrix, &identityMatrix, fontOptions);
+
+ cairo_font_options_destroy(fontOptions);
+ cairo_font_face_destroy(fontFace);
+
+ if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) {
+ cairo_scaled_font_destroy(scaledFont);
+ return nullptr;
+ }
+
+ PrepareCairoScaledFont(scaledFont);
+ mScaledFont = scaledFont;
+ return mScaledFont;
+}
+
+SkPath ScaledFontBase::GetSkiaPathForGlyphs(const GlyphBuffer& aBuffer) {
+ SkTypeface* typeFace = GetSkTypeface();
+ MOZ_ASSERT(typeFace);
+
+ SkFont font(sk_ref_sp(typeFace), SkFloatToScalar(mSize));
+
+ std::vector<uint16_t> indices;
+ indices.resize(aBuffer.mNumGlyphs);
+ for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
+ indices[i] = aBuffer.mGlyphs[i].mIndex;
+ }
+
+ struct Context {
+ const Glyph* mGlyph;
+ SkPath mPath;
+ } ctx = {aBuffer.mGlyphs};
+
+ font.getPaths(
+ indices.data(), indices.size(),
+ [](const SkPath* glyphPath, const SkMatrix& scaleMatrix, void* ctxPtr) {
+ Context& ctx = *reinterpret_cast<Context*>(ctxPtr);
+ if (glyphPath) {
+ SkMatrix transMatrix(scaleMatrix);
+ transMatrix.postTranslate(SkFloatToScalar(ctx.mGlyph->mPosition.x),
+ SkFloatToScalar(ctx.mGlyph->mPosition.y));
+ ctx.mPath.addPath(*glyphPath, transMatrix);
+ }
+ ++ctx.mGlyph;
+ },
+ &ctx);
+
+ return ctx.mPath;
+}
+
+already_AddRefed<Path> ScaledFontBase::GetPathForGlyphs(
+ const GlyphBuffer& aBuffer, const DrawTarget* aTarget) {
+ if (aTarget->GetBackendType() == BackendType::SKIA) {
+ SkPath path = GetSkiaPathForGlyphs(aBuffer);
+ return MakeAndAddRef<PathSkia>(path, FillRule::FILL_WINDING);
+ }
+#ifdef USE_CAIRO
+ if (aTarget->GetBackendType() == BackendType::CAIRO) {
+ MOZ_ASSERT(mScaledFont);
+
+ DrawTarget* dt = const_cast<DrawTarget*>(aTarget);
+ cairo_t* ctx = static_cast<cairo_t*>(
+ dt->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+
+ bool isNewContext = !ctx;
+ if (!ctx) {
+ ctx = cairo_create(DrawTargetCairo::GetDummySurface());
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(aTarget->GetTransform(), mat);
+ cairo_set_matrix(ctx, &mat);
+ }
+
+ cairo_set_scaled_font(ctx, mScaledFont);
+
+ // Convert our GlyphBuffer into an array of Cairo glyphs.
+ std::vector<cairo_glyph_t> glyphs(aBuffer.mNumGlyphs);
+ for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
+ glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
+ glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
+ glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
+ }
+
+ cairo_new_path(ctx);
+
+ cairo_glyph_path(ctx, &glyphs[0], aBuffer.mNumGlyphs);
+
+ RefPtr<PathCairo> newPath = new PathCairo(ctx);
+ if (isNewContext) {
+ cairo_destroy(ctx);
+ }
+
+ return newPath.forget();
+ }
+#endif
+ RefPtr<PathBuilder> builder = aTarget->CreatePathBuilder();
+ SkPath skPath = GetSkiaPathForGlyphs(aBuffer);
+ RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING);
+ path->StreamToSink(builder);
+ return builder->Finish();
+}
+
+void ScaledFontBase::CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
+ PathBuilder* aBuilder,
+ const Matrix* aTransformHint) {
+ BackendType backendType = aBuilder->GetBackendType();
+ if (backendType == BackendType::SKIA) {
+ PathBuilderSkia* builder = static_cast<PathBuilderSkia*>(aBuilder);
+ builder->AppendPath(GetSkiaPathForGlyphs(aBuffer));
+ return;
+ }
+#ifdef USE_CAIRO
+ if (backendType == BackendType::CAIRO) {
+ MOZ_ASSERT(mScaledFont);
+
+ PathBuilderCairo* builder = static_cast<PathBuilderCairo*>(aBuilder);
+ cairo_t* ctx = cairo_create(DrawTargetCairo::GetDummySurface());
+
+ if (aTransformHint) {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(*aTransformHint, mat);
+ cairo_set_matrix(ctx, &mat);
+ }
+
+ // Convert our GlyphBuffer into an array of Cairo glyphs.
+ std::vector<cairo_glyph_t> glyphs(aBuffer.mNumGlyphs);
+ for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
+ glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
+ glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
+ glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
+ }
+
+ cairo_set_scaled_font(ctx, mScaledFont);
+ cairo_glyph_path(ctx, &glyphs[0], aBuffer.mNumGlyphs);
+
+ RefPtr<PathCairo> cairoPath = new PathCairo(ctx);
+ cairo_destroy(ctx);
+
+ cairoPath->AppendPathToBuilder(builder);
+ return;
+ }
+#endif
+ if (backendType == BackendType::RECORDING) {
+ SkPath skPath = GetSkiaPathForGlyphs(aBuffer);
+ RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING);
+ path->StreamToSink(aBuilder);
+ return;
+ }
+ MOZ_ASSERT(false, "Path not being copied");
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ScaledFontBase.h b/gfx/2d/ScaledFontBase.h
new file mode 100644
index 0000000000..0eb875955e
--- /dev/null
+++ b/gfx/2d/ScaledFontBase.h
@@ -0,0 +1,59 @@
+/* -*- 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_SCALEDFONTBASE_H_
+#define MOZILLA_GFX_SCALEDFONTBASE_H_
+
+#include "2D.h"
+
+#include "skia/include/core/SkFont.h"
+#include "skia/include/core/SkPath.h"
+#include "skia/include/core/SkTypeface.h"
+// Skia uses cairo_scaled_font_t as the internal font type in ScaledFont
+#include "cairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+class ScaledFontBase : public ScaledFont {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontBase, override)
+
+ ScaledFontBase(const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
+ virtual ~ScaledFontBase();
+
+ virtual already_AddRefed<Path> GetPathForGlyphs(
+ const GlyphBuffer& aBuffer, const DrawTarget* aTarget) override;
+
+ virtual void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
+ PathBuilder* aBuilder,
+ const Matrix* aTransformHint) override;
+
+ virtual Float GetSize() const override { return mSize; }
+
+ SkTypeface* GetSkTypeface();
+ virtual void SetupSkFontDrawOptions(SkFont& aFont) {}
+
+ virtual cairo_scaled_font_t* GetCairoScaledFont() override;
+
+ protected:
+ friend class DrawTargetSkia;
+ Atomic<SkTypeface*> mTypeface;
+ virtual SkTypeface* CreateSkTypeface() { return nullptr; }
+ SkPath GetSkiaPathForGlyphs(const GlyphBuffer& aBuffer);
+ virtual cairo_font_face_t* CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ return nullptr;
+ }
+ virtual void PrepareCairoScaledFont(cairo_scaled_font_t* aFont) {}
+ cairo_scaled_font_t* mScaledFont;
+ Float mSize;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTBASE_H_ */
diff --git a/gfx/2d/ScaledFontDWrite.cpp b/gfx/2d/ScaledFontDWrite.cpp
new file mode 100644
index 0000000000..1392f5dc43
--- /dev/null
+++ b/gfx/2d/ScaledFontDWrite.cpp
@@ -0,0 +1,769 @@
+/* -*- 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 "ScaledFontDWrite.h"
+#include "gfxDWriteCommon.h"
+#include "UnscaledFontDWrite.h"
+#include "PathD2D.h"
+#include "gfxFont.h"
+#include "Logging.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "HelpersD2D.h"
+#include "StackArray.h"
+
+#include "dwrite_3.h"
+
+// Currently, we build with WINVER=0x601 (Win7), which means newer
+// declarations in dwrite_3.h will not be visible. Also, we don't
+// yet have the Fall Creators Update SDK available on build machines,
+// so even with updated WINVER, some of the interfaces we need would
+// not be present.
+// To work around this, until the build environment is updated,
+// we #include an extra header that contains copies of the relevant
+// classes/interfaces we need.
+#if !defined(__MINGW32__) && WINVER < 0x0A00
+# include "dw-extra.h"
+#endif
+
+#include "PathSkia.h"
+#include "skia/include/core/SkPaint.h"
+#include "skia/include/core/SkPath.h"
+#include "skia/include/ports/SkTypeface_win.h"
+
+#include <vector>
+
+#include "cairo-win32.h"
+
+#include "HelpersWinFonts.h"
+
+namespace mozilla {
+namespace gfx {
+
+#define GASP_TAG 0x70736167
+#define GASP_DOGRAY 0x2
+
+static inline unsigned short readShort(const char* aBuf) {
+ return (*aBuf << 8) | *(aBuf + 1);
+}
+
+static bool DoGrayscale(IDWriteFontFace* aDWFace, Float ppem) {
+ void* tableContext;
+ char* tableData;
+ UINT32 tableSize;
+ BOOL exists;
+ aDWFace->TryGetFontTable(GASP_TAG, (const void**)&tableData, &tableSize,
+ &tableContext, &exists);
+
+ if (exists) {
+ if (tableSize < 4) {
+ aDWFace->ReleaseFontTable(tableContext);
+ return true;
+ }
+ struct gaspRange {
+ unsigned short maxPPEM; // Stored big-endian
+ unsigned short behavior; // Stored big-endian
+ };
+ unsigned short numRanges = readShort(tableData + 2);
+ if (tableSize < (UINT)4 + numRanges * 4) {
+ aDWFace->ReleaseFontTable(tableContext);
+ return true;
+ }
+ gaspRange* ranges = (gaspRange*)(tableData + 4);
+ for (int i = 0; i < numRanges; i++) {
+ if (readShort((char*)&ranges[i].maxPPEM) >= ppem) {
+ if (!(readShort((char*)&ranges[i].behavior) & GASP_DOGRAY)) {
+ aDWFace->ReleaseFontTable(tableContext);
+ return false;
+ }
+ break;
+ }
+ }
+ aDWFace->ReleaseFontTable(tableContext);
+ }
+ return true;
+}
+
+ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace* aFontFace,
+ const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize, bool aUseEmbeddedBitmap,
+ bool aUseMultistrikeBold, bool aGDIForced,
+ const gfxFontStyle* aStyle)
+ : ScaledFontBase(aUnscaledFont, aSize),
+ mFontFace(aFontFace),
+ mUseEmbeddedBitmap(aUseEmbeddedBitmap),
+ mUseMultistrikeBold(aUseMultistrikeBold),
+ mGDIForced(aGDIForced) {
+ if (aStyle) {
+ mStyle = SkFontStyle(aStyle->weight.ToIntRounded(),
+ DWriteFontStretchFromStretch(aStyle->stretch),
+ // FIXME(jwatt): also use kOblique_Slant
+ aStyle->style == FontSlantStyle::NORMAL
+ ? SkFontStyle::kUpright_Slant
+ : SkFontStyle::kItalic_Slant);
+ }
+}
+
+already_AddRefed<Path> ScaledFontDWrite::GetPathForGlyphs(
+ const GlyphBuffer& aBuffer, const DrawTarget* aTarget) {
+ RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();
+
+ if (pathBuilder->GetBackendType() != BackendType::DIRECT2D &&
+ pathBuilder->GetBackendType() != BackendType::DIRECT2D1_1) {
+ return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
+ }
+
+ PathBuilderD2D* pathBuilderD2D =
+ static_cast<PathBuilderD2D*>(pathBuilder.get());
+
+ CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink());
+
+ return pathBuilder->Finish();
+}
+
+SkTypeface* ScaledFontDWrite::CreateSkTypeface() {
+ RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
+ if (!factory) {
+ return nullptr;
+ }
+
+ auto& settings = DWriteSettings();
+ Float gamma = settings.Gamma();
+ // Skia doesn't support a gamma value outside of 0-4, so default to 2.2
+ if (gamma < 0.0f || gamma > 4.0f) {
+ gamma = 2.2f;
+ }
+
+ Float contrast = settings.EnhancedContrast();
+ // Skia doesn't support a contrast value outside of 0-1, so default to 1.0
+ if (contrast < 0.0f || contrast > 1.0f) {
+ contrast = 1.0f;
+ }
+
+ Float clearTypeLevel = settings.ClearTypeLevel();
+ if (clearTypeLevel < 0.0f || clearTypeLevel > 1.0f) {
+ clearTypeLevel = 1.0f;
+ }
+
+ return SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle,
+ (int)settings.RenderingMode(), gamma,
+ contrast, clearTypeLevel);
+}
+
+void ScaledFontDWrite::SetupSkFontDrawOptions(SkFont& aFont) {
+ if (ForceGDIMode()) {
+ aFont.setEmbeddedBitmaps(true);
+ aFont.setSubpixel(false);
+ } else {
+ aFont.setEmbeddedBitmaps(UseEmbeddedBitmaps());
+ aFont.setSubpixel(true);
+ }
+}
+
+bool ScaledFontDWrite::MayUseBitmaps() {
+ return ForceGDIMode() || UseEmbeddedBitmaps();
+}
+
+void ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
+ PathBuilder* aBuilder,
+ const Matrix* aTransformHint) {
+ BackendType backendType = aBuilder->GetBackendType();
+ if (backendType != BackendType::DIRECT2D &&
+ backendType != BackendType::DIRECT2D1_1) {
+ ScaledFontBase::CopyGlyphsToBuilder(aBuffer, aBuilder, aTransformHint);
+ return;
+ }
+
+ PathBuilderD2D* pathBuilderD2D = static_cast<PathBuilderD2D*>(aBuilder);
+
+ if (pathBuilderD2D->IsFigureActive()) {
+ gfxCriticalNote
+ << "Attempting to copy glyphs to PathBuilderD2D with active figure.";
+ }
+
+ CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink());
+}
+
+void ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer& aBuffer,
+ ID2D1SimplifiedGeometrySink* aSink) {
+ std::vector<UINT16> indices;
+ std::vector<FLOAT> advances;
+ std::vector<DWRITE_GLYPH_OFFSET> offsets;
+ indices.resize(aBuffer.mNumGlyphs);
+ advances.resize(aBuffer.mNumGlyphs);
+ offsets.resize(aBuffer.mNumGlyphs);
+
+ memset(&advances.front(), 0, sizeof(FLOAT) * aBuffer.mNumGlyphs);
+ for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
+ indices[i] = aBuffer.mGlyphs[i].mIndex;
+ offsets[i].advanceOffset = aBuffer.mGlyphs[i].mPosition.x;
+ offsets[i].ascenderOffset = -aBuffer.mGlyphs[i].mPosition.y;
+ }
+
+ HRESULT hr = mFontFace->GetGlyphRunOutline(
+ mSize, &indices.front(), &advances.front(), &offsets.front(),
+ aBuffer.mNumGlyphs, FALSE, FALSE, aSink);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to copy glyphs to geometry sink. Code: "
+ << hexa(hr);
+ }
+}
+
+bool UnscaledFontDWrite::GetFontFileData(FontFileDataOutput aDataCallback,
+ void* aBaton) {
+ UINT32 fileCount = 0;
+ HRESULT hr = mFontFace->GetFiles(&fileCount, nullptr);
+
+ if (FAILED(hr) || fileCount > 1) {
+ MOZ_ASSERT(false);
+ return false;
+ }
+
+ if (!aDataCallback) {
+ return true;
+ }
+
+ RefPtr<IDWriteFontFile> file;
+ hr = mFontFace->GetFiles(&fileCount, getter_AddRefs(file));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ const void* referenceKey;
+ UINT32 refKeySize;
+ // XXX - This can currently crash for webfonts, as when we get the reference
+ // key out of the file, that can be an invalid reference key for the loader
+ // we use it with. The fix to this is not obvious but it will probably
+ // have to happen inside thebes.
+ hr = file->GetReferenceKey(&referenceKey, &refKeySize);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ RefPtr<IDWriteFontFileLoader> loader;
+ hr = file->GetLoader(getter_AddRefs(loader));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ RefPtr<IDWriteFontFileStream> stream;
+ hr = loader->CreateStreamFromKey(referenceKey, refKeySize,
+ getter_AddRefs(stream));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ UINT64 fileSize64;
+ hr = stream->GetFileSize(&fileSize64);
+ if (FAILED(hr) || fileSize64 > UINT32_MAX) {
+ MOZ_ASSERT(false);
+ return false;
+ }
+
+ // Try to catch any device memory exceptions that may occur while attempting
+ // to read the file fragment.
+ void* context = nullptr;
+ hr = E_FAIL;
+ MOZ_SEH_TRY {
+ uint32_t fileSize = static_cast<uint32_t>(fileSize64);
+ const void* fragmentStart = nullptr;
+ hr = stream->ReadFileFragment(&fragmentStart, 0, fileSize, &context);
+ if (SUCCEEDED(hr)) {
+ aDataCallback((uint8_t*)fragmentStart, fileSize, mFontFace->GetIndex(),
+ aBaton);
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred reading DWrite font file data";
+ }
+ if (FAILED(hr)) {
+ return false;
+ }
+ stream->ReleaseFileFragment(context);
+ return true;
+}
+
+static bool GetFontFileName(RefPtr<IDWriteFontFace> aFontFace,
+ std::vector<WCHAR>& aFileName) {
+ UINT32 numFiles;
+ HRESULT hr = aFontFace->GetFiles(&numFiles, nullptr);
+ if (FAILED(hr)) {
+ gfxDebug() << "Failed getting file count for WR font";
+ return false;
+ } else if (numFiles != 1) {
+ gfxDebug() << "Invalid file count " << numFiles << " for WR font";
+ return false;
+ }
+
+ RefPtr<IDWriteFontFile> file;
+ hr = aFontFace->GetFiles(&numFiles, getter_AddRefs(file));
+ if (FAILED(hr)) {
+ gfxDebug() << "Failed getting file for WR font";
+ return false;
+ }
+
+ const void* key;
+ UINT32 keySize;
+ hr = file->GetReferenceKey(&key, &keySize);
+ if (FAILED(hr)) {
+ gfxDebug() << "Failed getting file ref key for WR font";
+ return false;
+ }
+ RefPtr<IDWriteFontFileLoader> loader;
+ hr = file->GetLoader(getter_AddRefs(loader));
+ if (FAILED(hr)) {
+ gfxDebug() << "Failed getting file loader for WR font";
+ return false;
+ }
+ RefPtr<IDWriteLocalFontFileLoader> localLoader;
+ loader->QueryInterface(__uuidof(IDWriteLocalFontFileLoader),
+ (void**)getter_AddRefs(localLoader));
+ if (!localLoader) {
+ gfxDebug() << "Failed querying loader interface for WR font";
+ return false;
+ }
+ UINT32 pathLen;
+ hr = localLoader->GetFilePathLengthFromKey(key, keySize, &pathLen);
+ if (FAILED(hr)) {
+ gfxDebug() << "Failed getting path length for WR font";
+ return false;
+ }
+ aFileName.resize(pathLen + 1);
+ hr = localLoader->GetFilePathFromKey(key, keySize, aFileName.data(),
+ pathLen + 1);
+ if (FAILED(hr) || aFileName.back() != 0) {
+ gfxDebug() << "Failed getting path for WR font";
+ return false;
+ }
+ DWORD attribs = GetFileAttributesW(aFileName.data());
+ if (attribs == INVALID_FILE_ATTRIBUTES) {
+ gfxDebug() << "Invalid file \"" << aFileName.data() << "\" for WR font";
+ return false;
+ }
+ // We leave the null terminator at the end of the returned file name.
+ return true;
+}
+
+bool UnscaledFontDWrite::GetFontDescriptor(FontDescriptorOutput aCb,
+ void* aBaton) {
+ if (!mFont) {
+ return false;
+ }
+
+ std::vector<WCHAR> fileName;
+ if (!GetFontFileName(mFontFace, fileName)) {
+ return false;
+ }
+ uint32_t index = mFontFace->GetIndex();
+
+ aCb(reinterpret_cast<const uint8_t*>(fileName.data()),
+ fileName.size() * sizeof(WCHAR), index, aBaton);
+ return true;
+}
+
+ScaledFontDWrite::InstanceData::InstanceData(
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions) {
+ if (aOptions) {
+ if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
+ mUseEmbeddedBitmap = true;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
+ mUseBoldSimulation = true;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::MULTISTRIKE_BOLD) {
+ mUseMultistrikeBold = true;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::FORCE_GDI) {
+ mGDIForced = true;
+ }
+ }
+}
+
+bool ScaledFontDWrite::HasVariationSettings() {
+ RefPtr<IDWriteFontFace5> ff5;
+ mFontFace->QueryInterface(__uuidof(IDWriteFontFace5),
+ (void**)getter_AddRefs(ff5));
+ if (!ff5 || !ff5->HasVariations()) {
+ return false;
+ }
+
+ uint32_t count = ff5->GetFontAxisValueCount();
+ if (!count) {
+ return false;
+ }
+
+ RefPtr<IDWriteFontResource> res;
+ if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) {
+ return false;
+ }
+
+ std::vector<DWRITE_FONT_AXIS_VALUE> defaults(count);
+ if (FAILED(res->GetDefaultFontAxisValues(defaults.data(), count))) {
+ return false;
+ }
+
+ std::vector<DWRITE_FONT_AXIS_VALUE> values(count);
+ if (FAILED(ff5->GetFontAxisValues(values.data(), count))) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ DWRITE_FONT_AXIS_ATTRIBUTES attr = res->GetFontAxisAttributes(i);
+ if (attr & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) {
+ if (values[i].value != defaults[i].value) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// Helper for ScaledFontDWrite::GetFontInstanceData: if the font has variation
+// axes, get their current values into the aOutput vector.
+static void GetVariationsFromFontFace(IDWriteFontFace* aFace,
+ std::vector<FontVariation>* aOutput) {
+ RefPtr<IDWriteFontFace5> ff5;
+ aFace->QueryInterface(__uuidof(IDWriteFontFace5),
+ (void**)getter_AddRefs(ff5));
+ if (!ff5 || !ff5->HasVariations()) {
+ return;
+ }
+
+ uint32_t count = ff5->GetFontAxisValueCount();
+ if (!count) {
+ return;
+ }
+
+ RefPtr<IDWriteFontResource> res;
+ if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) {
+ return;
+ }
+
+ std::vector<DWRITE_FONT_AXIS_VALUE> values(count);
+ if (FAILED(ff5->GetFontAxisValues(values.data(), count))) {
+ return;
+ }
+
+ aOutput->reserve(count);
+ for (uint32_t i = 0; i < count; i++) {
+ DWRITE_FONT_AXIS_ATTRIBUTES attr = res->GetFontAxisAttributes(i);
+ if (attr & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) {
+ float v = values[i].value;
+ uint32_t t = TRUETYPE_TAG(
+ uint8_t(values[i].axisTag), uint8_t(values[i].axisTag >> 8),
+ uint8_t(values[i].axisTag >> 16), uint8_t(values[i].axisTag >> 24));
+ aOutput->push_back(FontVariation{uint32_t(t), float(v)});
+ }
+ }
+}
+
+bool ScaledFontDWrite::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ InstanceData instance(this);
+
+ // If the font has variations, get the list of axis values.
+ std::vector<FontVariation> variations;
+ GetVariationsFromFontFace(mFontFace, &variations);
+
+ aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance),
+ variations.data(), variations.size(), aBaton);
+
+ return true;
+}
+
+bool ScaledFontDWrite::GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) {
+ wr::FontInstanceOptions options;
+ options.render_mode = wr::ToFontRenderMode(GetDefaultAAMode());
+ options.flags = wr::FontInstanceFlags{0};
+ if (HasBoldSimulation()) {
+ options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
+ }
+ if (UseMultistrikeBold()) {
+ options.flags |= wr::FontInstanceFlags::MULTISTRIKE_BOLD;
+ }
+ if (UseEmbeddedBitmaps()) {
+ options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
+ }
+ if (ForceGDIMode()) {
+ options.flags |= wr::FontInstanceFlags::FORCE_GDI;
+ } else {
+ options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION;
+ }
+ auto& settings = DWriteSettings();
+ switch (settings.RenderingMode()) {
+ case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC:
+ options.flags |= wr::FontInstanceFlags::FORCE_SYMMETRIC;
+ break;
+ case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL:
+ options.flags |= wr::FontInstanceFlags::NO_SYMMETRIC;
+ break;
+ default:
+ break;
+ }
+ if (Factory::GetBGRSubpixelOrder()) {
+ options.flags |= wr::FontInstanceFlags::SUBPIXEL_BGR;
+ }
+ options.bg_color = wr::ToColorU(DeviceColor());
+ options.synthetic_italics =
+ wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
+
+ wr::FontInstancePlatformOptions platformOptions;
+ platformOptions.gamma = uint16_t(std::round(settings.Gamma() * 100.0f));
+ platformOptions.contrast =
+ uint8_t(std::round(std::min(settings.EnhancedContrast(), 1.0f) * 100.0f));
+ platformOptions.cleartype_level =
+ uint8_t(std::round(std::min(settings.ClearTypeLevel(), 1.0f) * 100.0f));
+
+ *aOutOptions = Some(options);
+ *aOutPlatformOptions = Some(platformOptions);
+
+ GetVariationsFromFontFace(mFontFace, aOutVariations);
+
+ return true;
+}
+
+DWriteSettings& ScaledFontDWrite::DWriteSettings() const {
+ return DWriteSettings::Get(mGDIForced);
+}
+
+// Helper for UnscaledFontDWrite::CreateScaledFont: create a clone of the
+// given IDWriteFontFace, with specified variation-axis values applied.
+// Returns nullptr in case of failure.
+static already_AddRefed<IDWriteFontFace5> CreateFaceWithVariations(
+ IDWriteFontFace* aFace, DWRITE_FONT_SIMULATIONS aSimulations,
+ const FontVariation* aVariations = nullptr, uint32_t aNumVariations = 0) {
+ auto makeDWriteAxisTag = [](uint32_t aTag) {
+ return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff, (aTag >> 16) & 0xff,
+ (aTag >> 8) & 0xff, aTag & 0xff);
+ };
+
+ MOZ_SEH_TRY {
+ RefPtr<IDWriteFontFace5> ff5;
+ aFace->QueryInterface(__uuidof(IDWriteFontFace5),
+ (void**)getter_AddRefs(ff5));
+ if (!ff5) {
+ return nullptr;
+ }
+
+ RefPtr<IDWriteFontResource> res;
+ if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) {
+ return nullptr;
+ }
+
+ std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues;
+ if (aNumVariations) {
+ fontAxisValues.reserve(aNumVariations);
+ for (uint32_t i = 0; i < aNumVariations; i++) {
+ DWRITE_FONT_AXIS_VALUE axisValue = {
+ makeDWriteAxisTag(aVariations[i].mTag), aVariations[i].mValue};
+ fontAxisValues.push_back(axisValue);
+ }
+ } else {
+ uint32_t count = ff5->GetFontAxisValueCount();
+ if (count) {
+ fontAxisValues.resize(count);
+ if (FAILED(ff5->GetFontAxisValues(fontAxisValues.data(), count))) {
+ fontAxisValues.clear();
+ }
+ }
+ }
+
+ RefPtr<IDWriteFontFace5> newFace;
+ if (FAILED(res->CreateFontFace(aSimulations, fontAxisValues.data(),
+ fontAxisValues.size(),
+ getter_AddRefs(newFace)))) {
+ return nullptr;
+ }
+ return newFace.forget();
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred initializing variation face";
+ return nullptr;
+ }
+}
+
+bool UnscaledFontDWrite::InitBold() {
+ if (mFontFaceBold) {
+ return true;
+ }
+
+ DWRITE_FONT_SIMULATIONS sims = mFontFace->GetSimulations();
+ if (sims & DWRITE_FONT_SIMULATIONS_BOLD) {
+ mFontFaceBold = mFontFace;
+ return true;
+ }
+ sims |= DWRITE_FONT_SIMULATIONS_BOLD;
+
+ RefPtr<IDWriteFontFace5> ff5 = CreateFaceWithVariations(mFontFace, sims);
+ if (ff5) {
+ mFontFaceBold = ff5;
+ } else {
+ MOZ_SEH_TRY {
+ UINT32 numFiles = 0;
+ if (FAILED(mFontFace->GetFiles(&numFiles, nullptr))) {
+ return false;
+ }
+ StackArray<IDWriteFontFile*, 1> files(numFiles);
+ if (FAILED(mFontFace->GetFiles(&numFiles, files.data()))) {
+ return false;
+ }
+ HRESULT hr = Factory::GetDWriteFactory()->CreateFontFace(
+ mFontFace->GetType(), numFiles, files.data(), mFontFace->GetIndex(),
+ sims, getter_AddRefs(mFontFaceBold));
+ for (UINT32 i = 0; i < numFiles; ++i) {
+ files[i]->Release();
+ }
+ if (FAILED(hr) || !mFontFaceBold) {
+ return false;
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred initializing bold face";
+ return false;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<ScaledFont> UnscaledFontDWrite::CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) {
+ if (aInstanceDataLength < sizeof(ScaledFontDWrite::InstanceData)) {
+ gfxWarning() << "DWrite scaled font instance data is truncated.";
+ return nullptr;
+ }
+ const ScaledFontDWrite::InstanceData& instanceData =
+ *reinterpret_cast<const ScaledFontDWrite::InstanceData*>(aInstanceData);
+
+ IDWriteFontFace* face = mFontFace;
+ if (instanceData.mUseBoldSimulation) {
+ if (!InitBold()) {
+ gfxWarning() << "Failed creating bold IDWriteFontFace.";
+ return nullptr;
+ }
+ face = mFontFaceBold;
+ }
+ DWRITE_FONT_SIMULATIONS sims = face->GetSimulations();
+
+ // If variations are required, we create a separate IDWriteFontFace5 with
+ // the requested settings applied.
+ RefPtr<IDWriteFontFace5> ff5;
+ if (aNumVariations) {
+ ff5 =
+ CreateFaceWithVariations(mFontFace, sims, aVariations, aNumVariations);
+ if (ff5) {
+ face = ff5;
+ } else {
+ gfxWarning() << "Failed to create IDWriteFontFace5 with variations.";
+ }
+ }
+
+ return MakeAndAddRef<ScaledFontDWrite>(
+ face, this, aGlyphSize, instanceData.mUseEmbeddedBitmap,
+ instanceData.mUseMultistrikeBold, instanceData.mGDIForced, nullptr);
+}
+
+already_AddRefed<ScaledFont> UnscaledFontDWrite::CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ ScaledFontDWrite::InstanceData instanceData(aOptions, aPlatformOptions);
+ return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData),
+ sizeof(instanceData), aVariations, aNumVariations);
+}
+
+AntialiasMode ScaledFontDWrite::GetDefaultAAMode() {
+ AntialiasMode defaultMode = GetSystemDefaultAAMode();
+
+ switch (defaultMode) {
+ case AntialiasMode::SUBPIXEL:
+ case AntialiasMode::DEFAULT:
+ if (DWriteSettings().ClearTypeLevel() == 0.0f) {
+ defaultMode = AntialiasMode::GRAY;
+ }
+ break;
+ case AntialiasMode::GRAY:
+ if (!DoGrayscale(mFontFace, mSize)) {
+ defaultMode = AntialiasMode::NONE;
+ }
+ break;
+ case AntialiasMode::NONE:
+ break;
+ }
+
+ return defaultMode;
+}
+
+cairo_font_face_t* ScaledFontDWrite::CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ if (!mFontFace) {
+ return nullptr;
+ }
+
+ return cairo_dwrite_font_face_create_for_dwrite_fontface(nullptr, mFontFace);
+}
+
+void ScaledFontDWrite::PrepareCairoScaledFont(cairo_scaled_font_t* aFont) {
+ if (mGDIForced) {
+ cairo_dwrite_scaled_font_set_force_GDI_classic(aFont, true);
+ }
+}
+
+already_AddRefed<UnscaledFont> UnscaledFontDWrite::CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
+ // Note that despite the type of aData here, it actually points to a 16-bit
+ // Windows font file path (hence the cast to WCHAR* below).
+ if (aDataLength == 0) {
+ gfxWarning() << "DWrite font descriptor is truncated.";
+ return nullptr;
+ }
+
+ RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
+ if (!factory) {
+ return nullptr;
+ }
+
+ MOZ_SEH_TRY {
+ RefPtr<IDWriteFontFile> fontFile;
+ HRESULT hr = factory->CreateFontFileReference((const WCHAR*)aData, nullptr,
+ getter_AddRefs(fontFile));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+ BOOL isSupported;
+ DWRITE_FONT_FILE_TYPE fileType;
+ DWRITE_FONT_FACE_TYPE faceType;
+ UINT32 numFaces;
+ hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numFaces);
+ if (FAILED(hr) || !isSupported || aIndex >= numFaces) {
+ return nullptr;
+ }
+ IDWriteFontFile* fontFiles[1] = {fontFile.get()};
+ RefPtr<IDWriteFontFace> fontFace;
+ hr = factory->CreateFontFace(faceType, 1, fontFiles, aIndex,
+ DWRITE_FONT_SIMULATIONS_NONE,
+ getter_AddRefs(fontFace));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+ RefPtr unscaledFont = new UnscaledFontDWrite(fontFace, nullptr);
+ return unscaledFont.forget();
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred creating unscaledFont for "
+ << NS_ConvertUTF16toUTF8((const char16_t*)aData).get();
+ return nullptr;
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ScaledFontDWrite.h b/gfx/2d/ScaledFontDWrite.h
new file mode 100644
index 0000000000..da1568b8ca
--- /dev/null
+++ b/gfx/2d/ScaledFontDWrite.h
@@ -0,0 +1,105 @@
+/* -*- 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_SCALEDFONTDWRITE_H_
+#define MOZILLA_GFX_SCALEDFONTDWRITE_H_
+
+#include <dwrite.h>
+#include "DWriteSettings.h"
+#include "ScaledFontBase.h"
+
+struct ID2D1GeometrySink;
+struct gfxFontStyle;
+
+namespace mozilla {
+namespace gfx {
+
+class NativeFontResourceDWrite;
+class UnscaledFontDWrite;
+
+class ScaledFontDWrite final : public ScaledFontBase {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontDWrite, override)
+ ScaledFontDWrite(IDWriteFontFace* aFontFace,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ bool aUseEmbeddedBitmap, bool aUseMultistrikeBold,
+ bool aGDIForced, const gfxFontStyle* aStyle);
+
+ FontType GetType() const override { return FontType::DWRITE; }
+
+ already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer& aBuffer,
+ const DrawTarget* aTarget) override;
+ void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, PathBuilder* aBuilder,
+ const Matrix* aTransformHint) override;
+
+ void CopyGlyphsToSink(const GlyphBuffer& aBuffer,
+ ID2D1SimplifiedGeometrySink* aSink);
+
+ bool CanSerialize() override { return true; }
+
+ bool MayUseBitmaps() override;
+
+ bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
+
+ bool GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) override;
+
+ DWriteSettings& DWriteSettings() const;
+
+ AntialiasMode GetDefaultAAMode() override;
+
+ bool UseEmbeddedBitmaps() const { return mUseEmbeddedBitmap; }
+ bool UseMultistrikeBold() const { return mUseMultistrikeBold; }
+ bool ForceGDIMode() const { return mGDIForced; }
+
+ bool UseSubpixelPosition() const override { return !ForceGDIMode(); }
+
+ bool HasBoldSimulation() const {
+ return (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) != 0;
+ }
+
+ bool HasVariationSettings() override;
+
+ SkTypeface* CreateSkTypeface() override;
+ void SetupSkFontDrawOptions(SkFont& aFont) override;
+ SkFontStyle mStyle;
+
+ RefPtr<IDWriteFontFace> mFontFace;
+ bool mUseEmbeddedBitmap;
+ bool mUseMultistrikeBold = false;
+ bool mGDIForced = false;
+
+ cairo_font_face_t* CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) override;
+ void PrepareCairoScaledFont(cairo_scaled_font_t* aFont) override;
+
+ private:
+ friend class NativeFontResourceDWrite;
+ friend class UnscaledFontDWrite;
+
+ struct InstanceData {
+ explicit InstanceData(ScaledFontDWrite* aScaledFont)
+ : mUseEmbeddedBitmap(aScaledFont->mUseEmbeddedBitmap),
+ mUseBoldSimulation(aScaledFont->HasBoldSimulation()),
+ mUseMultistrikeBold(aScaledFont->UseMultistrikeBold()),
+ mGDIForced(aScaledFont->mGDIForced) {}
+
+ InstanceData(const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions);
+
+ bool mUseEmbeddedBitmap = false;
+ bool mUseBoldSimulation = false;
+ bool mUseMultistrikeBold = false;
+ bool mGDIForced = false;
+ };
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTDWRITE_H_ */
diff --git a/gfx/2d/ScaledFontFontconfig.cpp b/gfx/2d/ScaledFontFontconfig.cpp
new file mode 100644
index 0000000000..e3025c1072
--- /dev/null
+++ b/gfx/2d/ScaledFontFontconfig.cpp
@@ -0,0 +1,550 @@
+/* -*- 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 "ScaledFontFontconfig.h"
+#include "UnscaledFontFreeType.h"
+#include "Logging.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+#include "skia/include/ports/SkTypeface_cairo.h"
+#include "HelpersSkia.h"
+
+#include <fontconfig/fcfreetype.h>
+
+#include FT_LCD_FILTER_H
+#include FT_MULTIPLE_MASTERS_H
+
+namespace mozilla::gfx {
+
+ScaledFontFontconfig::ScaledFontFontconfig(
+ RefPtr<SharedFTFace>&& aFace, FcPattern* aPattern,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize)
+ : ScaledFontBase(aUnscaledFont, aSize),
+ mFace(std::move(aFace)),
+ mInstanceData(aPattern) {}
+
+ScaledFontFontconfig::ScaledFontFontconfig(
+ RefPtr<SharedFTFace>&& aFace, const InstanceData& aInstanceData,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize)
+ : ScaledFontBase(aUnscaledFont, aSize),
+ mFace(std::move(aFace)),
+ mInstanceData(aInstanceData) {}
+
+bool ScaledFontFontconfig::UseSubpixelPosition() const {
+ return !MOZ_UNLIKELY(
+ StaticPrefs::
+ gfx_text_subpixel_position_force_disabled_AtStartup()) &&
+ mInstanceData.mAntialias != AntialiasMode::NONE &&
+ FT_IS_SCALABLE(mFace->GetFace()) &&
+ (mInstanceData.mHinting == FontHinting::NONE ||
+ mInstanceData.mHinting == FontHinting::LIGHT ||
+ MOZ_UNLIKELY(
+ StaticPrefs::
+ gfx_text_subpixel_position_force_enabled_AtStartup()));
+}
+
+SkTypeface* ScaledFontFontconfig::CreateSkTypeface() {
+ SkPixelGeometry geo = mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR
+ ? (mInstanceData.mFlags & InstanceData::LCD_VERTICAL
+ ? kBGR_V_SkPixelGeometry
+ : kBGR_H_SkPixelGeometry)
+ : (mInstanceData.mFlags & InstanceData::LCD_VERTICAL
+ ? kRGB_V_SkPixelGeometry
+ : kRGB_H_SkPixelGeometry);
+ return SkCreateTypefaceFromCairoFTFont(mFace->GetFace(), mFace.get(), geo,
+ mInstanceData.mLcdFilter);
+}
+
+void ScaledFontFontconfig::SetupSkFontDrawOptions(SkFont& aFont) {
+ aFont.setSubpixel(UseSubpixelPosition());
+
+ if (mInstanceData.mFlags & InstanceData::AUTOHINT) {
+ aFont.setForceAutoHinting(true);
+ }
+ if (mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP) {
+ aFont.setEmbeddedBitmaps(true);
+ }
+ if (mInstanceData.mFlags & InstanceData::EMBOLDEN) {
+ aFont.setEmbolden(true);
+ }
+
+ aFont.setHinting(GfxHintingToSkiaHinting(mInstanceData.mHinting));
+}
+
+bool ScaledFontFontconfig::MayUseBitmaps() {
+ return mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP &&
+ !FT_IS_SCALABLE(mFace->GetFace());
+}
+
+cairo_font_face_t* ScaledFontFontconfig::CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ int loadFlags;
+ unsigned int synthFlags;
+ mInstanceData.SetupFontOptions(aFontOptions, &loadFlags, &synthFlags);
+
+ return cairo_ft_font_face_create_for_ft_face(mFace->GetFace(), loadFlags,
+ synthFlags, mFace.get());
+}
+
+AntialiasMode ScaledFontFontconfig::GetDefaultAAMode() {
+ return mInstanceData.mAntialias;
+}
+
+bool FcPatternAllowsBitmaps(FcPattern* aPattern, bool aAntialias,
+ bool aHinting) {
+ if (!aAntialias) {
+ // Always allow bitmaps when antialiasing is disabled
+ return true;
+ }
+ FcBool bitmap;
+ if (FcPatternGetBool(aPattern, FC_EMBEDDED_BITMAP, 0, &bitmap) !=
+ FcResultMatch ||
+ !bitmap) {
+ // If bitmaps were explicitly disabled, then disallow them
+ return false;
+ }
+ if (aHinting) {
+ // If hinting is used and bitmaps were enabled, then allow them
+ return true;
+ }
+ // When hinting is disabled, then avoid loading bitmaps from outline
+ // fonts. However, emoji fonts may have no outlines while containing
+ // bitmaps intended to be scaled, so still allow those.
+ FcBool outline;
+ if (FcPatternGetBool(aPattern, FC_OUTLINE, 0, &outline) == FcResultMatch &&
+ outline) {
+ return false;
+ }
+ FcBool scalable;
+ if (FcPatternGetBool(aPattern, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
+ !scalable) {
+ return false;
+ }
+ return true;
+}
+
+ScaledFontFontconfig::InstanceData::InstanceData(FcPattern* aPattern)
+ : mFlags(0),
+ mAntialias(AntialiasMode::NONE),
+ mHinting(FontHinting::NONE),
+ mLcdFilter(FT_LCD_FILTER_LEGACY) {
+ // Record relevant Fontconfig properties into instance data.
+ FcBool autohint;
+ if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch &&
+ autohint) {
+ mFlags |= AUTOHINT;
+ }
+ FcBool embolden;
+ if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch &&
+ embolden) {
+ mFlags |= EMBOLDEN;
+ }
+
+ // For printer fonts, Cairo hint metrics and hinting will be disabled.
+ // For other fonts, allow hint metrics and hinting.
+ FcBool printing;
+ if (FcPatternGetBool(aPattern, "gfx.printing", 0, &printing) !=
+ FcResultMatch ||
+ !printing) {
+ mFlags |= HINT_METRICS;
+
+ FcBool hinting;
+ if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch ||
+ hinting) {
+ int hintstyle;
+ if (FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &hintstyle) !=
+ FcResultMatch) {
+ hintstyle = FC_HINT_FULL;
+ }
+ switch (hintstyle) {
+ case FC_HINT_SLIGHT:
+ mHinting = FontHinting::LIGHT;
+ break;
+ case FC_HINT_MEDIUM:
+ mHinting = FontHinting::NORMAL;
+ break;
+ case FC_HINT_FULL:
+ mHinting = FontHinting::FULL;
+ break;
+ case FC_HINT_NONE:
+ default:
+ break;
+ }
+ }
+ }
+
+ FcBool antialias;
+ if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &antialias) ==
+ FcResultMatch &&
+ !antialias) {
+ // If AA is explicitly disabled, leave bitmaps enabled.
+ mFlags |= EMBEDDED_BITMAP;
+ } else {
+ mAntialias = AntialiasMode::GRAY;
+
+ // Otherwise, if AA is enabled, disable embedded bitmaps unless explicitly
+ // enabled.
+ if (FcPatternAllowsBitmaps(aPattern, true, mHinting != FontHinting::NONE)) {
+ mFlags |= EMBEDDED_BITMAP;
+ }
+
+ // Only record subpixel order and lcd filtering if antialiasing is enabled.
+ int rgba;
+ if (mFlags & HINT_METRICS &&
+ FcPatternGetInteger(aPattern, FC_RGBA, 0, &rgba) == FcResultMatch) {
+ switch (rgba) {
+ case FC_RGBA_RGB:
+ case FC_RGBA_BGR:
+ case FC_RGBA_VRGB:
+ case FC_RGBA_VBGR:
+ mAntialias = AntialiasMode::SUBPIXEL;
+ if (rgba == FC_RGBA_VRGB || rgba == FC_RGBA_VBGR) {
+ mFlags |= LCD_VERTICAL;
+ }
+ if (rgba == FC_RGBA_BGR || rgba == FC_RGBA_VBGR) {
+ mFlags |= SUBPIXEL_BGR;
+ }
+ break;
+ case FC_RGBA_NONE:
+ case FC_RGBA_UNKNOWN:
+ default:
+ break;
+ }
+ }
+
+ int filter;
+ if (mAntialias == AntialiasMode::SUBPIXEL &&
+ FcPatternGetInteger(aPattern, FC_LCD_FILTER, 0, &filter) ==
+ FcResultMatch) {
+ switch (filter) {
+ case FC_LCD_NONE:
+ mLcdFilter = FT_LCD_FILTER_NONE;
+ break;
+ case FC_LCD_DEFAULT:
+ mLcdFilter = FT_LCD_FILTER_DEFAULT;
+ break;
+ case FC_LCD_LIGHT:
+ mLcdFilter = FT_LCD_FILTER_LIGHT;
+ break;
+ case FC_LCD_LEGACY:
+ default:
+ break;
+ }
+ }
+ }
+}
+
+ScaledFontFontconfig::InstanceData::InstanceData(
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions)
+ : mFlags(HINT_METRICS),
+ mAntialias(AntialiasMode::NONE),
+ mHinting(FontHinting::FULL),
+ mLcdFilter(FT_LCD_FILTER_LEGACY) {
+ if (aOptions) {
+ if (aOptions->flags & wr::FontInstanceFlags::FORCE_AUTOHINT) {
+ mFlags |= AUTOHINT;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
+ mFlags |= EMBEDDED_BITMAP;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
+ mFlags |= EMBOLDEN;
+ }
+ if (aOptions->render_mode == wr::FontRenderMode::Subpixel) {
+ mAntialias = AntialiasMode::SUBPIXEL;
+ if (aOptions->flags & wr::FontInstanceFlags::SUBPIXEL_BGR) {
+ mFlags |= SUBPIXEL_BGR;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::LCD_VERTICAL) {
+ mFlags |= LCD_VERTICAL;
+ }
+ } else if (aOptions->render_mode != wr::FontRenderMode::Mono) {
+ mAntialias = AntialiasMode::GRAY;
+ }
+ }
+ if (aPlatformOptions) {
+ switch (aPlatformOptions->hinting) {
+ case wr::FontHinting::None:
+ mHinting = FontHinting::NONE;
+ break;
+ case wr::FontHinting::Light:
+ mHinting = FontHinting::LIGHT;
+ break;
+ case wr::FontHinting::Normal:
+ mHinting = FontHinting::NORMAL;
+ break;
+ default:
+ break;
+ }
+ switch (aPlatformOptions->lcd_filter) {
+ case wr::FontLCDFilter::None:
+ mLcdFilter = FT_LCD_FILTER_NONE;
+ break;
+ case wr::FontLCDFilter::Default:
+ mLcdFilter = FT_LCD_FILTER_DEFAULT;
+ break;
+ case wr::FontLCDFilter::Light:
+ mLcdFilter = FT_LCD_FILTER_LIGHT;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void ScaledFontFontconfig::InstanceData::SetupFontOptions(
+ cairo_font_options_t* aFontOptions, int* aOutLoadFlags,
+ unsigned int* aOutSynthFlags) const {
+ // For regular (non-printer) fonts, enable hint metrics as well as hinting
+ // and (possibly subpixel) antialiasing.
+ cairo_font_options_set_hint_metrics(
+ aFontOptions,
+ mFlags & HINT_METRICS ? CAIRO_HINT_METRICS_ON : CAIRO_HINT_METRICS_OFF);
+
+ cairo_hint_style_t hinting;
+ switch (mHinting) {
+ case FontHinting::NONE:
+ hinting = CAIRO_HINT_STYLE_NONE;
+ break;
+ case FontHinting::LIGHT:
+ hinting = CAIRO_HINT_STYLE_SLIGHT;
+ break;
+ case FontHinting::NORMAL:
+ hinting = CAIRO_HINT_STYLE_MEDIUM;
+ break;
+ case FontHinting::FULL:
+ hinting = CAIRO_HINT_STYLE_FULL;
+ break;
+ }
+ cairo_font_options_set_hint_style(aFontOptions, hinting);
+
+ switch (mAntialias) {
+ case AntialiasMode::NONE:
+ cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_NONE);
+ break;
+ case AntialiasMode::GRAY:
+ default:
+ cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_GRAY);
+ break;
+ case AntialiasMode::SUBPIXEL: {
+ cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_SUBPIXEL);
+ cairo_font_options_set_subpixel_order(
+ aFontOptions,
+ mFlags & SUBPIXEL_BGR
+ ? (mFlags & LCD_VERTICAL ? CAIRO_SUBPIXEL_ORDER_VBGR
+ : CAIRO_SUBPIXEL_ORDER_BGR)
+ : (mFlags & LCD_VERTICAL ? CAIRO_SUBPIXEL_ORDER_VRGB
+ : CAIRO_SUBPIXEL_ORDER_RGB));
+ cairo_lcd_filter_t lcdFilter = CAIRO_LCD_FILTER_DEFAULT;
+ switch (mLcdFilter) {
+ case FT_LCD_FILTER_NONE:
+ lcdFilter = CAIRO_LCD_FILTER_NONE;
+ break;
+ case FT_LCD_FILTER_DEFAULT:
+ lcdFilter = CAIRO_LCD_FILTER_FIR5;
+ break;
+ case FT_LCD_FILTER_LIGHT:
+ lcdFilter = CAIRO_LCD_FILTER_FIR3;
+ break;
+ case FT_LCD_FILTER_LEGACY:
+ lcdFilter = CAIRO_LCD_FILTER_INTRA_PIXEL;
+ break;
+ }
+ cairo_font_options_set_lcd_filter(aFontOptions, lcdFilter);
+ break;
+ }
+ }
+
+ // Try to build a sane initial set of Cairo font options based on the
+ // Fontconfig pattern.
+ int loadFlags = FT_LOAD_DEFAULT;
+ unsigned int synthFlags = 0;
+
+ if (!(mFlags & EMBEDDED_BITMAP)) {
+ loadFlags |= FT_LOAD_NO_BITMAP;
+ }
+ if (mFlags & AUTOHINT) {
+ loadFlags |= FT_LOAD_FORCE_AUTOHINT;
+ }
+ if (mFlags & EMBOLDEN) {
+ synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD;
+ }
+
+ *aOutLoadFlags = loadFlags;
+ *aOutSynthFlags = synthFlags;
+}
+
+bool ScaledFontFontconfig::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ std::vector<FontVariation> variations;
+ if (HasVariationSettings()) {
+ UnscaledFontFreeType::GetVariationSettingsFromFace(&variations,
+ mFace->GetFace());
+ }
+
+ aCb(reinterpret_cast<uint8_t*>(&mInstanceData), sizeof(mInstanceData),
+ variations.data(), variations.size(), aBaton);
+ return true;
+}
+
+bool ScaledFontFontconfig::GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) {
+ wr::FontInstanceOptions options;
+ options.render_mode = wr::FontRenderMode::Alpha;
+ options.flags = wr::FontInstanceFlags{0};
+ if (UseSubpixelPosition()) {
+ options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION;
+ }
+ options.bg_color = wr::ToColorU(DeviceColor());
+ options.synthetic_italics =
+ wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
+
+ wr::FontInstancePlatformOptions platformOptions;
+ platformOptions.lcd_filter = wr::FontLCDFilter::Legacy;
+ platformOptions.hinting = wr::FontHinting::Normal;
+
+ if (mInstanceData.mFlags & InstanceData::AUTOHINT) {
+ options.flags |= wr::FontInstanceFlags::FORCE_AUTOHINT;
+ }
+ if (mInstanceData.mFlags & InstanceData::EMBOLDEN) {
+ options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
+ }
+ if (mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP) {
+ options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
+ }
+ if (mInstanceData.mAntialias != AntialiasMode::NONE) {
+ if (mInstanceData.mAntialias == AntialiasMode::SUBPIXEL) {
+ options.render_mode = wr::FontRenderMode::Subpixel;
+ platformOptions.hinting = wr::FontHinting::LCD;
+ if (mInstanceData.mFlags & InstanceData::LCD_VERTICAL) {
+ options.flags |= wr::FontInstanceFlags::LCD_VERTICAL;
+ }
+ if (mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR) {
+ options.flags |= wr::FontInstanceFlags::SUBPIXEL_BGR;
+ }
+ }
+
+ switch (mInstanceData.mLcdFilter) {
+ case FT_LCD_FILTER_NONE:
+ platformOptions.lcd_filter = wr::FontLCDFilter::None;
+ break;
+ case FT_LCD_FILTER_DEFAULT:
+ platformOptions.lcd_filter = wr::FontLCDFilter::Default;
+ break;
+ case FT_LCD_FILTER_LIGHT:
+ platformOptions.lcd_filter = wr::FontLCDFilter::Light;
+ break;
+ case FT_LCD_FILTER_LEGACY:
+ default:
+ break;
+ }
+
+ switch (mInstanceData.mHinting) {
+ case FontHinting::NONE:
+ platformOptions.hinting = wr::FontHinting::None;
+ break;
+ case FontHinting::LIGHT:
+ platformOptions.hinting = wr::FontHinting::Light;
+ break;
+ case FontHinting::NORMAL:
+ platformOptions.hinting = wr::FontHinting::Normal;
+ break;
+ case FontHinting::FULL:
+ break;
+ }
+ } else {
+ options.render_mode = wr::FontRenderMode::Mono;
+
+ switch (mInstanceData.mHinting) {
+ case FontHinting::NONE:
+ platformOptions.hinting = wr::FontHinting::None;
+ break;
+ default:
+ platformOptions.hinting = wr::FontHinting::Mono;
+ break;
+ }
+ }
+
+ *aOutOptions = Some(options);
+ *aOutPlatformOptions = Some(platformOptions);
+
+ if (HasVariationSettings()) {
+ UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations,
+ mFace->GetFace());
+ }
+
+ return true;
+}
+
+already_AddRefed<ScaledFont> UnscaledFontFontconfig::CreateScaledFont(
+ Float aSize, const uint8_t* aInstanceData, uint32_t aInstanceDataLength,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ if (aInstanceDataLength < sizeof(ScaledFontFontconfig::InstanceData)) {
+ gfxWarning() << "Fontconfig scaled font instance data is truncated.";
+ return nullptr;
+ }
+ const ScaledFontFontconfig::InstanceData& instanceData =
+ *reinterpret_cast<const ScaledFontFontconfig::InstanceData*>(
+ aInstanceData);
+
+ RefPtr<SharedFTFace> face(InitFace());
+ if (!face) {
+ gfxWarning() << "Attempted to deserialize Fontconfig scaled font without "
+ "FreeType face";
+ return nullptr;
+ }
+
+ if (aNumVariations > 0 && face->GetData()) {
+ if (RefPtr<SharedFTFace> varFace = face->GetData()->CloneFace()) {
+ face = varFace;
+ }
+ }
+
+ // Only apply variations if we have an explicitly cloned face.
+ if (aNumVariations > 0 && face != GetFace()) {
+ ApplyVariationsToFace(aVariations, aNumVariations, face->GetFace());
+ }
+
+ RefPtr<ScaledFontFontconfig> scaledFont =
+ new ScaledFontFontconfig(std::move(face), instanceData, this, aSize);
+
+ return scaledFont.forget();
+}
+
+already_AddRefed<ScaledFont> UnscaledFontFontconfig::CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ ScaledFontFontconfig::InstanceData instanceData(aOptions, aPlatformOptions);
+ return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData),
+ sizeof(instanceData), aVariations, aNumVariations);
+}
+
+bool ScaledFontFontconfig::HasVariationSettings() {
+ // Check if the FT face has been cloned.
+ return mFace &&
+ mFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS &&
+ mFace != static_cast<UnscaledFontFontconfig*>(mUnscaledFont.get())
+ ->GetFace();
+}
+
+already_AddRefed<UnscaledFont> UnscaledFontFontconfig::CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
+ if (aDataLength == 0) {
+ gfxWarning() << "Fontconfig font descriptor is truncated.";
+ return nullptr;
+ }
+ const char* path = reinterpret_cast<const char*>(aData);
+ RefPtr<UnscaledFont> unscaledFont =
+ new UnscaledFontFontconfig(std::string(path, aDataLength), aIndex);
+ return unscaledFont.forget();
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/ScaledFontFontconfig.h b/gfx/2d/ScaledFontFontconfig.h
new file mode 100644
index 0000000000..e6dfd48097
--- /dev/null
+++ b/gfx/2d/ScaledFontFontconfig.h
@@ -0,0 +1,91 @@
+/* -*- 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_SCALEDFONTFONTCONFIG_H_
+#define MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_
+
+#include "ScaledFontBase.h"
+
+#include <cairo-ft.h>
+
+namespace mozilla {
+namespace gfx {
+
+class NativeFontResourceFontconfig;
+class UnscaledFontFontconfig;
+
+class ScaledFontFontconfig : public ScaledFontBase {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontFontconfig, override)
+ ScaledFontFontconfig(RefPtr<SharedFTFace>&& aFace, FcPattern* aPattern,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
+
+ FontType GetType() const override { return FontType::FONTCONFIG; }
+
+ SkTypeface* CreateSkTypeface() override;
+ void SetupSkFontDrawOptions(SkFont& aFont) override;
+
+ AntialiasMode GetDefaultAAMode() override;
+
+ bool UseSubpixelPosition() const override;
+
+ bool CanSerialize() override { return true; }
+
+ bool MayUseBitmaps() override;
+
+ bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
+
+ bool GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) override;
+
+ bool HasVariationSettings() override;
+
+ protected:
+ cairo_font_face_t* CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) override;
+
+ private:
+ friend class NativeFontResourceFontconfig;
+ friend class UnscaledFontFontconfig;
+
+ struct InstanceData {
+ enum {
+ AUTOHINT = 1 << 0,
+ EMBEDDED_BITMAP = 1 << 1,
+ EMBOLDEN = 1 << 2,
+ HINT_METRICS = 1 << 3,
+ LCD_VERTICAL = 1 << 4,
+ SUBPIXEL_BGR = 1 << 5,
+ };
+
+ explicit InstanceData(FcPattern* aPattern);
+ InstanceData(const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions);
+
+ void SetupFontOptions(cairo_font_options_t* aFontOptions,
+ int* aOutLoadFlags,
+ unsigned int* aOutSynthFlags) const;
+
+ uint8_t mFlags;
+ AntialiasMode mAntialias;
+ FontHinting mHinting;
+ uint8_t mLcdFilter;
+ };
+
+ ScaledFontFontconfig(RefPtr<SharedFTFace>&& aFace,
+ const InstanceData& aInstanceData,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
+
+ RefPtr<SharedFTFace> mFace;
+ InstanceData mInstanceData;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ */
diff --git a/gfx/2d/ScaledFontFreeType.cpp b/gfx/2d/ScaledFontFreeType.cpp
new file mode 100644
index 0000000000..3e335e49c3
--- /dev/null
+++ b/gfx/2d/ScaledFontFreeType.cpp
@@ -0,0 +1,139 @@
+/* -*- 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 "ScaledFontFreeType.h"
+#include "UnscaledFontFreeType.h"
+#include "NativeFontResourceFreeType.h"
+#include "Logging.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+#include "skia/include/ports/SkTypeface_cairo.h"
+
+#include FT_MULTIPLE_MASTERS_H
+
+namespace mozilla {
+namespace gfx {
+
+ScaledFontFreeType::ScaledFontFreeType(
+ RefPtr<SharedFTFace>&& aFace, const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize, bool aApplySyntheticBold)
+ : ScaledFontBase(aUnscaledFont, aSize),
+ mFace(std::move(aFace)),
+ mApplySyntheticBold(aApplySyntheticBold) {}
+
+bool ScaledFontFreeType::UseSubpixelPosition() const {
+ return !MOZ_UNLIKELY(
+ StaticPrefs::
+ gfx_text_subpixel_position_force_disabled_AtStartup()) &&
+ FT_IS_SCALABLE(mFace->GetFace());
+}
+
+SkTypeface* ScaledFontFreeType::CreateSkTypeface() {
+ return SkCreateTypefaceFromCairoFTFont(mFace->GetFace(), mFace.get());
+}
+
+void ScaledFontFreeType::SetupSkFontDrawOptions(SkFont& aFont) {
+ aFont.setSubpixel(UseSubpixelPosition());
+
+ if (mApplySyntheticBold) {
+ aFont.setEmbolden(true);
+ }
+
+ aFont.setEmbeddedBitmaps(true);
+}
+
+bool ScaledFontFreeType::MayUseBitmaps() {
+ return !FT_IS_SCALABLE(mFace->GetFace());
+}
+
+cairo_font_face_t* ScaledFontFreeType::CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_OFF);
+
+ int loadFlags = FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
+ if (mFace->GetFace()->face_flags & FT_FACE_FLAG_TRICKY) {
+ loadFlags &= ~FT_LOAD_NO_AUTOHINT;
+ }
+
+ unsigned int synthFlags = 0;
+ if (mApplySyntheticBold) {
+ synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD;
+ }
+
+ return cairo_ft_font_face_create_for_ft_face(mFace->GetFace(), loadFlags,
+ synthFlags, mFace.get());
+}
+
+bool ScaledFontFreeType::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ std::vector<FontVariation> variations;
+ if (HasVariationSettings()) {
+ UnscaledFontFreeType::GetVariationSettingsFromFace(&variations,
+ mFace->GetFace());
+ }
+
+ InstanceData instance(this);
+ aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance),
+ variations.data(), variations.size(), aBaton);
+ return true;
+}
+
+bool ScaledFontFreeType::GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) {
+ wr::FontInstanceOptions options;
+ options.render_mode = wr::FontRenderMode::Alpha;
+ options.flags = wr::FontInstanceFlags{0};
+ if (UseSubpixelPosition()) {
+ options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION;
+ }
+ options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
+ options.bg_color = wr::ToColorU(DeviceColor());
+ options.synthetic_italics =
+ wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
+
+ if (mApplySyntheticBold) {
+ options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
+ }
+
+ wr::FontInstancePlatformOptions platformOptions;
+ platformOptions.lcd_filter = wr::FontLCDFilter::None;
+ platformOptions.hinting = wr::FontHinting::None;
+
+ *aOutOptions = Some(options);
+ *aOutPlatformOptions = Some(platformOptions);
+
+ if (HasVariationSettings()) {
+ UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations,
+ mFace->GetFace());
+ }
+
+ return true;
+}
+
+ScaledFontFreeType::InstanceData::InstanceData(
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions)
+ : mApplySyntheticBold(false) {
+ if (aOptions) {
+ if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
+ mApplySyntheticBold = true;
+ }
+ }
+}
+
+bool ScaledFontFreeType::HasVariationSettings() {
+ // Check if the FT face has been cloned.
+ return mFace &&
+ mFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS &&
+ mFace !=
+ static_cast<UnscaledFontFreeType*>(mUnscaledFont.get())->GetFace();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ScaledFontFreeType.h b/gfx/2d/ScaledFontFreeType.h
new file mode 100644
index 0000000000..5a76e8e6c0
--- /dev/null
+++ b/gfx/2d/ScaledFontFreeType.h
@@ -0,0 +1,74 @@
+/* -*- 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_SCALEDFONTCAIRO_H_
+#define MOZILLA_GFX_SCALEDFONTCAIRO_H_
+
+#include "ScaledFontBase.h"
+
+#include <cairo-ft.h>
+
+namespace mozilla {
+namespace gfx {
+
+class UnscaledFontFreeType;
+
+class ScaledFontFreeType : public ScaledFontBase {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontFreeType, override)
+
+ ScaledFontFreeType(RefPtr<SharedFTFace>&& aFace,
+ const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ bool aApplySyntheticBold = false);
+
+ FontType GetType() const override { return FontType::FREETYPE; }
+
+ SkTypeface* CreateSkTypeface() override;
+ void SetupSkFontDrawOptions(SkFont& aFont) override;
+
+ AntialiasMode GetDefaultAAMode() override { return AntialiasMode::GRAY; }
+
+ bool UseSubpixelPosition() const override;
+
+ bool CanSerialize() override { return true; }
+
+ bool MayUseBitmaps() override;
+
+ bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
+
+ bool GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) override;
+
+ bool HasVariationSettings() override;
+
+ protected:
+ cairo_font_face_t* CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) override;
+
+ private:
+ friend UnscaledFontFreeType;
+
+ RefPtr<SharedFTFace> mFace;
+
+ bool mApplySyntheticBold;
+
+ struct InstanceData {
+ explicit InstanceData(ScaledFontFreeType* aScaledFont)
+ : mApplySyntheticBold(aScaledFont->mApplySyntheticBold) {}
+
+ InstanceData(const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions);
+
+ bool mApplySyntheticBold;
+ };
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTCAIRO_H_ */
diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp
new file mode 100644
index 0000000000..042c0a19eb
--- /dev/null
+++ b/gfx/2d/ScaledFontMac.cpp
@@ -0,0 +1,796 @@
+/* -*- 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 "ScaledFontMac.h"
+#include "UnscaledFontMac.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "nsCocoaFeatures.h"
+#include "PathSkia.h"
+#include "skia/include/core/SkPaint.h"
+#include "skia/include/core/SkPath.h"
+#include "skia/include/ports/SkTypeface_mac.h"
+#include <vector>
+#include <dlfcn.h>
+#ifdef MOZ_WIDGET_UIKIT
+# include <CoreFoundation/CoreFoundation.h>
+#endif
+#include "nsCocoaFeatures.h"
+#include "mozilla/gfx/Logging.h"
+
+#ifdef MOZ_WIDGET_COCOA
+// prototype for private API
+extern "C" {
+CGPathRef CGFontGetGlyphPath(CGFontRef fontRef,
+ CGAffineTransform* textTransform, int unknown,
+ CGGlyph glyph);
+};
+#endif
+
+#include "cairo-quartz.h"
+
+namespace mozilla {
+namespace gfx {
+
+// Simple helper class to automatically release a CFObject when it goes out
+// of scope.
+template <class T>
+class AutoRelease final {
+ public:
+ explicit AutoRelease(T aObject) : mObject(aObject) {}
+
+ ~AutoRelease() {
+ if (mObject) {
+ CFRelease(mObject);
+ }
+ }
+
+ AutoRelease<T>& operator=(const T& aObject) {
+ if (aObject != mObject) {
+ if (mObject) {
+ CFRelease(mObject);
+ }
+ mObject = aObject;
+ }
+ return *this;
+ }
+
+ operator T() { return mObject; }
+
+ T forget() {
+ T obj = mObject;
+ mObject = nullptr;
+ return obj;
+ }
+
+ private:
+ T mObject;
+};
+
+// Helper to create a CTFont from a CGFont, copying any variations that were
+// set on the CGFont, and applying attributes from (optional) aFontDesc.
+CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize,
+ bool aInstalledFont,
+ CTFontDescriptorRef aFontDesc) {
+ // New implementation (see bug 1856035) for macOS 13+.
+ if (nsCocoaFeatures::OnVenturaOrLater()) {
+ // Create CTFont, applying any descriptor that was passed (used by
+ // gfxCoreTextShaper to set features).
+ AutoRelease<CTFontRef> ctFont(
+ CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc));
+ AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
+ if (vars) {
+ // Create an attribute dictionary containing the variations.
+ AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate(
+ nullptr, (const void**)&kCTFontVariationAttribute,
+ (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ // Get the original descriptor from the CTFont, then add the variations
+ // attribute to it.
+ AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont));
+ desc = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);
+ // Return a copy of the font that has the variations added.
+ return CTFontCreateCopyWithAttributes(ctFont, 0.0, nullptr, desc);
+ }
+ // No variations to set, just return the default CTFont.
+ return ctFont.forget();
+ }
+
+ // Older implementation used up to macOS 12.
+ CTFontRef ctFont;
+ if (aInstalledFont) {
+ AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
+ if (vars) {
+ AutoRelease<CFDictionaryRef> varAttr(CFDictionaryCreate(
+ nullptr, (const void**)&kCTFontVariationAttribute,
+ (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ AutoRelease<CTFontDescriptorRef> varDesc(
+ aFontDesc
+ ? ::CTFontDescriptorCreateCopyWithAttributes(aFontDesc, varAttr)
+ : ::CTFontDescriptorCreateWithAttributes(varAttr));
+
+ ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, varDesc);
+ } else {
+ ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
+ }
+ } else {
+ ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
+ }
+ return ctFont;
+}
+
+ScaledFontMac::ScaledFontMac(CGFontRef aFont,
+ const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize, bool aOwnsFont,
+ const DeviceColor& aFontSmoothingBackgroundColor,
+ bool aUseFontSmoothing, bool aApplySyntheticBold,
+ bool aHasColorGlyphs)
+ : ScaledFontBase(aUnscaledFont, aSize),
+ mFont(aFont),
+ mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor),
+ mUseFontSmoothing(aUseFontSmoothing),
+ mApplySyntheticBold(aApplySyntheticBold),
+ mHasColorGlyphs(aHasColorGlyphs) {
+ if (!aOwnsFont) {
+ // XXX: should we be taking a reference
+ CGFontRetain(aFont);
+ }
+
+ auto unscaledMac = static_cast<UnscaledFontMac*>(aUnscaledFont.get());
+ bool dataFont = unscaledMac->IsDataFont();
+ mCTFont = CreateCTFontFromCGFontWithVariations(aFont, aSize, !dataFont);
+}
+
+ScaledFontMac::ScaledFontMac(CTFontRef aFont,
+ const RefPtr<UnscaledFont>& aUnscaledFont,
+ const DeviceColor& aFontSmoothingBackgroundColor,
+ bool aUseFontSmoothing, bool aApplySyntheticBold,
+ bool aHasColorGlyphs)
+ : ScaledFontBase(aUnscaledFont, CTFontGetSize(aFont)),
+ mCTFont(aFont),
+ mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor),
+ mUseFontSmoothing(aUseFontSmoothing),
+ mApplySyntheticBold(aApplySyntheticBold),
+ mHasColorGlyphs(aHasColorGlyphs) {
+ mFont = CTFontCopyGraphicsFont(aFont, nullptr);
+
+ CFRetain(mCTFont);
+}
+
+ScaledFontMac::~ScaledFontMac() {
+ CFRelease(mCTFont);
+ CGFontRelease(mFont);
+}
+
+SkTypeface* ScaledFontMac::CreateSkTypeface() {
+ return SkMakeTypefaceFromCTFont(mCTFont).release();
+}
+
+void ScaledFontMac::SetupSkFontDrawOptions(SkFont& aFont) {
+ aFont.setSubpixel(true);
+
+ // Normally, Skia enables LCD FontSmoothing which creates thicker fonts
+ // and also enables subpixel AA. CoreGraphics without font smoothing
+ // explicitly creates thinner fonts and grayscale AA.
+ // CoreGraphics doesn't support a configuration that produces thicker
+ // fonts with grayscale AA as LCD Font Smoothing enables or disables
+ // both. However, Skia supports it by enabling font smoothing (producing
+ // subpixel AA) and converts it to grayscale AA. Since Skia doesn't
+ // support subpixel AA on transparent backgrounds, we still want font
+ // smoothing for the thicker fonts, even if it is grayscale AA.
+ //
+ // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale),
+ // we want to have grayscale AA with no smoothing at all. This means
+ // disabling the LCD font smoothing behaviour.
+ // To accomplish this we have to explicitly disable hinting,
+ // and disable LCDRenderText.
+ if (aFont.getEdging() == SkFont::Edging::kAntiAlias && !mUseFontSmoothing) {
+ aFont.setHinting(SkFontHinting::kNone);
+ }
+}
+
+// private API here are the public options on OS X
+// CTFontCreatePathForGlyph
+// ATSUGlyphGetCubicPaths
+// we've used this in cairo sucessfully for some time.
+// Note: cairo dlsyms it. We could do that but maybe it's
+// safe just to use?
+
+already_AddRefed<Path> ScaledFontMac::GetPathForGlyphs(
+ const GlyphBuffer& aBuffer, const DrawTarget* aTarget) {
+ return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
+}
+
+static uint32_t CalcTableChecksum(const uint32_t* tableStart, uint32_t length,
+ bool skipChecksumAdjust = false) {
+ uint32_t sum = 0L;
+ const uint32_t* table = tableStart;
+ const uint32_t* end = table + length / sizeof(uint32_t);
+ while (table < end) {
+ if (skipChecksumAdjust && (table - tableStart) == 2) {
+ table++;
+ } else {
+ sum += CFSwapInt32BigToHost(*table++);
+ }
+ }
+
+ // The length is not 4-byte aligned, but we still must process the remaining
+ // bytes.
+ if (length & 3) {
+ // Pad with zero before adding to the checksum.
+ uint32_t last = 0;
+ memcpy(&last, end, length & 3);
+ sum += CFSwapInt32BigToHost(last);
+ }
+
+ return sum;
+}
+
+struct TableRecord {
+ uint32_t tag;
+ uint32_t checkSum;
+ uint32_t offset;
+ uint32_t length;
+ CFDataRef data;
+};
+
+static int maxPow2LessThanEqual(int a) {
+ int x = 1;
+ int shift = 0;
+ while ((x << (shift + 1)) <= a) {
+ shift++;
+ }
+ return shift;
+}
+
+struct writeBuf final {
+ explicit writeBuf(int size) {
+ this->data = new unsigned char[size];
+ this->offset = 0;
+ }
+ ~writeBuf() { delete[] this->data; }
+
+ template <class T>
+ void writeElement(T a) {
+ *reinterpret_cast<T*>(&this->data[this->offset]) = a;
+ this->offset += sizeof(T);
+ }
+
+ void writeMem(const void* data, unsigned long length) {
+ memcpy(&this->data[this->offset], data, length);
+ this->offset += length;
+ }
+
+ void align() {
+ while (this->offset & 3) {
+ this->data[this->offset] = 0;
+ this->offset++;
+ }
+ }
+
+ unsigned char* data;
+ int offset;
+};
+
+bool UnscaledFontMac::GetFontFileData(FontFileDataOutput aDataCallback,
+ void* aBaton) {
+ // We'll reconstruct a TTF font from the tables we can get from the CGFont
+ CFArrayRef tags = CGFontCopyTableTags(mFont);
+ CFIndex count = CFArrayGetCount(tags);
+
+ TableRecord* records = new TableRecord[count];
+ uint32_t offset = 0;
+ offset += sizeof(uint32_t) * 3;
+ offset += sizeof(uint32_t) * 4 * count;
+ bool CFF = false;
+ for (CFIndex i = 0; i < count; i++) {
+ uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, i);
+ if (tag == 0x43464620) { // 'CFF '
+ CFF = true;
+ }
+ CFDataRef data = CGFontCopyTableForTag(mFont, tag);
+ // Bug 1602391 suggests CGFontCopyTableForTag can fail, even though we just
+ // got the tag from the font via CGFontCopyTableTags above. If we can catch
+ // this (e.g. in fuzz-testing) it'd be good to understand when it happens,
+ // but in any case we'll handle it safely below by treating the table as
+ // zero-length.
+ MOZ_ASSERT(data, "failed to get font table data");
+ records[i].tag = tag;
+ records[i].offset = offset;
+ records[i].data = data;
+ if (data) {
+ records[i].length = CFDataGetLength(data);
+ bool skipChecksumAdjust = (tag == 0x68656164); // 'head'
+ records[i].checkSum = CalcTableChecksum(
+ reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(data)),
+ records[i].length, skipChecksumAdjust);
+ offset += records[i].length;
+ // 32 bit align the tables
+ offset = (offset + 3) & ~3;
+ } else {
+ records[i].length = 0;
+ records[i].checkSum = 0;
+ }
+ }
+ CFRelease(tags);
+
+ struct writeBuf buf(offset);
+ // write header/offset table
+ if (CFF) {
+ buf.writeElement(CFSwapInt32HostToBig(0x4f54544f));
+ } else {
+ buf.writeElement(CFSwapInt32HostToBig(0x00010000));
+ }
+ buf.writeElement(CFSwapInt16HostToBig(count));
+ int maxPow2Count = maxPow2LessThanEqual(count);
+ buf.writeElement(CFSwapInt16HostToBig((1 << maxPow2Count) * 16));
+ buf.writeElement(CFSwapInt16HostToBig(maxPow2Count));
+ buf.writeElement(CFSwapInt16HostToBig((count - (1 << maxPow2Count)) * 16));
+
+ // write table record entries
+ for (CFIndex i = 0; i < count; i++) {
+ buf.writeElement(CFSwapInt32HostToBig(records[i].tag));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].checkSum));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].offset));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].length));
+ }
+
+ // write tables
+ int checkSumAdjustmentOffset = 0;
+ for (CFIndex i = 0; i < count; i++) {
+ if (records[i].tag == 0x68656164) {
+ checkSumAdjustmentOffset = buf.offset + 2 * 4;
+ }
+ if (records[i].data) {
+ buf.writeMem(CFDataGetBytePtr(records[i].data), records[i].length);
+ buf.align();
+ CFRelease(records[i].data);
+ }
+ }
+ delete[] records;
+
+ // clear the checksumAdjust field before checksumming the whole font
+ memset(&buf.data[checkSumAdjustmentOffset], 0, sizeof(uint32_t));
+ uint32_t fontChecksum = CFSwapInt32HostToBig(
+ 0xb1b0afba -
+ CalcTableChecksum(reinterpret_cast<const uint32_t*>(buf.data), offset));
+ // set checkSumAdjust to the computed checksum
+ memcpy(&buf.data[checkSumAdjustmentOffset], &fontChecksum,
+ sizeof(fontChecksum));
+
+ // we always use an index of 0
+ aDataCallback(buf.data, buf.offset, 0, aBaton);
+
+ return true;
+}
+
+bool UnscaledFontMac::GetFontDescriptor(FontDescriptorOutput aCb,
+ void* aBaton) {
+ if (mIsDataFont) {
+ return false;
+ }
+
+ AutoRelease<CFStringRef> psname(CGFontCopyPostScriptName(mFont));
+ if (!psname) {
+ return false;
+ }
+
+ char buf[256];
+ const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8);
+ if (!cstr) {
+ if (!CFStringGetCString(psname, buf, sizeof(buf), kCFStringEncodingUTF8)) {
+ return false;
+ }
+ cstr = buf;
+ }
+
+ aCb(reinterpret_cast<const uint8_t*>(cstr), strlen(cstr), 0, aBaton);
+ return true;
+}
+
+static void CollectVariationsFromDictionary(const void* aKey,
+ const void* aValue,
+ void* aContext) {
+ auto keyPtr = static_cast<const CFTypeRef>(aKey);
+ auto valuePtr = static_cast<const CFTypeRef>(aValue);
+ auto outVariations = static_cast<std::vector<FontVariation>*>(aContext);
+ if (CFGetTypeID(keyPtr) == CFNumberGetTypeID() &&
+ CFGetTypeID(valuePtr) == CFNumberGetTypeID()) {
+ uint64_t t;
+ double v;
+ if (CFNumberGetValue(static_cast<CFNumberRef>(keyPtr), kCFNumberSInt64Type,
+ &t) &&
+ CFNumberGetValue(static_cast<CFNumberRef>(valuePtr),
+ kCFNumberDoubleType, &v)) {
+ outVariations->push_back(FontVariation{uint32_t(t), float(v)});
+ }
+ }
+}
+
+static bool GetVariationsForCTFont(CTFontRef aCTFont,
+ std::vector<FontVariation>* aOutVariations) {
+ if (!aCTFont) {
+ return true;
+ }
+ AutoRelease<CFDictionaryRef> dict(CTFontCopyVariation(aCTFont));
+ CFIndex count = dict ? CFDictionaryGetCount(dict) : 0;
+ if (count > 0) {
+ aOutVariations->reserve(count);
+ CFDictionaryApplyFunction(dict, CollectVariationsFromDictionary,
+ aOutVariations);
+ }
+ return true;
+}
+
+bool ScaledFontMac::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ // Collect any variation settings that were incorporated into the CTFont.
+ std::vector<FontVariation> variations;
+ if (!GetVariationsForCTFont(mCTFont, &variations)) {
+ return false;
+ }
+
+ InstanceData instance(this);
+ aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance),
+ variations.data(), variations.size(), aBaton);
+ return true;
+}
+
+bool ScaledFontMac::GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) {
+ GetVariationsForCTFont(mCTFont, aOutVariations);
+
+ wr::FontInstanceOptions options;
+ options.render_mode = wr::FontRenderMode::Subpixel;
+ options.flags = wr::FontInstanceFlags::SUBPIXEL_POSITION;
+ if (mUseFontSmoothing) {
+ options.flags |= wr::FontInstanceFlags::FONT_SMOOTHING;
+ }
+ if (mApplySyntheticBold) {
+ options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
+ }
+ if (mHasColorGlyphs) {
+ options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
+ }
+ options.bg_color = wr::ToColorU(mFontSmoothingBackgroundColor);
+ options.synthetic_italics =
+ wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
+ *aOutOptions = Some(options);
+ return true;
+}
+
+ScaledFontMac::InstanceData::InstanceData(
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions)
+ : mUseFontSmoothing(true),
+ mApplySyntheticBold(false),
+ mHasColorGlyphs(false) {
+ if (aOptions) {
+ if (!(aOptions->flags & wr::FontInstanceFlags::FONT_SMOOTHING)) {
+ mUseFontSmoothing = false;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
+ mApplySyntheticBold = true;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
+ mHasColorGlyphs = true;
+ }
+ if (aOptions->bg_color.a != 0) {
+ mFontSmoothingBackgroundColor =
+ DeviceColor::FromU8(aOptions->bg_color.r, aOptions->bg_color.g,
+ aOptions->bg_color.b, aOptions->bg_color.a);
+ }
+ }
+}
+
+static CFDictionaryRef CreateVariationDictionaryOrNull(
+ CGFontRef aCGFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
+ uint32_t aVariationCount, const FontVariation* aVariations) {
+ if (!aCGAxesCache) {
+ aCGAxesCache = CGFontCopyVariationAxes(aCGFont);
+ if (!aCGAxesCache) {
+ return nullptr;
+ }
+ }
+ if (!aCTAxesCache) {
+ AutoRelease<CTFontRef> ctFont(
+ CTFontCreateWithGraphicsFont(aCGFont, 0, nullptr, nullptr));
+ aCTAxesCache = CTFontCopyVariationAxes(ctFont);
+ if (!aCTAxesCache) {
+ return nullptr;
+ }
+ }
+
+ CFIndex axisCount = CFArrayGetCount(aCTAxesCache);
+ if (CFArrayGetCount(aCGAxesCache) != axisCount) {
+ return nullptr;
+ }
+
+ AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ // Number of variation settings passed in the aVariations parameter.
+ // This will typically be a very low value, so we just linear-search them.
+ bool allDefaultValues = true;
+
+ for (CFIndex i = 0; i < axisCount; ++i) {
+ // We sanity-check the axis info found in the CTFont, and bail out
+ // (returning null) if it doesn't have the expected types.
+ CFTypeRef axisInfo = CFArrayGetValueAtIndex(aCTAxesCache, i);
+ if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
+ return nullptr;
+ }
+ CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
+
+ CFTypeRef axisTag =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
+ if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ int64_t tagLong;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
+ kCFNumberSInt64Type, &tagLong)) {
+ return nullptr;
+ }
+
+ axisInfo = CFArrayGetValueAtIndex(aCGAxesCache, i);
+ if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
+ return nullptr;
+ }
+ CFTypeRef axisName = CFDictionaryGetValue(
+ static_cast<CFDictionaryRef>(axisInfo), kCGFontVariationAxisName);
+ if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) {
+ return nullptr;
+ }
+
+ // Clamp axis values to the supported range.
+ CFTypeRef min =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
+ CFTypeRef max =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
+ CFTypeRef def =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
+ if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
+ CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
+ CFGetTypeID(def) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ double minDouble;
+ double maxDouble;
+ double defDouble;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
+ &minDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
+ &maxDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
+ &defDouble)) {
+ return nullptr;
+ }
+
+ double value = defDouble;
+ for (uint32_t j = 0; j < aVariationCount; ++j) {
+ if (aVariations[j].mTag == tagLong) {
+ value = std::min(std::max<double>(aVariations[j].mValue, minDouble),
+ maxDouble);
+ if (value != defDouble) {
+ allDefaultValues = false;
+ }
+ break;
+ }
+ }
+ AutoRelease<CFNumberRef> valueNumber(
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
+ CFDictionaryAddValue(dict, axisName, valueNumber);
+ }
+
+ if (allDefaultValues) {
+ // We didn't actually set any non-default values, so throw away the
+ // variations dictionary and just use the default rendering.
+ return nullptr;
+ }
+
+ return dict.forget();
+}
+
+static CFDictionaryRef CreateVariationTagDictionaryOrNull(
+ CTFontRef aCTFont, uint32_t aVariationCount,
+ const FontVariation* aVariations) {
+ AutoRelease<CFArrayRef> axes(CTFontCopyVariationAxes(aCTFont));
+ CFIndex axisCount = CFArrayGetCount(axes);
+
+ AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ // Number of variation settings passed in the aVariations parameter.
+ // This will typically be a very low value, so we just linear-search them.
+ bool allDefaultValues = true;
+
+ for (CFIndex i = 0; i < axisCount; ++i) {
+ // We sanity-check the axis info found in the CTFont, and bail out
+ // (returning null) if it doesn't have the expected types.
+ CFTypeRef axisInfo = CFArrayGetValueAtIndex(axes, i);
+ if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
+ return nullptr;
+ }
+ CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
+
+ CFTypeRef axisTag =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
+ if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ int64_t tagLong;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
+ kCFNumberSInt64Type, &tagLong)) {
+ return nullptr;
+ }
+
+ // Clamp axis values to the supported range.
+ CFTypeRef min =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
+ CFTypeRef max =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
+ CFTypeRef def =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
+ if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
+ CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
+ CFGetTypeID(def) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ double minDouble;
+ double maxDouble;
+ double defDouble;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
+ &minDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
+ &maxDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
+ &defDouble)) {
+ return nullptr;
+ }
+
+ double value = defDouble;
+ for (uint32_t j = 0; j < aVariationCount; ++j) {
+ if (aVariations[j].mTag == tagLong) {
+ value = std::min(std::max<double>(aVariations[j].mValue, minDouble),
+ maxDouble);
+ if (value != defDouble) {
+ allDefaultValues = false;
+ }
+ break;
+ }
+ }
+ AutoRelease<CFNumberRef> valueNumber(
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
+ CFDictionaryAddValue(dict, axisTag, valueNumber);
+ }
+
+ if (allDefaultValues) {
+ // We didn't actually set any non-default values, so throw away the
+ // variations dictionary and just use the default rendering.
+ return nullptr;
+ }
+
+ return dict.forget();
+}
+
+/* static */
+CGFontRef UnscaledFontMac::CreateCGFontWithVariations(
+ CGFontRef aFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
+ uint32_t aVariationCount, const FontVariation* aVariations) {
+ if (!aVariationCount) {
+ return nullptr;
+ }
+ MOZ_ASSERT(aVariations);
+
+ AutoRelease<CFDictionaryRef> varDict(CreateVariationDictionaryOrNull(
+ aFont, aCGAxesCache, aCTAxesCache, aVariationCount, aVariations));
+ if (!varDict) {
+ return nullptr;
+ }
+
+ return CGFontCreateCopyWithVariations(aFont, varDict);
+}
+
+already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations)
+
+{
+ if (aInstanceDataLength < sizeof(ScaledFontMac::InstanceData)) {
+ gfxWarning() << "Mac scaled font instance data is truncated.";
+ return nullptr;
+ }
+ const ScaledFontMac::InstanceData& instanceData =
+ *reinterpret_cast<const ScaledFontMac::InstanceData*>(aInstanceData);
+ RefPtr<ScaledFontMac> scaledFont;
+ if (mFontDesc) {
+ AutoRelease<CTFontRef> font(
+ CTFontCreateWithFontDescriptor(mFontDesc, aGlyphSize, nullptr));
+ if (aNumVariations > 0) {
+ AutoRelease<CFDictionaryRef> varDict(CreateVariationTagDictionaryOrNull(
+ font, aNumVariations, aVariations));
+ if (varDict) {
+ CFDictionaryRef varAttr = CFDictionaryCreate(
+ nullptr, (const void**)&kCTFontVariationAttribute,
+ (const void**)&varDict, 1, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ AutoRelease<CTFontDescriptorRef> fontDesc(
+ CTFontDescriptorCreateCopyWithAttributes(mFontDesc, varAttr));
+ if (!fontDesc) {
+ return nullptr;
+ }
+ font = CTFontCreateWithFontDescriptor(fontDesc, aGlyphSize, nullptr);
+ }
+ }
+ scaledFont = new ScaledFontMac(
+ font, this, instanceData.mFontSmoothingBackgroundColor,
+ instanceData.mUseFontSmoothing, instanceData.mApplySyntheticBold,
+ instanceData.mHasColorGlyphs);
+ } else {
+ CGFontRef fontRef = mFont;
+ if (aNumVariations > 0) {
+ CGFontRef varFont = CreateCGFontWithVariations(
+ mFont, mCGAxesCache, mCTAxesCache, aNumVariations, aVariations);
+ if (varFont) {
+ fontRef = varFont;
+ }
+ }
+
+ scaledFont = new ScaledFontMac(fontRef, this, aGlyphSize, fontRef != mFont,
+ instanceData.mFontSmoothingBackgroundColor,
+ instanceData.mUseFontSmoothing,
+ instanceData.mApplySyntheticBold,
+ instanceData.mHasColorGlyphs);
+ }
+ return scaledFont.forget();
+}
+
+already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ ScaledFontMac::InstanceData instanceData(aOptions, aPlatformOptions);
+ return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData),
+ sizeof(instanceData), aVariations, aNumVariations);
+}
+
+cairo_font_face_t* ScaledFontMac::CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ MOZ_ASSERT(mFont);
+ return cairo_quartz_font_face_create_for_cgfont(mFont);
+}
+
+already_AddRefed<UnscaledFont> UnscaledFontMac::CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
+ if (aDataLength == 0) {
+ gfxWarning() << "Mac font descriptor is truncated.";
+ return nullptr;
+ }
+ CFStringRef name =
+ CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aData,
+ aDataLength, kCFStringEncodingUTF8, false);
+ if (!name) {
+ return nullptr;
+ }
+ CGFontRef font = CGFontCreateWithFontName(name);
+ CFRelease(name);
+ if (!font) {
+ return nullptr;
+ }
+ RefPtr<UnscaledFont> unscaledFont = new UnscaledFontMac(font);
+ CFRelease(font);
+ return unscaledFont.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ScaledFontMac.h b/gfx/2d/ScaledFontMac.h
new file mode 100644
index 0000000000..88c6b553db
--- /dev/null
+++ b/gfx/2d/ScaledFontMac.h
@@ -0,0 +1,102 @@
+/* -*- 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_SCALEDFONTMAC_H_
+#define MOZILLA_GFX_SCALEDFONTMAC_H_
+
+#ifdef MOZ_WIDGET_COCOA
+# include <ApplicationServices/ApplicationServices.h>
+#else
+# include <CoreGraphics/CoreGraphics.h>
+# include <CoreText/CoreText.h>
+#endif
+
+#include "2D.h"
+
+#include "ScaledFontBase.h"
+
+namespace mozilla {
+namespace gfx {
+
+// Utility to create a CTFont from a CGFont, copying any variations that were
+// set on the original CGFont, and applying additional attributes from aDesc
+// (which may be NULL).
+// Exposed here because it is also used by gfxMacFont and gfxCoreTextShaper.
+CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize,
+ bool aInstalledFont,
+ CTFontDescriptorRef aFontDesc = nullptr);
+
+class UnscaledFontMac;
+
+class ScaledFontMac : public ScaledFontBase {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontMac, override)
+ ScaledFontMac(CGFontRef aFont, const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize,
+ bool aOwnsFont = false,
+ const DeviceColor& aFontSmoothingBackgroundColor = DeviceColor(),
+ bool aUseFontSmoothing = true, bool aApplySyntheticBold = false,
+ bool aHasColorGlyphs = false);
+ ScaledFontMac(CTFontRef aFont, const RefPtr<UnscaledFont>& aUnscaledFont,
+ const DeviceColor& aFontSmoothingBackgroundColor = DeviceColor(),
+ bool aUseFontSmoothing = true, bool aApplySyntheticBold = false,
+ bool aHasColorGlyphs = false);
+ ~ScaledFontMac();
+
+ FontType GetType() const override { return FontType::MAC; }
+ SkTypeface* CreateSkTypeface() override;
+ void SetupSkFontDrawOptions(SkFont& aFont) override;
+ already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer& aBuffer,
+ const DrawTarget* aTarget) override;
+
+ bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
+
+ bool GetWRFontInstanceOptions(Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) override;
+
+ bool CanSerialize() override { return true; }
+
+ bool MayUseBitmaps() override { return mHasColorGlyphs; }
+
+ bool UseSubpixelPosition() const override { return true; }
+
+ DeviceColor FontSmoothingBackgroundColor() { return mFontSmoothingBackgroundColor; }
+
+ cairo_font_face_t* CreateCairoFontFace(cairo_font_options_t* aFontOptions) override;
+
+ private:
+ friend class DrawTargetSkia;
+ friend class UnscaledFontMac;
+
+ CGFontRef mFont;
+ CTFontRef mCTFont; // only created if CTFontDrawGlyphs is available, otherwise null
+
+ DeviceColor mFontSmoothingBackgroundColor;
+ bool mUseFontSmoothing;
+ bool mApplySyntheticBold;
+ bool mHasColorGlyphs;
+
+ struct InstanceData {
+ explicit InstanceData(ScaledFontMac* aScaledFont)
+ : mFontSmoothingBackgroundColor(aScaledFont->mFontSmoothingBackgroundColor),
+ mUseFontSmoothing(aScaledFont->mUseFontSmoothing),
+ mApplySyntheticBold(aScaledFont->mApplySyntheticBold),
+ mHasColorGlyphs(aScaledFont->mHasColorGlyphs) {}
+
+ InstanceData(const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions);
+
+ DeviceColor mFontSmoothingBackgroundColor;
+ bool mUseFontSmoothing;
+ bool mApplySyntheticBold;
+ bool mHasColorGlyphs;
+ };
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTMAC_H_ */
diff --git a/gfx/2d/ScaledFontWin.cpp b/gfx/2d/ScaledFontWin.cpp
new file mode 100644
index 0000000000..5a90e5ad01
--- /dev/null
+++ b/gfx/2d/ScaledFontWin.cpp
@@ -0,0 +1,121 @@
+/* -*- 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 "ScaledFontWin.h"
+#include "UnscaledFontGDI.h"
+
+#include "AutoHelpersWin.h"
+#include "Logging.h"
+#include "nsString.h"
+
+#include "skia/include/ports/SkTypeface_win.h"
+
+#include "cairo-win32.h"
+
+#include "HelpersWinFonts.h"
+
+namespace mozilla {
+namespace gfx {
+
+ScaledFontWin::ScaledFontWin(const LOGFONT* aFont,
+ const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize)
+ : ScaledFontBase(aUnscaledFont, aSize), mLogFont(*aFont) {}
+
+bool UnscaledFontGDI::GetFontFileData(FontFileDataOutput aDataCallback,
+ void* aBaton) {
+ AutoDC dc;
+ AutoSelectFont font(dc.GetDC(), &mLogFont);
+
+ // Check for a font collection first.
+ uint32_t table = 0x66637474; // 'ttcf'
+ uint32_t tableSize = ::GetFontData(dc.GetDC(), table, 0, nullptr, 0);
+ if (tableSize == GDI_ERROR) {
+ // Try as if just a single font.
+ table = 0;
+ tableSize = ::GetFontData(dc.GetDC(), table, 0, nullptr, 0);
+ if (tableSize == GDI_ERROR) {
+ return false;
+ }
+ }
+
+ UniquePtr<uint8_t[]> fontData(new uint8_t[tableSize]);
+
+ uint32_t sizeGot =
+ ::GetFontData(dc.GetDC(), table, 0, fontData.get(), tableSize);
+ if (sizeGot != tableSize) {
+ return false;
+ }
+
+ aDataCallback(fontData.get(), tableSize, 0, aBaton);
+ return true;
+}
+
+bool ScaledFontWin::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ aCb(reinterpret_cast<uint8_t*>(&mLogFont), sizeof(mLogFont), nullptr, 0,
+ aBaton);
+ return true;
+}
+
+bool UnscaledFontGDI::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ aCb(reinterpret_cast<uint8_t*>(&mLogFont), sizeof(mLogFont), aBaton);
+ return true;
+}
+
+bool UnscaledFontGDI::GetFontDescriptor(FontDescriptorOutput aCb,
+ void* aBaton) {
+ // Because all the callers of this function are preparing a recorded
+ // event to be played back in another process, it's not helpful to ever
+ // return a font descriptor, since it isn't meaningful in another
+ // process. Those callers will always need to send full font data, and
+ // returning false here will ensure that that happens.
+ return false;
+}
+
+already_AddRefed<UnscaledFont> UnscaledFontGDI::CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
+ if (aDataLength < sizeof(LOGFONT)) {
+ gfxWarning() << "GDI font descriptor is truncated.";
+ return nullptr;
+ }
+
+ const LOGFONT* logFont = reinterpret_cast<const LOGFONT*>(aData);
+ RefPtr<UnscaledFont> unscaledFont = new UnscaledFontGDI(*logFont);
+ return unscaledFont.forget();
+}
+
+already_AddRefed<ScaledFont> UnscaledFontGDI::CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) {
+ if (aInstanceDataLength < sizeof(LOGFONT)) {
+ gfxWarning() << "GDI unscaled font instance data is truncated.";
+ return nullptr;
+ }
+ return MakeAndAddRef<ScaledFontWin>(
+ reinterpret_cast<const LOGFONT*>(aInstanceData), this, aGlyphSize);
+}
+
+AntialiasMode ScaledFontWin::GetDefaultAAMode() {
+ return GetSystemDefaultAAMode();
+}
+
+SkTypeface* ScaledFontWin::CreateSkTypeface() {
+ return SkCreateTypefaceFromLOGFONT(mLogFont);
+}
+
+cairo_font_face_t* ScaledFontWin::CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ if (mLogFont.lfFaceName[0] == 0) {
+ return nullptr;
+ }
+ return cairo_win32_font_face_create_for_logfontw(&mLogFont);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ScaledFontWin.h b/gfx/2d/ScaledFontWin.h
new file mode 100644
index 0000000000..45ca23a3bb
--- /dev/null
+++ b/gfx/2d/ScaledFontWin.h
@@ -0,0 +1,46 @@
+/* -*- 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_SCALEDFONTWIN_H_
+#define MOZILLA_GFX_SCALEDFONTWIN_H_
+
+#include "ScaledFontBase.h"
+#include <windows.h>
+
+namespace mozilla {
+namespace gfx {
+
+class ScaledFontWin : public ScaledFontBase {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontWin, override)
+ ScaledFontWin(const LOGFONT* aFont, const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize);
+
+ FontType GetType() const override { return FontType::GDI; }
+
+ bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
+
+ AntialiasMode GetDefaultAAMode() override;
+
+ SkTypeface* CreateSkTypeface() override;
+
+ bool MayUseBitmaps() override { return true; }
+
+ bool UseSubpixelPosition() const override { return false; }
+
+ protected:
+ cairo_font_face_t* CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) override;
+
+ private:
+ friend class DrawTargetSkia;
+ LOGFONT mLogFont;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTWIN_H_ */
diff --git a/gfx/2d/ShadersD2D.fx b/gfx/2d/ShadersD2D.fx
new file mode 100644
index 0000000000..b87ee18a91
--- /dev/null
+++ b/gfx/2d/ShadersD2D.fx
@@ -0,0 +1,824 @@
+// We store vertex coordinates and the quad shape in a constant buffer, this is
+// easy to update and allows us to use a single call to set the x, y, w, h of
+// the quad.
+// The QuadDesc and TexCoords both work as follows:
+// The x component is the quad left point, the y component is the top point
+// the z component is the width, and the w component is the height. The quad
+// are specified in viewport coordinates, i.e. { -1.0f, 1.0f, 2.0f, -2.0f }
+// would cover the entire viewport (which runs from <-1.0f, 1.0f> left to right
+// and <-1.0f, 1.0f> -bottom- to top. The TexCoords desc is specified in texture
+// space <0, 1.0f> left to right and top to bottom. The input vertices of the
+// shader stage always form a rectangle from {0, 0} - {1, 1}
+cbuffer cb0
+{
+ float4 QuadDesc;
+ float4 TexCoords;
+ float4 MaskTexCoords;
+ float4 TextColor;
+}
+
+cbuffer cb1
+{
+ float4 BlurOffsetsH[3];
+ float4 BlurOffsetsV[3];
+ float4 BlurWeights[3];
+ float4 ShadowColor;
+}
+
+cbuffer cb2
+{
+ float3x3 DeviceSpaceToUserSpace;
+ float2 dimensions;
+ // Precalculate as much as we can!
+ float3 diff;
+ float2 center1;
+ float A;
+ float radius1;
+ float sq_radius1;
+}
+
+cbuffer cb3
+{
+ float3x3 DeviceSpaceToUserSpace_cb3;
+ float2 dimensions_cb3;
+ float2 center;
+ float angle;
+ float start_offset;
+ float end_offset;
+}
+
+struct VS_OUTPUT
+{
+ float4 Position : SV_Position;
+ float2 TexCoord : TEXCOORD0;
+ float2 MaskTexCoord : TEXCOORD1;
+};
+
+struct VS_RADIAL_OUTPUT
+{
+ float4 Position : SV_Position;
+ float2 MaskTexCoord : TEXCOORD0;
+ float2 PixelCoord : TEXCOORD1;
+};
+
+struct VS_CONIC_OUTPUT
+{
+ float4 Position : SV_Position;
+ float2 MaskTexCoord : TEXCOORD0;
+ float2 PixelCoord : TEXCOORD1;
+};
+
+struct PS_TEXT_OUTPUT
+{
+ float4 color;
+ float4 alpha;
+};
+
+Texture2D tex;
+Texture2D bcktex;
+Texture2D mask;
+uint blendop;
+
+sampler sSampler = sampler_state {
+ Filter = MIN_MAG_MIP_LINEAR;
+ Texture = tex;
+ AddressU = Clamp;
+ AddressV = Clamp;
+};
+
+sampler sBckSampler = sampler_state {
+ Filter = MIN_MAG_MIP_LINEAR;
+ Texture = bcktex;
+ AddressU = Clamp;
+ AddressV = Clamp;
+};
+
+sampler sWrapSampler = sampler_state {
+ Filter = MIN_MAG_MIP_LINEAR;
+ Texture = tex;
+ AddressU = Wrap;
+ AddressV = Wrap;
+};
+
+sampler sMirrorSampler = sampler_state {
+ Filter = MIN_MAG_MIP_LINEAR;
+ Texture = tex;
+ AddressU = Mirror;
+ AddressV = Mirror;
+};
+
+sampler sMaskSampler = sampler_state {
+ Filter = MIN_MAG_MIP_LINEAR;
+ Texture = mask;
+ AddressU = Clamp;
+ AddressV = Clamp;
+};
+
+sampler sShadowSampler = sampler_state {
+ Filter = MIN_MAG_MIP_LINEAR;
+ Texture = tex;
+ AddressU = Border;
+ AddressV = Border;
+ BorderColor = float4(0, 0, 0, 0);
+};
+
+RasterizerState TextureRast
+{
+ ScissorEnable = True;
+ CullMode = None;
+};
+
+BlendState ShadowBlendH
+{
+ BlendEnable[0] = False;
+ RenderTargetWriteMask[0] = 0xF;
+};
+
+BlendState ShadowBlendV
+{
+ BlendEnable[0] = True;
+ SrcBlend = One;
+ DestBlend = Inv_Src_Alpha;
+ BlendOp = Add;
+ SrcBlendAlpha = One;
+ DestBlendAlpha = Inv_Src_Alpha;
+ BlendOpAlpha = Add;
+ RenderTargetWriteMask[0] = 0xF;
+};
+
+BlendState bTextBlend
+{
+ AlphaToCoverageEnable = FALSE;
+ BlendEnable[0] = TRUE;
+ SrcBlend = Src1_Color;
+ DestBlend = Inv_Src1_Color;
+ BlendOp = Add;
+ SrcBlendAlpha = Src1_Alpha;
+ DestBlendAlpha = Inv_Src1_Alpha;
+ BlendOpAlpha = Add;
+ RenderTargetWriteMask[0] = 0x0F; // All
+};
+
+VS_OUTPUT SampleTextureVS(float3 pos : POSITION)
+{
+ VS_OUTPUT Output;
+ Output.Position.w = 1.0f;
+ Output.Position.x = pos.x * QuadDesc.z + QuadDesc.x;
+ Output.Position.y = pos.y * QuadDesc.w + QuadDesc.y;
+ Output.Position.z = 0;
+ Output.TexCoord.x = pos.x * TexCoords.z + TexCoords.x;
+ Output.TexCoord.y = pos.y * TexCoords.w + TexCoords.y;
+ Output.MaskTexCoord.x = pos.x * MaskTexCoords.z + MaskTexCoords.x;
+ Output.MaskTexCoord.y = pos.y * MaskTexCoords.w + MaskTexCoords.y;
+ return Output;
+}
+
+VS_RADIAL_OUTPUT SampleRadialVS(float3 pos : POSITION)
+{
+ VS_RADIAL_OUTPUT Output;
+ Output.Position.w = 1.0f;
+ Output.Position.x = pos.x * QuadDesc.z + QuadDesc.x;
+ Output.Position.y = pos.y * QuadDesc.w + QuadDesc.y;
+ Output.Position.z = 0;
+ Output.MaskTexCoord.x = pos.x * MaskTexCoords.z + MaskTexCoords.x;
+ Output.MaskTexCoord.y = pos.y * MaskTexCoords.w + MaskTexCoords.y;
+
+ // For the radial gradient pixel shader we need to pass in the pixel's
+ // coordinates in user space for the color to be correctly determined.
+
+ Output.PixelCoord.x = ((Output.Position.x + 1.0f) / 2.0f) * dimensions.x;
+ Output.PixelCoord.y = ((1.0f - Output.Position.y) / 2.0f) * dimensions.y;
+ Output.PixelCoord.xy = mul(float3(Output.PixelCoord.x, Output.PixelCoord.y, 1.0f), DeviceSpaceToUserSpace).xy;
+ return Output;
+}
+
+VS_CONIC_OUTPUT SampleConicVS(float3 pos : POSITION)
+{
+ VS_CONIC_OUTPUT Output;
+ Output.Position.w = 1.0f;
+ Output.Position.x = pos.x * QuadDesc.z + QuadDesc.x;
+ Output.Position.y = pos.y * QuadDesc.w + QuadDesc.y;
+ Output.Position.z = 0;
+ Output.MaskTexCoord.x = pos.x * MaskTexCoords.z + MaskTexCoords.x;
+ Output.MaskTexCoord.y = pos.y * MaskTexCoords.w + MaskTexCoords.y;
+
+ // For the conic gradient pixel shader we need to pass in the pixel's
+ // coordinates in user space for the color to be correctly determined.
+
+ Output.PixelCoord.x = ((Output.Position.x + 1.0f) / 2.0f) * dimensions_cb3.x;
+ Output.PixelCoord.y = ((1.0f - Output.Position.y) / 2.0f) * dimensions_cb3.y;
+ Output.PixelCoord.xy = mul(float3(Output.PixelCoord.x, Output.PixelCoord.y, 1.0f), DeviceSpaceToUserSpace_cb3).xy;
+ return Output;
+}
+
+float Screen(float a, float b)
+{
+ return 1 - ((1 - a)*(1 - b));
+}
+
+static float RedLuminance = 0.3f;
+static float GreenLuminance = 0.59f;
+static float BlueLuminance = 0.11f;
+
+float Lum(float3 C)
+{
+ return RedLuminance * C.r + GreenLuminance * C.g + BlueLuminance * C.b;
+}
+
+float3 ClipColor(float3 C)
+{
+ float L = Lum(C);
+ float n = min(min(C.r, C.g), C.b);
+ float x = max(max(C.r, C.g), C.b);
+
+ if(n < 0)
+ C = L + (((C - L) * L) / (L - n));
+
+ if(x > 1)
+ C = L + ((C - L) * (1 - L) / (x - L));
+
+ return C;
+}
+
+float3 SetLum(float3 C, float l)
+{
+ float d = l - Lum(C);
+ C = C + d;
+ return ClipColor(C);
+}
+
+float Sat(float3 C)
+{
+ return max(C.r, max(C.g, C.b)) - min(C.r, min(C.g, C.b));
+}
+
+void SetSatComponents(inout float minComp, inout float midComp, inout float maxComp, in float satVal)
+{
+ midComp -= minComp;
+ maxComp -= minComp;
+ minComp = 0.0;
+ if (maxComp > 0.0)
+ {
+ midComp *= satVal/maxComp;
+ maxComp = satVal;
+ }
+}
+
+float3 SetSat(float3 color, in float satVal)
+{
+ if (color.x <= color.y) {
+ if (color.y <= color.z) {
+ // x <= y <= z
+ SetSatComponents(color.x, color.y, color.z, satVal);
+ }
+ else {
+ if (color.x <= color.z) {
+ // x <= z <= y
+ SetSatComponents(color.x, color.z, color.y, satVal);
+ }
+ else {
+ // z <= x <= y
+ SetSatComponents(color.z, color.x, color.y, satVal);
+ }
+ }
+ }
+ else {
+ if (color.x <= color.z) {
+ // y <= x <= z
+ SetSatComponents(color.y, color.x, color.z, satVal);
+ }
+ else {
+ if (color.y <= color.z) {
+ // y <= z <= x
+ SetSatComponents(color.y, color.z, color.x, satVal);
+ }
+ else {
+ // z <= y <= x
+ SetSatComponents(color.z, color.y, color.x, satVal);
+ }
+ }
+ }
+
+ return color;
+}
+
+float4 SampleBlendTextureSeparablePS_1( VS_OUTPUT In) : SV_Target
+{
+ float4 output = tex.Sample(sSampler, In.TexCoord);
+ float4 background = bcktex.Sample(sBckSampler, In.TexCoord);
+ if((output.a == 0) || (background.a == 0))
+ return output;
+
+ output.rgb /= output.a;
+ background.rgb /= background.a;
+
+ float4 retval = output;
+
+ if(blendop == 1) { // multiply
+ retval.rgb = output.rgb * background.rgb;
+ } else if(blendop == 2) {
+ retval.rgb = output.rgb + background.rgb - output.rgb * background.rgb;
+ } else if(blendop == 3) {
+ if(background.r <= 0.5)
+ retval.r = 2*background.r * output.r;
+ else
+ retval.r = Screen(output.r, 2 * background.r - 1);
+ if(background.g <= 0.5)
+ retval.g = 2 * background.g * output.g;
+ else
+ retval.g = Screen(output.g, 2 * background.g - 1);
+ if(background.b <= 0.5)
+ retval.b = 2 * background.b * output.b;
+ else
+ retval.b = Screen(output.b, 2 * background.b - 1);
+ } else if(blendop == 4) {
+ retval.rgb = min(output.rgb, background.rgb);
+ } else if(blendop == 5) {
+ retval.rgb = max(output.rgb, background.rgb);
+ } else {
+ if(background.r == 0)
+ retval.r = 0;
+ else
+ if(output.r == 1)
+ retval.r = 1;
+ else
+ retval.r = min(1, background.r / (1 - output.r));
+ if(background.g == 0)
+ retval.g = 0;
+ else
+ if(output.g == 1)
+ retval.g = 1;
+ else
+ retval.g = min(1, background.g / (1 - output.g));
+ if(background.b == 0)
+ retval.b = 0;
+ else
+ if(output.b == 1)
+ retval.b = 1;
+ else
+ retval.b = min(1, background.b / (1 - output.b));
+ }
+
+ output.rgb = ((1 - background.a) * output.rgb + background.a * retval.rgb) * output.a;
+ return output;
+}
+
+float4 SampleBlendTextureSeparablePS_2( VS_OUTPUT In) : SV_Target
+{
+ float4 output = tex.Sample(sSampler, In.TexCoord);
+ float4 background = bcktex.Sample(sBckSampler, In.TexCoord);
+ if((output.a == 0) || (background.a == 0))
+ return output;
+
+ output.rgb /= output.a;
+ background.rgb /= background.a;
+
+ float4 retval = output;
+
+ if(blendop == 7) {
+ if(background.r == 1)
+ retval.r = 1;
+ else
+ if(output.r == 0)
+ retval.r = 0;
+ else
+ retval.r = 1 - min(1, (1 - background.r) / output.r);
+ if(background.g == 1)
+ retval.g = 1;
+ else
+ if(output.g == 0)
+ retval.g = 0;
+ else
+ retval.g = 1 - min(1, (1 - background.g) / output.g);
+ if(background.b == 1)
+ retval.b = 1;
+ else
+ if(output.b == 0)
+ retval.b = 0;
+ else
+ retval.b = 1 - min(1, (1 - background.b) / output.b);
+ } else if(blendop == 8) {
+ if(output.r <= 0.5)
+ retval.r = 2 * output.r * background.r;
+ else
+ retval.r = Screen(background.r, 2 * output.r -1);
+ if(output.g <= 0.5)
+ retval.g = 2 * output.g * background.g;
+ else
+ retval.g = Screen(background.g, 2 * output.g -1);
+ if(output.b <= 0.5)
+ retval.b = 2 * output.b * background.b;
+ else
+ retval.b = Screen(background.b, 2 * output.b -1);
+ } else if(blendop == 9){
+ float D;
+ if(background.r <= 0.25)
+ D = ((16 * background.r - 12) * background.r + 4) * background.r;
+ else
+ D = sqrt(background.r);
+ if(output.r <= 0.5)
+ retval.r = background.r - (1 - 2 * output.r) * background.r * (1 - background.r);
+ else
+ retval.r = background.r + (2 * output.r - 1) * (D - background.r);
+
+ if(background.g <= 0.25)
+ D = ((16 * background.g - 12) * background.g + 4) * background.g;
+ else
+ D = sqrt(background.g);
+ if(output.g <= 0.5)
+ retval.g = background.g - (1 - 2 * output.g) * background.g * (1 - background.g);
+ else
+ retval.g = background.g + (2 * output.g - 1) * (D - background.g);
+
+ if(background.b <= 0.25)
+ D = ((16 * background.b - 12) * background.b + 4) * background.b;
+ else
+ D = sqrt(background.b);
+
+ if(output.b <= 0.5)
+ retval.b = background.b - (1 - 2 * output.b) * background.b * (1 - background.b);
+ else
+ retval.b = background.b + (2 * output.b - 1) * (D - background.b);
+ } else if(blendop == 10) {
+ retval.rgb = abs(output.rgb - background.rgb);
+ } else {
+ retval.rgb = output.rgb + background.rgb - 2 * output.rgb * background.rgb;
+ }
+
+ output.rgb = ((1 - background.a) * output.rgb + background.a * retval.rgb) * output.a;
+ return output;
+}
+
+float4 SampleBlendTextureNonSeparablePS( VS_OUTPUT In) : SV_Target
+{
+ float4 output = tex.Sample(sSampler, In.TexCoord);
+ float4 background = bcktex.Sample(sBckSampler, In.TexCoord);
+ if((output.a == 0) || (background.a == 0))
+ return output;
+
+ output.rgb /= output.a;
+ background.rgb /= background.a;
+
+ float4 retval = output;
+
+ if(blendop == 12) {
+ retval.rgb = SetLum(SetSat(output.rgb, Sat(background.rgb)), Lum(background.rgb));
+ } else if(blendop == 13) {
+ retval.rgb = SetLum(SetSat(background.rgb, Sat(output.rgb)), Lum(background.rgb));
+ } else if(blendop == 14) {
+ retval.rgb = SetLum(output.rgb, Lum(background.rgb));
+ } else {
+ retval.rgb = SetLum(background.rgb, Lum(output.rgb));
+ }
+
+ output.rgb = ((1 - background.a) * output.rgb + background.a * retval.rgb) * output.a;
+ return output;
+}
+
+
+float4 SampleTexturePS( VS_OUTPUT In) : SV_Target
+{
+ return tex.Sample(sSampler, In.TexCoord);
+}
+
+float4 SampleMaskTexturePS( VS_OUTPUT In) : SV_Target
+{
+ return tex.Sample(sSampler, In.TexCoord) * mask.Sample(sMaskSampler, In.MaskTexCoord).a;
+}
+
+float4 SampleRadialGradientPS(VS_RADIAL_OUTPUT In, uniform sampler aSampler) : SV_Target
+{
+ // Radial gradient painting is defined as the set of circles whose centers
+ // are described by C(t) = (C2 - C1) * t + C1; with radii
+ // R(t) = (R2 - R1) * t + R1; for R(t) > 0. This shader solves the
+ // quadratic equation that arises when calculating t for pixel (x, y).
+ //
+ // A more extensive derrivation can be found in the pixman radial gradient
+ // code.
+
+ float2 p = In.PixelCoord;
+ float3 dp = float3(p - center1, radius1);
+
+ // dpx * dcx + dpy * dcy + r * dr
+ float B = dot(dp, diff);
+
+ float C = pow(dp.x, 2) + pow(dp.y, 2) - sq_radius1;
+
+ float det = pow(B, 2) - A * C;
+
+ if (det < 0) {
+ return float4(0, 0, 0, 0);
+ }
+
+ float sqrt_det = sqrt(abs(det));
+
+ float2 t = (B + float2(sqrt_det, -sqrt_det)) / A;
+
+ float2 isValid = step(float2(-radius1, -radius1), t * diff.z);
+
+ if (max(isValid.x, isValid.y) <= 0) {
+ return float4(0, 0, 0, 0);
+ }
+
+ float upper_t = lerp(t.y, t.x, isValid.x);
+
+ float4 output = tex.Sample(aSampler, float2(upper_t, 0.5));
+ // Premultiply
+ output.rgb *= output.a;
+ // Multiply the output color by the input mask for the operation.
+ output *= mask.Sample(sMaskSampler, In.MaskTexCoord).a;
+ return output;
+};
+
+float4 SampleRadialGradientA0PS( VS_RADIAL_OUTPUT In, uniform sampler aSampler ) : SV_Target
+{
+ // This simpler shader is used for the degenerate case where A is 0,
+ // i.e. we're actually solving a linear equation.
+
+ float2 p = In.PixelCoord;
+ float3 dp = float3(p - center1, radius1);
+
+ // dpx * dcx + dpy * dcy + r * dr
+ float B = dot(dp, diff);
+
+ float C = pow(dp.x, 2) + pow(dp.y, 2) - pow(radius1, 2);
+
+ float t = 0.5 * C / B;
+
+ if (-radius1 >= t * diff.z) {
+ return float4(0, 0, 0, 0);
+ }
+
+ float4 output = tex.Sample(aSampler, float2(t, 0.5));
+ // Premultiply
+ output.rgb *= output.a;
+ // Multiply the output color by the input mask for the operation.
+ output *= mask.Sample(sMaskSampler, In.MaskTexCoord).a;
+ return output;
+};
+
+float4 SampleConicGradientPS(VS_CONIC_OUTPUT In, uniform sampler aSampler) : SV_Target
+{
+ float2 current_dir = In.PixelCoord - center;
+ float current_angle = atan2(current_dir.y, current_dir.x) + (3.141592 / 2.0 - angle);
+ float offset = fmod(current_angle / (2.0 * 3.141592), 1.0) - start_offset;
+ offset = offset / (end_offset - start_offset);
+
+ float upper_t = lerp(0, 1, offset);
+
+ float4 output = tex.Sample(aSampler, float2(upper_t, 0.5));
+ // Premultiply
+ output.rgb *= output.a;
+ // Multiply the output color by the input mask for the operation.
+ output *= mask.Sample(sMaskSampler, In.MaskTexCoord).a;
+ return output;
+};
+
+float4 SampleShadowHPS( VS_OUTPUT In) : SV_Target
+{
+ float outputStrength = 0;
+
+ outputStrength += BlurWeights[0].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].x, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[0].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].y, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[0].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].z, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[0].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].w, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[1].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].x, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[1].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].y, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[1].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].z, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[1].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].w, In.TexCoord.y)).a;
+ outputStrength += BlurWeights[2].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[2].x, In.TexCoord.y)).a;
+
+ return ShadowColor * outputStrength;
+};
+
+float4 SampleShadowVPS( VS_OUTPUT In) : SV_Target
+{
+ float4 outputColor = float4(0, 0, 0, 0);
+
+ outputColor += BlurWeights[0].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].x));
+ outputColor += BlurWeights[0].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].y));
+ outputColor += BlurWeights[0].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].z));
+ outputColor += BlurWeights[0].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].w));
+ outputColor += BlurWeights[1].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].x));
+ outputColor += BlurWeights[1].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].y));
+ outputColor += BlurWeights[1].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].z));
+ outputColor += BlurWeights[1].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].w));
+ outputColor += BlurWeights[2].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[2].x));
+
+ return outputColor;
+};
+
+float4 SampleMaskShadowVPS( VS_OUTPUT In) : SV_Target
+{
+ float4 outputColor = float4(0, 0, 0, 0);
+
+ outputColor += BlurWeights[0].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].x));
+ outputColor += BlurWeights[0].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].y));
+ outputColor += BlurWeights[0].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].z));
+ outputColor += BlurWeights[0].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].w));
+ outputColor += BlurWeights[1].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].x));
+ outputColor += BlurWeights[1].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].y));
+ outputColor += BlurWeights[1].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].z));
+ outputColor += BlurWeights[1].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].w));
+ outputColor += BlurWeights[2].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[2].x));
+
+ return outputColor * mask.Sample(sMaskSampler, In.MaskTexCoord).a;
+};
+
+PS_TEXT_OUTPUT SampleTextTexturePS( VS_OUTPUT In) : SV_Target
+{
+ PS_TEXT_OUTPUT output;
+ output.color = float4(TextColor.r, TextColor.g, TextColor.b, 1.0);
+ output.alpha.rgba = tex.Sample(sSampler, In.TexCoord).bgrg * TextColor.a;
+ return output;
+};
+
+PS_TEXT_OUTPUT SampleTextTexturePSMasked( VS_OUTPUT In) : SV_Target
+{
+ PS_TEXT_OUTPUT output;
+
+ float maskValue = mask.Sample(sMaskSampler, In.MaskTexCoord).a;
+
+ output.color = float4(TextColor.r, TextColor.g, TextColor.b, 1.0);
+ output.alpha.rgba = tex.Sample(sSampler, In.TexCoord).bgrg * TextColor.a * maskValue;
+
+ return output;
+};
+
+technique10 SampleTexture
+{
+ pass P0
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTexturePS()));
+ }
+}
+
+technique10 SampleTextureForSeparableBlending_1
+{
+ pass P0
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleBlendTextureSeparablePS_1()));
+ }
+}
+
+technique10 SampleTextureForSeparableBlending_2
+{
+ pass P0
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleBlendTextureSeparablePS_2()));
+ }
+}
+
+technique10 SampleTextureForNonSeparableBlending
+{
+ pass P0
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleBlendTextureNonSeparablePS()));
+ }
+}
+
+technique10 SampleRadialGradient
+{
+ pass APos
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientPS( sSampler )));
+ }
+ pass A0
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientA0PS( sSampler )));
+ }
+ pass APosWrap
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientPS( sWrapSampler )));
+ }
+ pass A0Wrap
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientA0PS( sWrapSampler )));
+ }
+ pass APosMirror
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientPS( sMirrorSampler )));
+ }
+ pass A0Mirror
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientA0PS( sMirrorSampler )));
+ }
+}
+
+technique10 SampleConicGradient
+{
+ pass APos
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleConicVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleConicGradientPS( sSampler )));
+ }
+ pass APosWrap
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleConicVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleConicGradientPS( sWrapSampler )));
+ }
+ pass APosMirror
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleConicVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleConicGradientPS( sMirrorSampler )));
+ }
+}
+
+technique10 SampleMaskedTexture
+{
+ pass P0
+ {
+ SetRasterizerState(TextureRast);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleMaskTexturePS()));
+ }
+}
+
+technique10 SampleTextureWithShadow
+{
+ // Horizontal pass
+ pass P0
+ {
+ SetRasterizerState(TextureRast);
+ SetBlendState(ShadowBlendH, float4(1.0f, 1.0f, 1.0f, 1.0f), 0xffffffff);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleShadowHPS()));
+ }
+ // Vertical pass
+ pass P1
+ {
+ SetRasterizerState(TextureRast);
+ SetBlendState(ShadowBlendV, float4(1.0f, 1.0f, 1.0f, 1.0f), 0xffffffff);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleShadowVPS()));
+ }
+ // Vertical pass - used when using a mask
+ pass P2
+ {
+ SetRasterizerState(TextureRast);
+ SetBlendState(ShadowBlendV, float4(1.0f, 1.0f, 1.0f, 1.0f), 0xffffffff);
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleMaskShadowVPS()));
+ }
+}
+
+technique10 SampleTextTexture
+{
+ pass Unmasked
+ {
+ SetRasterizerState(TextureRast);
+ SetBlendState(bTextBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTextTexturePS()));
+ }
+ pass Masked
+ {
+ SetRasterizerState(TextureRast);
+ SetBlendState(bTextBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
+ SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS()));
+ SetGeometryShader(NULL);
+ SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTextTexturePSMasked()));
+ }
+}
+
diff --git a/gfx/2d/ShadersD2D.h b/gfx/2d/ShadersD2D.h
new file mode 100644
index 0000000000..ceca3df511
--- /dev/null
+++ b/gfx/2d/ShadersD2D.h
@@ -0,0 +1,10855 @@
+#if 0
+//
+// FX Version: fx_4_0
+// Child effect (requires effect pool): false
+//
+// 5 local buffer(s)
+//
+cbuffer $Globals
+{
+ uint blendop; // Offset: 0, size: 4
+}
+
+cbuffer cb0
+{
+ float4 QuadDesc; // Offset: 0, size: 16
+ float4 TexCoords; // Offset: 16, size: 16
+ float4 MaskTexCoords; // Offset: 32, size: 16
+ float4 TextColor; // Offset: 48, size: 16
+}
+
+cbuffer cb1
+{
+ float4 BlurOffsetsH[3]; // Offset: 0, size: 48
+ float4 BlurOffsetsV[3]; // Offset: 48, size: 48
+ float4 BlurWeights[3]; // Offset: 96, size: 48
+ float4 ShadowColor; // Offset: 144, size: 16
+}
+
+cbuffer cb2
+{
+ float3x3 DeviceSpaceToUserSpace; // Offset: 0, size: 44
+ float2 dimensions; // Offset: 48, size: 8
+ float3 diff; // Offset: 64, size: 12
+ float2 center1; // Offset: 80, size: 8
+ float A; // Offset: 88, size: 4
+ float radius1; // Offset: 92, size: 4
+ float sq_radius1; // Offset: 96, size: 4
+}
+
+cbuffer cb3
+{
+ float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0, size: 44
+ float2 dimensions_cb3; // Offset: 48, size: 8
+ float2 center; // Offset: 56, size: 8
+ float angle; // Offset: 64, size: 4
+ float start_offset; // Offset: 68, size: 4
+ float end_offset; // Offset: 72, size: 4
+}
+
+//
+// 13 local object(s)
+//
+Texture2D tex;
+Texture2D bcktex;
+Texture2D mask;
+SamplerState sSampler
+{
+ Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */);
+ Texture = tex;
+ AddressU = uint(CLAMP /* 3 */);
+ AddressV = uint(CLAMP /* 3 */);
+};
+SamplerState sBckSampler
+{
+ Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */);
+ Texture = bcktex;
+ AddressU = uint(CLAMP /* 3 */);
+ AddressV = uint(CLAMP /* 3 */);
+};
+SamplerState sWrapSampler
+{
+ Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */);
+ Texture = tex;
+ AddressU = uint(WRAP /* 1 */);
+ AddressV = uint(WRAP /* 1 */);
+};
+SamplerState sMirrorSampler
+{
+ Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */);
+ Texture = tex;
+ AddressU = uint(MIRROR /* 2 */);
+ AddressV = uint(MIRROR /* 2 */);
+};
+SamplerState sMaskSampler
+{
+ Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */);
+ Texture = mask;
+ AddressU = uint(CLAMP /* 3 */);
+ AddressV = uint(CLAMP /* 3 */);
+};
+SamplerState sShadowSampler
+{
+ Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */);
+ Texture = tex;
+ AddressU = uint(BORDER /* 4 */);
+ AddressV = uint(BORDER /* 4 */);
+ BorderColor = float4(0, 0, 0, 0);
+};
+RasterizerState TextureRast
+{
+ ScissorEnable = bool(TRUE /* 1 */);
+ CullMode = uint(NONE /* 1 */);
+};
+BlendState ShadowBlendH
+{
+ BlendEnable[0] = bool(FALSE /* 0 */);
+ RenderTargetWriteMask[0] = byte(0x0f);
+};
+BlendState ShadowBlendV
+{
+ BlendEnable[0] = bool(TRUE /* 1 */);
+ SrcBlend[0] = uint(ONE /* 2 */);
+ DestBlend[0] = uint(INV_SRC_ALPHA /* 6 */);
+ BlendOp[0] = uint(ADD /* 1 */);
+ SrcBlendAlpha[0] = uint(ONE /* 2 */);
+ DestBlendAlpha[0] = uint(INV_SRC_ALPHA /* 6 */);
+ BlendOpAlpha[0] = uint(ADD /* 1 */);
+ RenderTargetWriteMask[0] = byte(0x0f);
+};
+BlendState bTextBlend
+{
+ AlphaToCoverageEnable = bool(FALSE /* 0 */);
+ BlendEnable[0] = bool(TRUE /* 1 */);
+ SrcBlend[0] = uint(SRC1_COLOR /* 16 */);
+ DestBlend[0] = uint(INV_SRC1_COLOR /* 17 */);
+ BlendOp[0] = uint(ADD /* 1 */);
+ SrcBlendAlpha[0] = uint(SRC1_ALPHA /* 18 */);
+ DestBlendAlpha[0] = uint(INV_SRC1_ALPHA /* 19 */);
+ BlendOpAlpha[0] = uint(ADD /* 1 */);
+ RenderTargetWriteMask[0] = byte(0x0f);
+};
+
+//
+// 9 technique(s)
+//
+technique10 SampleTexture
+{
+ pass P0
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // tex texture float4 2d 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ dcl t0
+ dcl_2d s0
+ texld r0, t0, s0
+ mov oC0, r0
+
+ // approximately 2 instruction slots used (1 texture, 1 arithmetic)
+ ps_4_0
+ dcl_sampler s0, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ sample o0.xyzw, v1.xyxx, t0.xyzw, s0
+ ret
+ // Approximately 2 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleTextureForSeparableBlending_1
+{
+ pass P0
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer $Globals
+ // {
+ //
+ // uint blendop; // Offset: 0 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sBckSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // bcktex texture float4 2d 1 1
+ // $Globals cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 0 1 (UINT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c1, -1, -2, -3, -4
+ def c2, 1, 0, 0.5, -2
+ def c3, -5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mov r0.w, c0.x
+ add r0.x, r0.w, c3.x
+ mul r0.x, r0.x, r0.x
+ texld r1, t0, s1
+ texld r2, t0, s0
+ rcp r0.y, r2.w
+ mad r3.xyz, r2, r0.y, -c2.x
+ mul r3.xyz, r3, r3
+ mad r4.xyz, r2, -r0.y, c2.x
+ rcp r3.w, r4.x
+ rcp r4.w, r1.w
+ mul r5.xyz, r1, r4.w
+ mad r1.xyz, r1, -r4.w, c2.z
+ mul r3.w, r3.w, r5.x
+ min r4.w, r3.w, c2.x
+ cmp r4.w, -r3.x, c2.x, r4.w
+ mul r6.xyz, r5, r5
+ cmp r7.x, -r6.x, c2.y, r4.w
+ rcp r4.w, r4.y
+ mul r4.w, r4.w, r5.y
+ min r5.w, r4.w, c2.x
+ cmp r4.w, -r3.y, c2.x, r5.w
+ cmp r7.y, -r6.y, c2.y, r4.w
+ rcp r4.w, r4.z
+ mul r4.w, r4.w, r5.z
+ min r5.w, r4.w, c2.x
+ cmp r4.w, -r3.z, c2.x, r5.w
+ cmp r7.z, -r6.z, c2.y, r4.w
+ mul r3.xyz, r0.y, r2
+ mad r6.xyz, r2, r0.y, r5
+ mad r6.xyz, r3, -r5, r6
+ max r8.xyz, r3, r5
+ cmp r0.xyz, -r0.x, r8, r7
+ add r7, r0.w, c1
+ mul r7, r7, r7
+ min r8.xyz, r5, r3
+ cmp r0.xyz, -r7.w, r8, r0
+ mad r8.xyz, r5, -c2.w, -c2.x
+ add r8.xyz, -r8, c2.x
+ mad r4.xyz, r4, -r8, c2.x
+ add r8.xyz, r5, r5
+ mul r5.xyz, r5, r3
+ mul r8.xyz, r3, r8
+ cmp r1.xyz, r1, r8, r4
+ cmp r0.xyz, -r7.z, r1, r0
+ cmp r0.xyz, -r7.y, r6, r0
+ cmp r0.xyz, -r7.x, r5, r0
+ lrp r4.xyz, r1.w, r0, r3
+ mul r4.w, r1.w, r1.w
+ cmp r4.w, -r4.w, c2.x, c2.y
+ mul r0.xyz, r2.w, r4
+ mul r0.w, r2.w, r2.w
+ cmp r0.w, -r0.w, c2.x, c2.y
+ add r0.w, r4.w, r0.w
+ cmp r2.xyz, -r0.w, r0, r2
+ mov oC0, r2
+
+ // approximately 56 instruction slots used (2 texture, 54 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[1], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ dcl_temps 7
+ sample r0.xyzw, v1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, v1.xyxx, t1.xyzw, s1
+ eq r2.x, r0.w, l(0.000000)
+ eq r2.y, r1.w, l(0.000000)
+ or r2.x, r2.y, r2.x
+ if_nz r2.x
+ mov o0.xyzw, r0.xyzw
+ ret
+ endif
+ div r0.xyz, r0.xyzx, r0.wwww
+ div r1.xyz, r1.xyzx, r1.wwww
+ ieq r2.x, cb0[0].x, l(1)
+ if_nz r2.x
+ mul r2.xyz, r0.xyzx, r1.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(2)
+ if_nz r2.w
+ add r3.xyz, r0.xyzx, r1.xyzx
+ mad r2.xyz, -r0.xyzx, r1.xyzx, r3.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(3)
+ if_nz r2.w
+ ge r3.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r1.xyzx
+ add r4.xyz, r1.xyzx, r1.xyzx
+ mul r4.xyz, r0.xyzx, r4.xyzx
+ mad r5.xyz, r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000)
+ add r6.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ add r5.xyz, -r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ mad r5.xyz, -r6.xyzx, r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ movc r2.xyz, r3.xyzx, r4.xyzx, r5.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(4)
+ if_nz r2.w
+ min r2.xyz, r0.xyzx, r1.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(5)
+ if_nz r2.w
+ max r2.xyz, r0.xyzx, r1.xyzx
+ else
+ eq r3.xyz, r1.xyzx, l(0.000000, 0.000000, 0.000000, 0.000000)
+ eq r4.xyz, r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ add r5.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ div r1.xyz, r1.xyzx, r5.xyzx
+ min r1.xyz, r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ movc r1.xyz, r4.xyzx, l(1.000000,1.000000,1.000000,0), r1.xyzx
+ movc r2.xyz, r3.xyzx, l(0,0,0,0), r1.xyzx
+ endif
+ endif
+ endif
+ endif
+ endif
+ add r1.x, -r1.w, l(1.000000)
+ mul r1.yzw, r1.wwww, r2.xxyz
+ mad r0.xyz, r1.xxxx, r0.xyzx, r1.yzwy
+ mul o0.xyz, r0.wwww, r0.xyzx
+ mov o0.w, r0.w
+ ret
+ // Approximately 57 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleTextureForSeparableBlending_2
+{
+ pass P0
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer $Globals
+ // {
+ //
+ // uint blendop; // Offset: 0 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sBckSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // bcktex texture float4 2d 1 1
+ // $Globals cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 0 1 (UINT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c1, -7, -8, -9, -10
+ def c2, 1, 0, -1, 0.25
+ def c3, 0.5, 2, -1, 4
+ def c4, 16, -12, 2, 1
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mov r0.w, c0.x
+ add r0, r0.w, c1
+ mul r0, r0, r0
+ texld r1, t0, s0
+ texld r2, t0, s1
+ rcp r3.w, r2.w
+ mad r3.xy, r2.yzzw, -r3.w, c2.w
+ mul r4.xyz, r2, r3.w
+ mad r5.xyz, r4, c4.x, c4.y
+ mad r5.xyz, r5, r4, c3.w
+ mul r5.xyz, r4, r5
+ rsq r4.w, r4.y
+ rcp r4.w, r4.w
+ cmp r4.w, r3.x, r5.y, r4.w
+ mad r4.w, r2.y, -r3.w, r4.w
+ rcp r3.x, r1.w
+ mul r6.xyz, r1, r3.x
+ mad r7.xyz, r6, c3.y, c3.z
+ mad r4.w, r7.y, r4.w, r4.y
+ mad r8.xyz, r1, -r3.x, c3.x
+ mad r9, r2.xyzx, -r3.w, c2.xxxw
+ mad r10.xyz, r6, -c4.z, c4.w
+ mul r10.xyz, r4, r10
+ mad r10.xyz, r10, -r9, r4
+ cmp r11.y, r8.y, r10.y, r4.w
+ rsq r4.w, r4.z
+ rcp r4.w, r4.w
+ cmp r4.w, r3.y, r5.z, r4.w
+ mad r4.w, r2.z, -r3.w, r4.w
+ mad r4.w, r7.z, r4.w, r4.z
+ cmp r11.z, r8.z, r10.z, r4.w
+ rsq r4.w, r4.x
+ rcp r4.w, r4.w
+ cmp r4.w, r9.w, r5.x, r4.w
+ mad r4.w, r2.x, -r3.w, r4.w
+ mad r2.xyz, r2, r3.w, c2.z
+ mul r2.xyz, r2, r2
+ mad r4.w, r7.x, r4.w, r4.x
+ add r3.yzw, -r7.xxyz, c2.x
+ mad r3.yzw, r9.xxyz, -r3, c2.x
+ cmp r11.x, r8.x, r10.x, r4.w
+ mad r5.xyz, r1, r3.x, -r4
+ mad r7.xyz, r1, r3.x, r4
+ abs r5.xyz, r5
+ mul r10.xyz, r4, r6
+ mad r7.xyz, r10, -c3.y, r7
+ cmp r5.xyz, -r0.w, r5, r7
+ cmp r5.xyz, -r0.z, r11, r5
+ add r7.xyz, r6, r6
+ mul r4.xyz, r4, r7
+ cmp r3.xyz, r8, r4, r3.yzww
+ cmp r0.yzw, -r0.y, r3.xxyz, r5.xxyz
+ rcp r6.w, r6.x
+ mad r6.w, r9.x, -r6.w, c2.x
+ max r3.x, r6.w, c2.y
+ mul r3.yzw, r6.xxyz, r6.xxyz
+ cmp r6.w, -r3.y, c2.y, r3.x
+ cmp r4.x, -r2.x, c2.x, r6.w
+ rcp r4.w, r6.y
+ mad r4.w, r9.y, -r4.w, c2.x
+ max r6.w, r4.w, c2.y
+ cmp r4.w, -r3.z, c2.y, r6.w
+ cmp r4.y, -r2.y, c2.x, r4.w
+ rcp r4.w, r6.z
+ mad r4.w, r9.z, -r4.w, c2.x
+ max r6.w, r4.w, c2.y
+ cmp r4.w, -r3.w, c2.y, r6.w
+ cmp r4.z, -r2.z, c2.x, r4.w
+ cmp r0.xyz, -r0.x, r4, r0.yzww
+ lrp r3.xyz, r2.w, r0, r6
+ mul r3.w, r2.w, r2.w
+ cmp r3.w, -r3.w, c2.x, c2.y
+ mul r0.xyz, r1.w, r3
+ mul r0.w, r1.w, r1.w
+ cmp r0.w, -r0.w, c2.x, c2.y
+ add r0.w, r3.w, r0.w
+ cmp r1.xyz, -r0.w, r0, r1
+ mov oC0, r1
+
+ // approximately 78 instruction slots used (2 texture, 76 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[1], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ dcl_temps 7
+ sample r0.xyzw, v1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, v1.xyxx, t1.xyzw, s1
+ eq r2.x, r0.w, l(0.000000)
+ eq r2.y, r1.w, l(0.000000)
+ or r2.x, r2.y, r2.x
+ if_nz r2.x
+ mov o0.xyzw, r0.xyzw
+ ret
+ endif
+ div r0.xyz, r0.xyzx, r0.wwww
+ div r1.xyz, r1.xyzx, r1.wwww
+ ieq r2.x, cb0[0].x, l(7)
+ if_nz r2.x
+ eq r2.xyz, r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ eq r3.xyz, r0.xyzx, l(0.000000, 0.000000, 0.000000, 0.000000)
+ add r4.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ div r4.xyz, r4.xyzx, r0.xyzx
+ min r4.xyz, r4.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ add r4.xyz, -r4.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ movc r3.xyz, r3.xyzx, l(0,0,0,0), r4.xyzx
+ movc r2.xyz, r2.xyzx, l(1.000000,1.000000,1.000000,0), r3.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(8)
+ if_nz r2.w
+ ge r3.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r0.xyzx
+ add r4.xyz, r0.xyzx, r0.xyzx
+ mul r4.xyz, r1.xyzx, r4.xyzx
+ mad r5.xyz, r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000)
+ add r6.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ add r5.xyz, -r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ mad r5.xyz, -r6.xyzx, r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ movc r2.xyz, r3.xyzx, r4.xyzx, r5.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(9)
+ if_nz r2.w
+ ge r3.xyz, l(0.250000, 0.250000, 0.250000, 0.000000), r1.xyzx
+ mad r4.xyz, r1.xyzx, l(16.000000, 16.000000, 16.000000, 0.000000), l(-12.000000, -12.000000, -12.000000, 0.000000)
+ mad r4.xyz, r4.xyzx, r1.xyzx, l(4.000000, 4.000000, 4.000000, 0.000000)
+ mul r4.xyz, r1.xyzx, r4.xyzx
+ sqrt r5.xyz, r1.xyzx
+ movc r3.xyz, r3.xyzx, r4.xyzx, r5.xyzx
+ ge r4.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r0.xyzx
+ mad r5.xyz, -r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(1.000000, 1.000000, 1.000000, 0.000000)
+ mul r5.xyz, r1.xyzx, r5.xyzx
+ add r6.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
+ mad r5.xyz, -r5.xyzx, r6.xyzx, r1.xyzx
+ mad r6.xyz, r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000)
+ add r3.xyz, -r1.xyzx, r3.xyzx
+ mad r3.xyz, r6.xyzx, r3.xyzx, r1.xyzx
+ movc r2.xyz, r4.xyzx, r5.xyzx, r3.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(10)
+ add r3.xyz, r0.xyzx, -r1.xyzx
+ add r4.xyz, r0.xyzx, r1.xyzx
+ mul r1.xyz, r0.xyzx, r1.xyzx
+ mad r1.xyz, -r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), r4.xyzx
+ movc r2.xyz, r2.wwww, |r3.xyzx|, r1.xyzx
+ endif
+ endif
+ endif
+ add r1.x, -r1.w, l(1.000000)
+ mul r1.yzw, r1.wwww, r2.xxyz
+ mad r0.xyz, r1.xxxx, r0.xyzx, r1.yzwy
+ mul o0.xyz, r0.wwww, r0.xyzx
+ mov o0.w, r0.w
+ ret
+ // Approximately 66 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleTextureForNonSeparableBlending
+{
+ pass P0
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer $Globals
+ // {
+ //
+ // uint blendop; // Offset: 0 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sBckSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // bcktex texture float4 2d 1 1
+ // $Globals cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 0 1 (UINT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c1, -12, -13, -14, 0
+ def c2, 1, 0, 0, 0
+ def c3, 0.300000012, 0.589999974, 0.109999999, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mov r0.y, c2.y
+ mov r1.y, c2.y
+ mov r2.z, c2.y
+ texld r3, t0, s1
+ texld r4, t0, s0
+ rcp r0.w, r4.w
+ mul r5.xyz, r0.w, r4
+ mad r6.xy, r4.yxzw, r0.w, -r5.zyzw
+ cmp r7.xy, r6.x, r5.yzzw, r5.zyzw
+ max r1.w, r5.x, r7.x
+ min r2.w, r7.y, r5.x
+ add r7.w, r1.w, -r2.w
+ rcp r1.w, r3.w
+ mul r8.xyz, r1.w, r3
+ mad r9.xy, r3.x, r1.w, -r8.zyzw
+ rcp r2.w, r9.y
+ mul r2.w, r2.w, r7.w
+ mad r10, r3.zyyz, r1.w, -r8.xxzy
+ mul r7.y, r2.w, r10.w
+ mov r9.zw, r10
+ cmp r1.xz, -r9.y, r9.yyww, r7.wyyw
+ rcp r2.w, r9.x
+ mul r2.w, r2.w, r7.w
+ mul r7.x, r2.w, r9.z
+ cmp r2.xy, -r9.x, r9.xzzw, r7.wxzw
+ cmp r1.xyz, r9.w, r1, r2
+ rcp r5.w, r9.w
+ mul r5.w, r5.w, r7.w
+ mul r7.z, r5.w, r9.y
+ cmp r0.xz, -r10.w, r9.yyww, r7.zyww
+ cmp r0.xyz, r10.x, r0, r1
+ mov r1.x, c2.y
+ mov r2.x, c2.y
+ mov r11.z, c2.y
+ rcp r2.w, r9.z
+ mul r2.w, r2.w, r7.w
+ mul r7.x, r2.w, r9.x
+ cmp r11.xy, -r10.z, r9.xzzw, r7.xwzw
+ rcp r2.w, r10.y
+ mul r2.w, r2.w, r7.w
+ mul r7.y, r2.w, r10.x
+ cmp r2.yz, -r10.y, r10.xyxw, r7.xwyw
+ cmp r2.xyz, r10.x, r2, r11
+ rcp r2.w, r10.x
+ mul r2.w, r2.w, r7.w
+ mul r7.z, r2.w, r10.y
+ cmp r1.yz, -r10.x, r10.xyxw, r7.xzww
+ cmp r1.xyz, r9.w, r1, r2
+ cmp r0.xyz, r10.y, r1, r0
+ cmp r1.xy, r9.z, r8.yzzw, r8.zyzw
+ dp3 r5.w, r0, c3
+ dp3 r1.z, r8, c3
+ add r5.w, -r5.w, r1.z
+ add r0.xyz, r0, r5.w
+ add r5.w, -r0.y, r0.x
+ cmp r2.xy, r5.w, r0.yxzw, r0
+ min r5.w, r0.z, r2.x
+ max r7.x, r2.y, r0.z
+ dp3 r2.x, r0, c3
+ add r2.y, -r5.w, r2.x
+ rcp r2.y, r2.y
+ add r7.yzw, r0.xxyz, -r2.x
+ mul r7.yzw, r2.x, r7
+ mad r2.yzw, r7, r2.y, r2.x
+ cmp r0.xyz, r5.w, r0, r2.yzww
+ add r2.yzw, -r2.x, r0.xxyz
+ add r5.w, -r2.x, c2.x
+ mul r2.yzw, r2, r5.w
+ add r5.w, -r2.x, r7.x
+ add r7.x, -r7.x, c2.x
+ rcp r5.w, r5.w
+ mad r2.xyz, r2.yzww, r5.w, r2.x
+ cmp r0.xyz, r7.x, r0, r2
+ dp3 r5.w, r5, c3
+ add r2.x, r1.z, -r5.w
+ add r5.w, -r1.z, r5.w
+ mad r2.yzw, r3.xxyz, r1.w, r5.w
+ mad r3.xyz, r4, r0.w, r2.x
+ mad r7, r4.zyzx, r0.w, -r5.xxyz
+ add r0.w, -r3.y, r3.x
+ cmp r8.yz, r0.w, r3.xyxw, r3.xxyw
+ min r0.w, r3.z, r8.y
+ max r1.w, r8.z, r3.z
+ dp3 r5.w, r3, c3
+ add r2.x, -r0.w, r5.w
+ rcp r2.x, r2.x
+ add r8.yzw, r3.xxyz, -r5.w
+ mul r8.yzw, r5.w, r8
+ mad r8.yzw, r8, r2.x, r5.w
+ cmp r3.xyz, r0.w, r3, r8.yzww
+ add r8.yzw, -r5.w, r3.xxyz
+ add r0.w, -r5.w, c2.x
+ mul r8.yzw, r0.w, r8
+ add r0.w, r1.w, -r5.w
+ add r1.w, -r1.w, c2.x
+ rcp r0.w, r0.w
+ mad r8.yzw, r8, r0.w, r5.w
+ cmp r3.xyz, r1.w, r3, r8.yzww
+ add r0.w, -r2.z, r2.y
+ cmp r8.yz, r0.w, r2.xzyw, r2
+ min r0.w, r2.w, r8.y
+ max r1.w, r8.z, r2.w
+ dp3 r5.w, r2.yzww, c3
+ add r2.x, -r0.w, r5.w
+ rcp r2.x, r2.x
+ add r8.yzw, r2, -r5.w
+ mul r8.yzw, r5.w, r8
+ mad r8.yzw, r8, r2.x, r5.w
+ cmp r2.xyz, r0.w, r2.yzww, r8.yzww
+ add r8.yzw, -r5.w, r2.xxyz
+ add r0.w, -r5.w, c2.x
+ mul r8.yzw, r0.w, r8
+ add r0.w, r1.w, -r5.w
+ add r1.w, -r1.w, c2.x
+ rcp r0.w, r0.w
+ mad r8.yzw, r8, r0.w, r5.w
+ cmp r2.xyz, r1.w, r2, r8.yzww
+ mov r0.w, c0.x
+ add r8.yzw, r0.w, c1.xxyz
+ mul r8.yzw, r8, r8
+ cmp r2.xyz, -r8.w, r3, r2
+ cmp r0.xyz, -r8.z, r0, r2
+ mov r2.y, c2.y
+ mov r3.y, c2.y
+ mov r9.z, c2.y
+ max r0.w, r8.x, r1.x
+ min r2.w, r1.y, r8.x
+ add r10.w, r0.w, -r2.w
+ rcp r0.w, r7.w
+ mul r0.w, r0.w, r10.w
+ mul r10.x, r0.w, r6.x
+ mov r6.zw, r7.xywz
+ cmp r9.xy, -r7.w, r6.zxzw, r10.wxzw
+ rcp r0.w, r6.y
+ mul r0.w, r0.w, r10.w
+ mul r10.y, r0.w, r7.z
+ cmp r3.xz, -r6.y, r6.yyww, r10.wyyw
+ cmp r1.xyw, r7.z, r3.xyzz, r9.xyzz
+ rcp r0.w, r7.z
+ mul r0.w, r0.w, r10.w
+ mul r10.z, r0.w, r6.y
+ cmp r2.xz, -r7.z, r6.yyww, r10.zyww
+ cmp r1.xyw, r7.x, r2.xyzz, r1
+ mov r2.x, c2.y
+ mov r3.z, c2.y
+ rcp r0.w, r6.x
+ mul r0.w, r0.w, r10.w
+ mul r10.x, r0.w, r7.w
+ cmp r3.xy, -r6.x, r6.zxzw, r10.xwzw
+ rcp r0.w, r7.y
+ mul r0.w, r0.w, r10.w
+ mul r10.y, r0.w, r7.x
+ cmp r2.yz, -r7.y, r7.xyxw, r10.xwyw
+ cmp r2.xyz, r7.x, r2, r3
+ mov r3.x, c2.y
+ rcp r0.w, r7.x
+ mul r0.w, r0.w, r10.w
+ mul r10.z, r0.w, r7.y
+ cmp r3.yz, -r7.x, r7.xyxw, r10.xzww
+ cmp r2.xyz, r7.z, r3, r2
+ cmp r1.xyw, r7.y, r2.xyzz, r1
+ dp3 r0.w, r1.xyww, c3
+ add r0.w, -r0.w, r1.z
+ add r1.xyz, r0.w, r1.xyww
+ add r0.w, -r1.y, r1.x
+ cmp r2.xy, r0.w, r1.yxzw, r1
+ min r0.w, r1.z, r2.x
+ max r5.w, r2.y, r1.z
+ dp3 r1.w, r1, c3
+ add r2.xyz, -r1.w, r1
+ mul r2.xyz, r1.w, r2
+ add r2.w, -r0.w, r1.w
+ rcp r2.w, r2.w
+ mad r2.xyz, r2, r2.w, r1.w
+ cmp r1.xyz, r0.w, r1, r2
+ add r2.xyz, -r1.w, r1
+ add r0.w, -r1.w, c2.x
+ mul r2.xyz, r0.w, r2
+ add r0.w, -r1.w, r5.w
+ add r2.w, -r5.w, c2.x
+ rcp r0.w, r0.w
+ mad r2.xyz, r2, r0.w, r1.w
+ cmp r1.xyz, r2.w, r1, r2
+ cmp r0.xyz, -r8.y, r1, r0
+ lrp r1.xyz, r3.w, r0, r5
+ mul r1.w, r3.w, r3.w
+ cmp r1.w, -r1.w, c2.x, c2.y
+ mul r0.xyz, r4.w, r1
+ mul r0.w, r4.w, r4.w
+ cmp r0.w, -r0.w, c2.x, c2.y
+ add r0.w, r1.w, r0.w
+ cmp r4.xyz, -r0.w, r0, r4
+ mov oC0, r4
+
+ // approximately 193 instruction slots used (2 texture, 191 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[1], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ dcl_temps 9
+ sample r0.xyzw, v1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, v1.xyxx, t1.xyzw, s1
+ eq r2.x, r0.w, l(0.000000)
+ eq r2.y, r1.w, l(0.000000)
+ or r2.x, r2.y, r2.x
+ if_nz r2.x
+ mov o0.xyzw, r0.xyzw
+ ret
+ endif
+ div r0.xyz, r0.xyzx, r0.wwww
+ div r1.xyz, r1.xyzx, r1.wwww
+ ieq r2.x, cb0[0].x, l(12)
+ if_nz r2.x
+ max r2.x, r1.z, r1.y
+ max r2.x, r1.x, r2.x
+ min r2.y, r1.z, r1.y
+ min r2.y, r1.x, r2.y
+ add r2.w, -r2.y, r2.x
+ ge r3.x, r0.y, r0.x
+ if_nz r3.x
+ add r3.xyzw, -r0.xxzz, r0.yzxy
+ lt r4.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r3.yxwy
+ div r5.xyz, r2.wwww, r3.yxwy
+ mul r2.xyz, r3.xyzx, r5.xyzx
+ movc r5.yz, r4.xxxx, r2.xxwx, r3.xxyx
+ ge r4.xw, r0.zzzz, r0.yyyx
+ movc r6.yz, r4.yyyy, r2.wwyw, r3.xxyx
+ movc r3.xy, r4.zzzz, r2.zwzz, r3.zwzz
+ mov r6.x, l(0)
+ mov r3.z, l(0)
+ movc r3.xyz, r4.wwww, r6.xyzx, r3.xyzx
+ mov r5.x, l(0)
+ movc r3.xyz, r4.xxxx, r5.xyzx, r3.xyzx
+ else
+ add r4.xyzw, -r0.yyzz, r0.xzyx
+ lt r5.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r4.yxwy
+ div r6.xyz, r2.wwww, r4.yxwy
+ mul r2.xyz, r4.xyzx, r6.xyzx
+ movc r6.xz, r5.xxxx, r2.xxwx, r4.xxyx
+ ge r5.xw, r0.zzzz, r0.xxxy
+ movc r7.xz, r5.yyyy, r2.wwyw, r4.xxyx
+ movc r2.xy, r5.zzzz, r2.wzww, r4.wzww
+ mov r7.y, l(0)
+ mov r2.z, l(0)
+ movc r2.xyz, r5.wwww, r7.xyzx, r2.xyzx
+ mov r6.y, l(0)
+ movc r3.xyz, r5.xxxx, r6.xyzx, r2.xyzx
+ endif
+ dp3 r2.x, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ dp3 r2.y, r3.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ add r2.x, -r2.y, r2.x
+ add r2.xyz, r2.xxxx, r3.xyzx
+ dp3 r2.w, r2.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ min r3.x, r2.y, r2.x
+ min r3.x, r2.z, r3.x
+ max r3.y, r2.y, r2.x
+ max r3.y, r2.z, r3.y
+ lt r3.z, r3.x, l(0.000000)
+ add r4.xyz, -r2.wwww, r2.xyzx
+ mul r4.xyz, r2.wwww, r4.xyzx
+ add r3.x, r2.w, -r3.x
+ div r4.xyz, r4.xyzx, r3.xxxx
+ add r4.xyz, r2.wwww, r4.xyzx
+ movc r2.xyz, r3.zzzz, r4.xyzx, r2.xyzx
+ lt r3.x, l(1.000000), r3.y
+ add r4.xyz, -r2.wwww, r2.xyzx
+ add r3.z, -r2.w, l(1.000000)
+ mul r4.xyz, r3.zzzz, r4.xyzx
+ add r3.y, -r2.w, r3.y
+ div r3.yzw, r4.xxyz, r3.yyyy
+ add r3.yzw, r2.wwww, r3.yyzw
+ movc r2.xyz, r3.xxxx, r3.yzwy, r2.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(13)
+ if_nz r2.w
+ max r2.w, r0.z, r0.y
+ max r2.w, r0.x, r2.w
+ min r3.x, r0.z, r0.y
+ min r3.x, r0.x, r3.x
+ add r3.w, r2.w, -r3.x
+ ge r2.w, r1.y, r1.x
+ if_nz r2.w
+ add r4.xyzw, -r1.xxzz, r1.yzxy
+ lt r5.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r4.yxwy
+ div r6.xyz, r3.wwww, r4.yxwy
+ mul r3.xyz, r4.xyzx, r6.xyzx
+ movc r6.yz, r5.xxxx, r3.xxwx, r4.xxyx
+ ge r5.xw, r1.zzzz, r1.yyyx
+ movc r7.yz, r5.yyyy, r3.wwyw, r4.xxyx
+ movc r4.xy, r5.zzzz, r3.zwzz, r4.zwzz
+ mov r7.x, l(0)
+ mov r4.z, l(0)
+ movc r4.xyz, r5.wwww, r7.xyzx, r4.xyzx
+ mov r6.x, l(0)
+ movc r4.xyz, r5.xxxx, r6.xyzx, r4.xyzx
+ else
+ add r5.xyzw, -r1.yyzz, r1.xzyx
+ lt r6.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r5.yxwy
+ div r7.xyz, r3.wwww, r5.yxwy
+ mul r3.xyz, r5.xyzx, r7.xyzx
+ movc r7.xz, r6.xxxx, r3.xxwx, r5.xxyx
+ ge r6.xw, r1.zzzz, r1.xxxy
+ movc r8.xz, r6.yyyy, r3.wwyw, r5.xxyx
+ movc r3.xy, r6.zzzz, r3.wzww, r5.wzww
+ mov r8.y, l(0)
+ mov r3.z, l(0)
+ movc r3.xyz, r6.wwww, r8.xyzx, r3.xyzx
+ mov r7.y, l(0)
+ movc r4.xyz, r6.xxxx, r7.xyzx, r3.xyzx
+ endif
+ dp3 r2.w, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ dp3 r3.x, r4.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ add r2.w, r2.w, -r3.x
+ add r3.xyz, r2.wwww, r4.xyzx
+ dp3 r2.w, r3.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ min r3.w, r3.y, r3.x
+ min r3.w, r3.z, r3.w
+ max r4.x, r3.y, r3.x
+ max r4.x, r3.z, r4.x
+ lt r4.y, r3.w, l(0.000000)
+ add r5.xyz, -r2.wwww, r3.xyzx
+ mul r5.xyz, r2.wwww, r5.xyzx
+ add r3.w, r2.w, -r3.w
+ div r5.xyz, r5.xyzx, r3.wwww
+ add r5.xyz, r2.wwww, r5.xyzx
+ movc r3.xyz, r4.yyyy, r5.xyzx, r3.xyzx
+ lt r3.w, l(1.000000), r4.x
+ add r4.yzw, -r2.wwww, r3.xxyz
+ add r5.x, -r2.w, l(1.000000)
+ mul r4.yzw, r4.yyzw, r5.xxxx
+ add r4.x, -r2.w, r4.x
+ div r4.xyz, r4.yzwy, r4.xxxx
+ add r4.xyz, r2.wwww, r4.xyzx
+ movc r2.xyz, r3.wwww, r4.xyzx, r3.xyzx
+ else
+ ieq r2.w, cb0[0].x, l(14)
+ if_nz r2.w
+ dp3 r2.w, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ dp3 r3.x, r0.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ add r2.w, r2.w, -r3.x
+ add r3.xyz, r0.xyzx, r2.wwww
+ dp3 r2.w, r3.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ min r3.w, r3.y, r3.x
+ min r3.w, r3.z, r3.w
+ max r4.x, r3.y, r3.x
+ max r4.x, r3.z, r4.x
+ lt r4.y, r3.w, l(0.000000)
+ add r5.xyz, -r2.wwww, r3.xyzx
+ mul r5.xyz, r2.wwww, r5.xyzx
+ add r3.w, r2.w, -r3.w
+ div r5.xyz, r5.xyzx, r3.wwww
+ add r5.xyz, r2.wwww, r5.xyzx
+ movc r3.xyz, r4.yyyy, r5.xyzx, r3.xyzx
+ lt r3.w, l(1.000000), r4.x
+ add r4.yzw, -r2.wwww, r3.xxyz
+ add r5.x, -r2.w, l(1.000000)
+ mul r4.yzw, r4.yyzw, r5.xxxx
+ add r4.x, -r2.w, r4.x
+ div r4.xyz, r4.yzwy, r4.xxxx
+ add r4.xyz, r2.wwww, r4.xyzx
+ movc r2.xyz, r3.wwww, r4.xyzx, r3.xyzx
+ else
+ dp3 r2.w, r0.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ dp3 r3.x, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ add r2.w, r2.w, -r3.x
+ add r1.xyz, r1.xyzx, r2.wwww
+ dp3 r2.w, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000)
+ min r3.x, r1.y, r1.x
+ min r3.x, r1.z, r3.x
+ max r3.y, r1.y, r1.x
+ max r3.y, r1.z, r3.y
+ lt r3.z, r3.x, l(0.000000)
+ add r4.xyz, r1.xyzx, -r2.wwww
+ mul r4.xyz, r2.wwww, r4.xyzx
+ add r3.x, r2.w, -r3.x
+ div r4.xyz, r4.xyzx, r3.xxxx
+ add r4.xyz, r2.wwww, r4.xyzx
+ movc r1.xyz, r3.zzzz, r4.xyzx, r1.xyzx
+ lt r3.x, l(1.000000), r3.y
+ add r4.xyz, -r2.wwww, r1.xyzx
+ add r3.z, -r2.w, l(1.000000)
+ mul r4.xyz, r3.zzzz, r4.xyzx
+ add r3.y, -r2.w, r3.y
+ div r3.yzw, r4.xxyz, r3.yyyy
+ add r3.yzw, r2.wwww, r3.yyzw
+ movc r2.xyz, r3.xxxx, r3.yzwy, r1.xyzx
+ endif
+ endif
+ endif
+ add r1.x, -r1.w, l(1.000000)
+ mul r1.yzw, r1.wwww, r2.xxyz
+ mad r0.xyz, r1.xxxx, r0.xyzx, r1.yzwy
+ mul o0.xyz, r0.wwww, r0.xyzx
+ mov o0.w, r0.w
+ ret
+ // Approximately 195 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleRadialGradient
+{
+ pass APos
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44
+ // float2 dimensions; // Offset: 48 Size: 8
+ // float3 diff; // Offset: 64 Size: 12 [unused]
+ // float2 center1; // Offset: 80 Size: 8 [unused]
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4 [unused]
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb2 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused]
+ // float2 dimensions; // Offset: 48 Size: 8 [unused]
+ // float3 diff; // Offset: 64 Size: 12
+ // float2 center1; // Offset: 80 Size: 8
+ // float A; // Offset: 88 Size: 4
+ // float radius1; // Offset: 92 Size: 4
+ // float sq_radius1; // Offset: 96 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb2 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 4 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c3, 0.5, 0, 0, 0
+ def c4, 1, -1, 0, -0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.xy, t0.wzzw, -c1
+ dp2add r0.w, r0, r0, -c2.x
+ mul r0.w, r0.w, c1.z
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ mad r0.y, r0.x, r0.x, -r0.w
+ abs r0.z, r0.y
+ rsq r0.z, r0.z
+ rcp r1.x, r0.z
+ mov r1.yz, -r1.x
+ add r0.xzw, r0.x, r1.xyyz
+ rcp r1.x, c1.z
+ mul r0.xzw, r0, r1.x
+ mov r1.w, c1.w
+ mad r1.xyz, r0.xzww, c0.z, r1.w
+ cmp r2.x, r1.x, r0.x, r0.w
+ cmp r0.xzw, r1.xyyz, c4.xyxy, c4.zyzw
+ mov r2.y, c3.x
+ texld r1, t0, s1
+ texld r2, r2, s0
+ mul r2.xyz, r2.w, r2
+ mul r1, r1.w, r2
+ add r0.w, r0.w, r0.x
+ cmp r0.x, r0.w, r0.x, r0.z
+ cmp r1, -r0.x, c4.z, r1
+ cmp r0, r0.y, r1, c4.z
+ mov oC0, r0
+
+ // approximately 28 instruction slots used (2 texture, 26 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[7], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 3
+ add r0.xy, v1.zwzz, -cb0[5].xyxx
+ mov r0.z, cb0[5].w
+ dp3 r0.z, r0.xyzx, cb0[4].xyzx
+ dp2 r0.x, r0.xyxx, r0.xyxx
+ add r0.x, r0.x, -cb0[6].x
+ mul r0.x, r0.x, cb0[5].z
+ mad r0.x, r0.z, r0.z, -r0.x
+ lt r0.y, r0.x, l(0.000000)
+ sqrt r1.x, |r0.x|
+ mov r1.y, -r1.x
+ add r0.xz, r0.zzzz, r1.xxyx
+ div r0.xz, r0.xxzx, cb0[5].zzzz
+ mul r1.xy, r0.xzxx, cb0[4].zzzz
+ ge r1.xy, r1.xyxx, -cb0[5].wwww
+ and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0)
+ add r0.x, -r0.z, r0.x
+ mad r2.x, r1.x, r0.x, r0.z
+ mov r2.y, l(0.500000)
+ sample r2.xyzw, r2.xyxx, t0.xyzw, s0
+ if_nz r0.y
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ max r0.x, r1.y, r1.x
+ ge r0.x, l(0.000000), r0.x
+ if_nz r0.x
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ mul r2.xyz, r2.wwww, r2.xyzx
+ sample r0.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.wwww, r2.xyzw
+ ret
+ // Approximately 33 instruction slots used
+
+ };
+ }
+
+ pass A0
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44
+ // float2 dimensions; // Offset: 48 Size: 8
+ // float3 diff; // Offset: 64 Size: 12 [unused]
+ // float2 center1; // Offset: 80 Size: 8 [unused]
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4 [unused]
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb2 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused]
+ // float2 dimensions; // Offset: 48 Size: 8 [unused]
+ // float3 diff; // Offset: 64 Size: 12
+ // float2 center1; // Offset: 80 Size: 8
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb2 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 4 2 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c2, 0.5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mul r0.w, c1.w, c1.w
+ add r0.xy, t0.wzzw, -c1
+ dp2add r0.w, r0, r0, -r0.w
+ mul r0.w, r0.w, c2.x
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ rcp r0.x, r0.x
+ mul r0.x, r0.x, r0.w
+ mov r0.y, c2.x
+ texld r1, t0, s1
+ texld r2, r0, s0
+ mov r0.w, c1.w
+ mad r0.x, r0.x, -c0.z, -r0.w
+ mul r2.xyz, r2.w, r2
+ mul r1, r1.w, r2
+ cmp r0, r0.x, c2.y, r1
+ mov oC0, r0
+
+ // approximately 18 instruction slots used (2 texture, 16 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[6], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ add r0.xy, v1.zwzz, -cb0[5].xyxx
+ mov r0.z, cb0[5].w
+ dp3 r0.z, r0.xyzx, cb0[4].xyzx
+ dp2 r0.x, r0.xyxx, r0.xyxx
+ mad r0.x, -cb0[5].w, cb0[5].w, r0.x
+ mul r0.x, r0.x, l(0.500000)
+ div r0.x, r0.x, r0.z
+ mul r0.z, r0.x, cb0[4].z
+ ge r0.z, -cb0[5].w, r0.z
+ mov r0.y, l(0.500000)
+ sample r1.xyzw, r0.xyxx, t0.xyzw, s0
+ if_nz r0.z
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ mul r1.xyz, r1.wwww, r1.xyzx
+ sample r0.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.wwww, r1.xyzw
+ ret
+ // Approximately 19 instruction slots used
+
+ };
+ }
+
+ pass APosWrap
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44
+ // float2 dimensions; // Offset: 48 Size: 8
+ // float3 diff; // Offset: 64 Size: 12 [unused]
+ // float2 center1; // Offset: 80 Size: 8 [unused]
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4 [unused]
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb2 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused]
+ // float2 dimensions; // Offset: 48 Size: 8 [unused]
+ // float3 diff; // Offset: 64 Size: 12
+ // float2 center1; // Offset: 80 Size: 8
+ // float A; // Offset: 88 Size: 4
+ // float radius1; // Offset: 92 Size: 4
+ // float sq_radius1; // Offset: 96 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sWrapSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb2 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 4 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c3, 0.5, 0, 0, 0
+ def c4, 1, -1, 0, -0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.xy, t0.wzzw, -c1
+ dp2add r0.w, r0, r0, -c2.x
+ mul r0.w, r0.w, c1.z
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ mad r0.y, r0.x, r0.x, -r0.w
+ abs r0.z, r0.y
+ rsq r0.z, r0.z
+ rcp r1.x, r0.z
+ mov r1.yz, -r1.x
+ add r0.xzw, r0.x, r1.xyyz
+ rcp r1.x, c1.z
+ mul r0.xzw, r0, r1.x
+ mov r1.w, c1.w
+ mad r1.xyz, r0.xzww, c0.z, r1.w
+ cmp r2.x, r1.x, r0.x, r0.w
+ cmp r0.xzw, r1.xyyz, c4.xyxy, c4.zyzw
+ mov r2.y, c3.x
+ texld r1, t0, s1
+ texld r2, r2, s0
+ mul r2.xyz, r2.w, r2
+ mul r1, r1.w, r2
+ add r0.w, r0.w, r0.x
+ cmp r0.x, r0.w, r0.x, r0.z
+ cmp r1, -r0.x, c4.z, r1
+ cmp r0, r0.y, r1, c4.z
+ mov oC0, r0
+
+ // approximately 28 instruction slots used (2 texture, 26 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[7], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 3
+ add r0.xy, v1.zwzz, -cb0[5].xyxx
+ mov r0.z, cb0[5].w
+ dp3 r0.z, r0.xyzx, cb0[4].xyzx
+ dp2 r0.x, r0.xyxx, r0.xyxx
+ add r0.x, r0.x, -cb0[6].x
+ mul r0.x, r0.x, cb0[5].z
+ mad r0.x, r0.z, r0.z, -r0.x
+ lt r0.y, r0.x, l(0.000000)
+ sqrt r1.x, |r0.x|
+ mov r1.y, -r1.x
+ add r0.xz, r0.zzzz, r1.xxyx
+ div r0.xz, r0.xxzx, cb0[5].zzzz
+ mul r1.xy, r0.xzxx, cb0[4].zzzz
+ ge r1.xy, r1.xyxx, -cb0[5].wwww
+ and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0)
+ add r0.x, -r0.z, r0.x
+ mad r2.x, r1.x, r0.x, r0.z
+ mov r2.y, l(0.500000)
+ sample r2.xyzw, r2.xyxx, t0.xyzw, s0
+ if_nz r0.y
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ max r0.x, r1.y, r1.x
+ ge r0.x, l(0.000000), r0.x
+ if_nz r0.x
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ mul r2.xyz, r2.wwww, r2.xyzx
+ sample r0.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.wwww, r2.xyzw
+ ret
+ // Approximately 33 instruction slots used
+
+ };
+ }
+
+ pass A0Wrap
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44
+ // float2 dimensions; // Offset: 48 Size: 8
+ // float3 diff; // Offset: 64 Size: 12 [unused]
+ // float2 center1; // Offset: 80 Size: 8 [unused]
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4 [unused]
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb2 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused]
+ // float2 dimensions; // Offset: 48 Size: 8 [unused]
+ // float3 diff; // Offset: 64 Size: 12
+ // float2 center1; // Offset: 80 Size: 8
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sWrapSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb2 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 4 2 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c2, 0.5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mul r0.w, c1.w, c1.w
+ add r0.xy, t0.wzzw, -c1
+ dp2add r0.w, r0, r0, -r0.w
+ mul r0.w, r0.w, c2.x
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ rcp r0.x, r0.x
+ mul r0.x, r0.x, r0.w
+ mov r0.y, c2.x
+ texld r1, t0, s1
+ texld r2, r0, s0
+ mov r0.w, c1.w
+ mad r0.x, r0.x, -c0.z, -r0.w
+ mul r2.xyz, r2.w, r2
+ mul r1, r1.w, r2
+ cmp r0, r0.x, c2.y, r1
+ mov oC0, r0
+
+ // approximately 18 instruction slots used (2 texture, 16 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[6], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ add r0.xy, v1.zwzz, -cb0[5].xyxx
+ mov r0.z, cb0[5].w
+ dp3 r0.z, r0.xyzx, cb0[4].xyzx
+ dp2 r0.x, r0.xyxx, r0.xyxx
+ mad r0.x, -cb0[5].w, cb0[5].w, r0.x
+ mul r0.x, r0.x, l(0.500000)
+ div r0.x, r0.x, r0.z
+ mul r0.z, r0.x, cb0[4].z
+ ge r0.z, -cb0[5].w, r0.z
+ mov r0.y, l(0.500000)
+ sample r1.xyzw, r0.xyxx, t0.xyzw, s0
+ if_nz r0.z
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ mul r1.xyz, r1.wwww, r1.xyzx
+ sample r0.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.wwww, r1.xyzw
+ ret
+ // Approximately 19 instruction slots used
+
+ };
+ }
+
+ pass APosMirror
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44
+ // float2 dimensions; // Offset: 48 Size: 8
+ // float3 diff; // Offset: 64 Size: 12 [unused]
+ // float2 center1; // Offset: 80 Size: 8 [unused]
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4 [unused]
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb2 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused]
+ // float2 dimensions; // Offset: 48 Size: 8 [unused]
+ // float3 diff; // Offset: 64 Size: 12
+ // float2 center1; // Offset: 80 Size: 8
+ // float A; // Offset: 88 Size: 4
+ // float radius1; // Offset: 92 Size: 4
+ // float sq_radius1; // Offset: 96 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sMirrorSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb2 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 4 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c3, 0.5, 0, 0, 0
+ def c4, 1, -1, 0, -0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.xy, t0.wzzw, -c1
+ dp2add r0.w, r0, r0, -c2.x
+ mul r0.w, r0.w, c1.z
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ mad r0.y, r0.x, r0.x, -r0.w
+ abs r0.z, r0.y
+ rsq r0.z, r0.z
+ rcp r1.x, r0.z
+ mov r1.yz, -r1.x
+ add r0.xzw, r0.x, r1.xyyz
+ rcp r1.x, c1.z
+ mul r0.xzw, r0, r1.x
+ mov r1.w, c1.w
+ mad r1.xyz, r0.xzww, c0.z, r1.w
+ cmp r2.x, r1.x, r0.x, r0.w
+ cmp r0.xzw, r1.xyyz, c4.xyxy, c4.zyzw
+ mov r2.y, c3.x
+ texld r1, t0, s1
+ texld r2, r2, s0
+ mul r2.xyz, r2.w, r2
+ mul r1, r1.w, r2
+ add r0.w, r0.w, r0.x
+ cmp r0.x, r0.w, r0.x, r0.z
+ cmp r1, -r0.x, c4.z, r1
+ cmp r0, r0.y, r1, c4.z
+ mov oC0, r0
+
+ // approximately 28 instruction slots used (2 texture, 26 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[7], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 3
+ add r0.xy, v1.zwzz, -cb0[5].xyxx
+ mov r0.z, cb0[5].w
+ dp3 r0.z, r0.xyzx, cb0[4].xyzx
+ dp2 r0.x, r0.xyxx, r0.xyxx
+ add r0.x, r0.x, -cb0[6].x
+ mul r0.x, r0.x, cb0[5].z
+ mad r0.x, r0.z, r0.z, -r0.x
+ lt r0.y, r0.x, l(0.000000)
+ sqrt r1.x, |r0.x|
+ mov r1.y, -r1.x
+ add r0.xz, r0.zzzz, r1.xxyx
+ div r0.xz, r0.xxzx, cb0[5].zzzz
+ mul r1.xy, r0.xzxx, cb0[4].zzzz
+ ge r1.xy, r1.xyxx, -cb0[5].wwww
+ and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0)
+ add r0.x, -r0.z, r0.x
+ mad r2.x, r1.x, r0.x, r0.z
+ mov r2.y, l(0.500000)
+ sample r2.xyzw, r2.xyxx, t0.xyzw, s0
+ if_nz r0.y
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ max r0.x, r1.y, r1.x
+ ge r0.x, l(0.000000), r0.x
+ if_nz r0.x
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ mul r2.xyz, r2.wwww, r2.xyzx
+ sample r0.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.wwww, r2.xyzw
+ ret
+ // Approximately 33 instruction slots used
+
+ };
+ }
+
+ pass A0Mirror
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44
+ // float2 dimensions; // Offset: 48 Size: 8
+ // float3 diff; // Offset: 64 Size: 12 [unused]
+ // float2 center1; // Offset: 80 Size: 8 [unused]
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4 [unused]
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb2 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb2
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused]
+ // float2 dimensions; // Offset: 48 Size: 8 [unused]
+ // float3 diff; // Offset: 64 Size: 12
+ // float2 center1; // Offset: 80 Size: 8
+ // float A; // Offset: 88 Size: 4 [unused]
+ // float radius1; // Offset: 92 Size: 4
+ // float sq_radius1; // Offset: 96 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sMirrorSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb2 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 4 2 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c2, 0.5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mul r0.w, c1.w, c1.w
+ add r0.xy, t0.wzzw, -c1
+ dp2add r0.w, r0, r0, -r0.w
+ mul r0.w, r0.w, c2.x
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ rcp r0.x, r0.x
+ mul r0.x, r0.x, r0.w
+ mov r0.y, c2.x
+ texld r1, t0, s1
+ texld r2, r0, s0
+ mov r0.w, c1.w
+ mad r0.x, r0.x, -c0.z, -r0.w
+ mul r2.xyz, r2.w, r2
+ mul r1, r1.w, r2
+ cmp r0, r0.x, c2.y, r1
+ mov oC0, r0
+
+ // approximately 18 instruction slots used (2 texture, 16 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[6], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ add r0.xy, v1.zwzz, -cb0[5].xyxx
+ mov r0.z, cb0[5].w
+ dp3 r0.z, r0.xyzx, cb0[4].xyzx
+ dp2 r0.x, r0.xyxx, r0.xyxx
+ mad r0.x, -cb0[5].w, cb0[5].w, r0.x
+ mul r0.x, r0.x, l(0.500000)
+ div r0.x, r0.x, r0.z
+ mul r0.z, r0.x, cb0[4].z
+ ge r0.z, -cb0[5].w, r0.z
+ mov r0.y, l(0.500000)
+ sample r1.xyzw, r0.xyxx, t0.xyzw, s0
+ if_nz r0.z
+ mov o0.xyzw, l(0,0,0,0)
+ ret
+ endif
+ mul r1.xyz, r1.wwww, r1.xyzx
+ sample r0.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.wwww, r1.xyzw
+ ret
+ // Approximately 19 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleConicGradient
+{
+ pass APos
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb3
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44
+ // float2 dimensions_cb3; // Offset: 48 Size: 8
+ // float2 center; // Offset: 56 Size: 8 [unused]
+ // float angle; // Offset: 64 Size: 4 [unused]
+ // float start_offset; // Offset: 68 Size: 4 [unused]
+ // float end_offset; // Offset: 72 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb3 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb3
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 [unused]
+ // float2 dimensions_cb3; // Offset: 48 Size: 8 [unused]
+ // float2 center; // Offset: 56 Size: 8
+ // float angle; // Offset: 64 Size: 4
+ // float start_offset; // Offset: 68 Size: 4
+ // float end_offset; // Offset: 72 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb3 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 2 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c2, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497
+ def c3, 0.999866009, 0, 1, 3.14159274
+ def c4, -2, 1.57079637, 1.57079601, 0.159154981
+ def c5, 0.5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.xy, t0.wzzw, -c0.zwzw
+ abs r0.zw, r0.xyxy
+ add r1.xy, -r0.zwzw, r0.wzzw
+ cmp r0.zw, r1.x, r0, r0.xywz
+ cmp r1.x, r1.y, c3.y, c3.z
+ rcp r0.w, r0.w
+ mul r0.z, r0.w, r0.z
+ mul r0.w, r0.z, r0.z
+ mad r1.y, r0.w, c2.x, c2.y
+ mad r1.y, r0.w, r1.y, c2.z
+ mad r1.y, r0.w, r1.y, c2.w
+ mad r0.w, r0.w, r1.y, c3.x
+ mul r0.z, r0.w, r0.z
+ mad r0.w, r0.z, c4.x, c4.y
+ mad r0.z, r0.w, r1.x, r0.z
+ cmp r0.w, r0.x, -c3.y, -c3.w
+ add r0.z, r0.w, r0.z
+ add r0.w, r0.z, r0.z
+ add r1.x, -r0.x, r0.y
+ cmp r0.xy, r1.x, r0, r0.yxzw
+ cmp r0.y, r0.y, c3.z, c3.y
+ cmp r0.x, r0.x, c3.y, r0.y
+ mad r0.x, r0.x, -r0.w, r0.z
+ add r0.x, r0.x, -c1.x
+ add r0.x, r0.x, c4.z
+ mul r0.y, r0.x, c4.w
+ abs r0.y, r0.y
+ frc r0.y, r0.y
+ cmp r0.x, r0.x, r0.y, -r0.y
+ add r0.x, r0.x, -c1.y
+ add r0.y, -c1.y, c1.z
+ rcp r0.y, r0.y
+ mul r0.x, r0.y, r0.x
+ mov r0.y, c5.x
+ texld r1, t0, s1
+ texld r0, r0, s0
+ mul r0.xyz, r0.w, r0
+ mul r0, r1.w, r0
+ mov oC0, r0
+
+ // approximately 39 instruction slots used (2 texture, 37 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[5], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ add r0.xy, v1.wzww, -cb0[3].wzww
+ max r0.z, |r0.y|, |r0.x|
+ div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z
+ min r0.w, |r0.y|, |r0.x|
+ mul r0.z, r0.z, r0.w
+ mul r0.w, r0.z, r0.z
+ mad r1.x, r0.w, l(0.020835), l(-0.085133)
+ mad r1.x, r0.w, r1.x, l(0.180141)
+ mad r1.x, r0.w, r1.x, l(-0.330299)
+ mad r0.w, r0.w, r1.x, l(0.999866)
+ mul r1.x, r0.w, r0.z
+ mad r1.x, r1.x, l(-2.000000), l(1.570796)
+ lt r1.y, |r0.y|, |r0.x|
+ and r1.x, r1.y, r1.x
+ mad r0.z, r0.z, r0.w, r1.x
+ lt r0.w, r0.y, -r0.y
+ and r0.w, r0.w, l(0xc0490fdb)
+ add r0.z, r0.w, r0.z
+ min r0.w, r0.y, r0.x
+ max r0.x, r0.y, r0.x
+ ge r0.x, r0.x, -r0.x
+ lt r0.y, r0.w, -r0.w
+ and r0.x, r0.x, r0.y
+ movc r0.x, r0.x, -r0.z, r0.z
+ add r0.x, r0.x, -cb0[4].x
+ add r0.x, r0.x, l(1.570796)
+ mul r0.x, r0.x, l(0.159155)
+ ge r0.y, r0.x, -r0.x
+ frc r0.x, |r0.x|
+ movc r0.x, r0.y, r0.x, -r0.x
+ add r0.x, r0.x, -cb0[4].y
+ add r0.y, -cb0[4].y, cb0[4].z
+ div r0.x, r0.x, r0.y
+ mov r0.y, l(0.500000)
+ sample r0.xyzw, r0.xyxx, t0.xyzw, s0
+ mul r0.xyz, r0.wwww, r0.xyzx
+ sample r1.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.xyzw, r1.wwww
+ ret
+ // Approximately 39 instruction slots used
+
+ };
+ }
+
+ pass APosWrap
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb3
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44
+ // float2 dimensions_cb3; // Offset: 48 Size: 8
+ // float2 center; // Offset: 56 Size: 8 [unused]
+ // float angle; // Offset: 64 Size: 4 [unused]
+ // float start_offset; // Offset: 68 Size: 4 [unused]
+ // float end_offset; // Offset: 72 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb3 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb3
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 [unused]
+ // float2 dimensions_cb3; // Offset: 48 Size: 8 [unused]
+ // float2 center; // Offset: 56 Size: 8
+ // float angle; // Offset: 64 Size: 4
+ // float start_offset; // Offset: 68 Size: 4
+ // float end_offset; // Offset: 72 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sWrapSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb3 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 2 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c2, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497
+ def c3, 0.999866009, 0, 1, 3.14159274
+ def c4, -2, 1.57079637, 1.57079601, 0.159154981
+ def c5, 0.5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.xy, t0.wzzw, -c0.zwzw
+ abs r0.zw, r0.xyxy
+ add r1.xy, -r0.zwzw, r0.wzzw
+ cmp r0.zw, r1.x, r0, r0.xywz
+ cmp r1.x, r1.y, c3.y, c3.z
+ rcp r0.w, r0.w
+ mul r0.z, r0.w, r0.z
+ mul r0.w, r0.z, r0.z
+ mad r1.y, r0.w, c2.x, c2.y
+ mad r1.y, r0.w, r1.y, c2.z
+ mad r1.y, r0.w, r1.y, c2.w
+ mad r0.w, r0.w, r1.y, c3.x
+ mul r0.z, r0.w, r0.z
+ mad r0.w, r0.z, c4.x, c4.y
+ mad r0.z, r0.w, r1.x, r0.z
+ cmp r0.w, r0.x, -c3.y, -c3.w
+ add r0.z, r0.w, r0.z
+ add r0.w, r0.z, r0.z
+ add r1.x, -r0.x, r0.y
+ cmp r0.xy, r1.x, r0, r0.yxzw
+ cmp r0.y, r0.y, c3.z, c3.y
+ cmp r0.x, r0.x, c3.y, r0.y
+ mad r0.x, r0.x, -r0.w, r0.z
+ add r0.x, r0.x, -c1.x
+ add r0.x, r0.x, c4.z
+ mul r0.y, r0.x, c4.w
+ abs r0.y, r0.y
+ frc r0.y, r0.y
+ cmp r0.x, r0.x, r0.y, -r0.y
+ add r0.x, r0.x, -c1.y
+ add r0.y, -c1.y, c1.z
+ rcp r0.y, r0.y
+ mul r0.x, r0.y, r0.x
+ mov r0.y, c5.x
+ texld r1, t0, s1
+ texld r0, r0, s0
+ mul r0.xyz, r0.w, r0
+ mul r0, r1.w, r0
+ mov oC0, r0
+
+ // approximately 39 instruction slots used (2 texture, 37 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[5], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ add r0.xy, v1.wzww, -cb0[3].wzww
+ max r0.z, |r0.y|, |r0.x|
+ div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z
+ min r0.w, |r0.y|, |r0.x|
+ mul r0.z, r0.z, r0.w
+ mul r0.w, r0.z, r0.z
+ mad r1.x, r0.w, l(0.020835), l(-0.085133)
+ mad r1.x, r0.w, r1.x, l(0.180141)
+ mad r1.x, r0.w, r1.x, l(-0.330299)
+ mad r0.w, r0.w, r1.x, l(0.999866)
+ mul r1.x, r0.w, r0.z
+ mad r1.x, r1.x, l(-2.000000), l(1.570796)
+ lt r1.y, |r0.y|, |r0.x|
+ and r1.x, r1.y, r1.x
+ mad r0.z, r0.z, r0.w, r1.x
+ lt r0.w, r0.y, -r0.y
+ and r0.w, r0.w, l(0xc0490fdb)
+ add r0.z, r0.w, r0.z
+ min r0.w, r0.y, r0.x
+ max r0.x, r0.y, r0.x
+ ge r0.x, r0.x, -r0.x
+ lt r0.y, r0.w, -r0.w
+ and r0.x, r0.x, r0.y
+ movc r0.x, r0.x, -r0.z, r0.z
+ add r0.x, r0.x, -cb0[4].x
+ add r0.x, r0.x, l(1.570796)
+ mul r0.x, r0.x, l(0.159155)
+ ge r0.y, r0.x, -r0.x
+ frc r0.x, |r0.x|
+ movc r0.x, r0.y, r0.x, -r0.x
+ add r0.x, r0.x, -cb0[4].y
+ add r0.y, -cb0[4].y, cb0[4].z
+ div r0.x, r0.x, r0.y
+ mov r0.y, l(0.500000)
+ sample r0.xyzw, r0.xyxx, t0.xyzw, s0
+ mul r0.xyz, r0.wwww, r0.xyzx
+ sample r1.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.xyzw, r1.wwww
+ ret
+ // Approximately 39 instruction slots used
+
+ };
+ }
+
+ pass APosMirror
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ // cbuffer cb3
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44
+ // float2 dimensions_cb3; // Offset: 48 Size: 8
+ // float2 center; // Offset: 56 Size: 8 [unused]
+ // float angle; // Offset: 64 Size: 4 [unused]
+ // float start_offset; // Offset: 68 Size: 4 [unused]
+ // float end_offset; // Offset: 72 Size: 4 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ // cb3 cbuffer NA NA 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 1 ( FLT, FLT, FLT, FLT)
+ // c2 cb0 2 1 ( FLT, FLT, FLT, FLT)
+ // c3 cb1 0 2 ( FLT, FLT, FLT, FLT)
+ // c5 cb1 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c6, 1, 0.5, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad r0.xy, v0, c1.zwzw, c1
+ add r0.z, r0.x, c6.x
+ mul r0.z, r0.z, c5.x
+ mul r1.x, r0.z, c6.y
+ add r0.z, -r0.y, c6.x
+ add oPos.xy, r0, c0
+ mul r0.x, r0.z, c5.y
+ mul r1.y, r0.x, c6.y
+ mov r1.z, c6.x
+ dp3 oT0.w, r1, c3
+ dp3 oT0.z, r1, c4
+ mov oPos.zw, c6.xyzx
+
+ // approximately 13 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_constantbuffer cb1[4], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ dcl_temps 2
+ mov o0.zw, l(0,0,0,1.000000)
+ mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.xy, r0.xyxx
+ add r0.x, r0.x, l(1.000000)
+ add r0.y, -r0.y, l(1.000000)
+ mul r0.xy, r0.xyxx, cb1[3].xyxx
+ mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
+ mov r1.z, l(1.000000)
+ dp3 o1.z, r1.xyzx, cb1[0].xyzx
+ dp3 o1.w, r1.xyzx, cb1[1].xyzx
+ mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx
+ ret
+ // Approximately 12 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb3
+ // {
+ //
+ // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 [unused]
+ // float2 dimensions_cb3; // Offset: 48 Size: 8 [unused]
+ // float2 center; // Offset: 56 Size: 8
+ // float angle; // Offset: 64 Size: 4
+ // float start_offset; // Offset: 68 Size: 4
+ // float end_offset; // Offset: 72 Size: 4
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sMirrorSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb3 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 2 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c2, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497
+ def c3, 0.999866009, 0, 1, 3.14159274
+ def c4, -2, 1.57079637, 1.57079601, 0.159154981
+ def c5, 0.5, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.xy, t0.wzzw, -c0.zwzw
+ abs r0.zw, r0.xyxy
+ add r1.xy, -r0.zwzw, r0.wzzw
+ cmp r0.zw, r1.x, r0, r0.xywz
+ cmp r1.x, r1.y, c3.y, c3.z
+ rcp r0.w, r0.w
+ mul r0.z, r0.w, r0.z
+ mul r0.w, r0.z, r0.z
+ mad r1.y, r0.w, c2.x, c2.y
+ mad r1.y, r0.w, r1.y, c2.z
+ mad r1.y, r0.w, r1.y, c2.w
+ mad r0.w, r0.w, r1.y, c3.x
+ mul r0.z, r0.w, r0.z
+ mad r0.w, r0.z, c4.x, c4.y
+ mad r0.z, r0.w, r1.x, r0.z
+ cmp r0.w, r0.x, -c3.y, -c3.w
+ add r0.z, r0.w, r0.z
+ add r0.w, r0.z, r0.z
+ add r1.x, -r0.x, r0.y
+ cmp r0.xy, r1.x, r0, r0.yxzw
+ cmp r0.y, r0.y, c3.z, c3.y
+ cmp r0.x, r0.x, c3.y, r0.y
+ mad r0.x, r0.x, -r0.w, r0.z
+ add r0.x, r0.x, -c1.x
+ add r0.x, r0.x, c4.z
+ mul r0.y, r0.x, c4.w
+ abs r0.y, r0.y
+ frc r0.y, r0.y
+ cmp r0.x, r0.x, r0.y, -r0.y
+ add r0.x, r0.x, -c1.y
+ add r0.y, -c1.y, c1.z
+ rcp r0.y, r0.y
+ mul r0.x, r0.y, r0.x
+ mov r0.y, c5.x
+ texld r1, t0, s1
+ texld r0, r0, s0
+ mul r0.xyz, r0.w, r0
+ mul r0, r1.w, r0
+ mov oC0, r0
+
+ // approximately 39 instruction slots used (2 texture, 37 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[5], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ add r0.xy, v1.wzww, -cb0[3].wzww
+ max r0.z, |r0.y|, |r0.x|
+ div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z
+ min r0.w, |r0.y|, |r0.x|
+ mul r0.z, r0.z, r0.w
+ mul r0.w, r0.z, r0.z
+ mad r1.x, r0.w, l(0.020835), l(-0.085133)
+ mad r1.x, r0.w, r1.x, l(0.180141)
+ mad r1.x, r0.w, r1.x, l(-0.330299)
+ mad r0.w, r0.w, r1.x, l(0.999866)
+ mul r1.x, r0.w, r0.z
+ mad r1.x, r1.x, l(-2.000000), l(1.570796)
+ lt r1.y, |r0.y|, |r0.x|
+ and r1.x, r1.y, r1.x
+ mad r0.z, r0.z, r0.w, r1.x
+ lt r0.w, r0.y, -r0.y
+ and r0.w, r0.w, l(0xc0490fdb)
+ add r0.z, r0.w, r0.z
+ min r0.w, r0.y, r0.x
+ max r0.x, r0.y, r0.x
+ ge r0.x, r0.x, -r0.x
+ lt r0.y, r0.w, -r0.w
+ and r0.x, r0.x, r0.y
+ movc r0.x, r0.x, -r0.z, r0.z
+ add r0.x, r0.x, -cb0[4].x
+ add r0.x, r0.x, l(1.570796)
+ mul r0.x, r0.x, l(0.159155)
+ ge r0.y, r0.x, -r0.x
+ frc r0.x, |r0.x|
+ movc r0.x, r0.y, r0.x, -r0.x
+ add r0.x, r0.x, -cb0[4].y
+ add r0.y, -cb0[4].y, cb0[4].z
+ div r0.x, r0.x, r0.y
+ mov r0.y, l(0.500000)
+ sample r0.xyzw, r0.xyxx, t0.xyzw, s0
+ mul r0.xyz, r0.wwww, r0.xyzx
+ sample r1.xyzw, v1.xyxx, t1.xyzw, s1
+ mul o0.xyzw, r0.xyzw, r1.wwww
+ ret
+ // Approximately 39 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleMaskedTexture
+{
+ pass P0
+ {
+ RasterizerState = TextureRast;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mov r0.xy, t0.wzzw
+ texld r1, t0, s0
+ texld r0, r0, s1
+ mul r0, r0.w, r1
+ mov oC0, r0
+
+ // approximately 5 instruction slots used (2 texture, 3 arithmetic)
+ ps_4_0
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 2
+ sample r0.xyzw, v1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, v1.zwzz, t1.xyzw, s1
+ mul o0.xyzw, r0.xyzw, r1.wwww
+ ret
+ // Approximately 4 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleTextureWithShadow
+{
+ pass P0
+ {
+ RasterizerState = TextureRast;
+ AB_BlendFactor = float4(1, 1, 1, 1);
+ AB_SampleMask = uint(0xffffffff);
+ BlendState = ShadowBlendH;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb1
+ // {
+ //
+ // float4 BlurOffsetsH[3]; // Offset: 0 Size: 48
+ // float4 BlurOffsetsV[3]; // Offset: 48 Size: 48 [unused]
+ // float4 BlurWeights[3]; // Offset: 96 Size: 48
+ // float4 ShadowColor; // Offset: 144 Size: 16
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sShadowSampler sampler NA NA 0 1
+ // tex texture float4 2d 0 1
+ // cb1 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ // c3 cb0 6 4 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ dcl t0
+ dcl_2d s0
+ add r0.x, t0.x, c0.y
+ mov r0.y, t0.y
+ add r1.x, t0.x, c0.x
+ mov r1.y, t0.y
+ texld r0, r0, s0
+ texld r1, r1, s0
+ mul r0.x, r0.w, c3.y
+ mad r0.x, c3.x, r1.w, r0.x
+ add r1.x, t0.x, c0.z
+ mov r1.y, t0.y
+ add r2.x, t0.x, c0.w
+ mov r2.y, t0.y
+ texld r1, r1, s0
+ texld r2, r2, s0
+ mad r0.x, c3.z, r1.w, r0.x
+ mad r0.x, c3.w, r2.w, r0.x
+ add r1.x, t0.x, c1.x
+ mov r1.y, t0.y
+ add r2.x, t0.x, c1.y
+ mov r2.y, t0.y
+ texld r1, r1, s0
+ texld r2, r2, s0
+ mad r0.x, c4.x, r1.w, r0.x
+ mad r0.x, c4.y, r2.w, r0.x
+ add r1.x, t0.x, c1.z
+ mov r1.y, t0.y
+ add r2.x, t0.x, c1.w
+ mov r2.y, t0.y
+ texld r1, r1, s0
+ texld r2, r2, s0
+ mad r0.x, c4.z, r1.w, r0.x
+ mad r0.x, c4.w, r2.w, r0.x
+ add r1.x, t0.x, c2.x
+ mov r1.y, t0.y
+ texld r1, r1, s0
+ mad r0.x, c5.x, r1.w, r0.x
+ mul r0, r0.x, c6
+ mov oC0, r0
+
+ // approximately 38 instruction slots used (9 texture, 29 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[10], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ dcl_temps 4
+ add r0.xyzw, v1.xxxx, cb0[0].zxwy
+ mov r1.xz, r0.yywy
+ mov r1.yw, v1.yyyy
+ sample r2.xyzw, r1.zwzz, t0.xyzw, s0
+ sample r1.xyzw, r1.xyxx, t0.xyzw, s0
+ mul r1.x, r2.w, cb0[6].y
+ mad r1.x, cb0[6].x, r1.w, r1.x
+ mov r0.yw, v1.yyyy
+ sample r2.xyzw, r0.xyxx, t0.xyzw, s0
+ sample r0.xyzw, r0.zwzz, t0.xyzw, s0
+ mad r0.x, cb0[6].z, r2.w, r1.x
+ mad r0.x, cb0[6].w, r0.w, r0.x
+ add r1.xyzw, v1.xxxx, cb0[1].zxwy
+ mov r2.xz, r1.yywy
+ mov r2.yw, v1.yyyy
+ sample r3.xyzw, r2.xyxx, t0.xyzw, s0
+ sample r2.xyzw, r2.zwzz, t0.xyzw, s0
+ mad r0.x, cb0[7].x, r3.w, r0.x
+ mad r0.x, cb0[7].y, r2.w, r0.x
+ mov r1.yw, v1.yyyy
+ sample r2.xyzw, r1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, r1.zwzz, t0.xyzw, s0
+ mad r0.x, cb0[7].z, r2.w, r0.x
+ mad r0.x, cb0[7].w, r1.w, r0.x
+ add r1.x, v1.x, cb0[2].x
+ mov r1.y, v1.y
+ sample r1.xyzw, r1.xyxx, t0.xyzw, s0
+ mad r0.x, cb0[8].x, r1.w, r0.x
+ mul o0.xyzw, r0.xxxx, cb0[9].xyzw
+ ret
+ // Approximately 30 instruction slots used
+
+ };
+ }
+
+ pass P1
+ {
+ RasterizerState = TextureRast;
+ AB_BlendFactor = float4(1, 1, 1, 1);
+ AB_SampleMask = uint(0xffffffff);
+ BlendState = ShadowBlendV;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb1
+ // {
+ //
+ // float4 BlurOffsetsH[3]; // Offset: 0 Size: 48 [unused]
+ // float4 BlurOffsetsV[3]; // Offset: 48 Size: 48
+ // float4 BlurWeights[3]; // Offset: 96 Size: 48
+ // float4 ShadowColor; // Offset: 144 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sShadowSampler sampler NA NA 0 1
+ // tex texture float4 2d 0 1
+ // cb1 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 6 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ dcl t0
+ dcl_2d s0
+ add r0.y, t0.y, c0.y
+ mov r0.x, t0.x
+ add r1.y, t0.y, c0.x
+ mov r1.x, t0.x
+ texld r0, r0, s0
+ texld r1, r1, s0
+ mul r0, r0, c3.y
+ mad r0, c3.x, r1, r0
+ add r1.y, t0.y, c0.z
+ mov r1.x, t0.x
+ add r2.y, t0.y, c0.w
+ mov r2.x, t0.x
+ texld r1, r1, s0
+ texld r2, r2, s0
+ mad r0, c3.z, r1, r0
+ mad r0, c3.w, r2, r0
+ add r1.y, t0.y, c1.x
+ mov r1.x, t0.x
+ add r2.y, t0.y, c1.y
+ mov r2.x, t0.x
+ texld r1, r1, s0
+ texld r2, r2, s0
+ mad r0, c4.x, r1, r0
+ mad r0, c4.y, r2, r0
+ add r1.y, t0.y, c1.z
+ mov r1.x, t0.x
+ add r2.y, t0.y, c1.w
+ mov r2.x, t0.x
+ texld r1, r1, s0
+ texld r2, r2, s0
+ mad r0, c4.z, r1, r0
+ mad r0, c4.w, r2, r0
+ add r1.y, t0.y, c2.x
+ mov r1.x, t0.x
+ texld r1, r1, s0
+ mad r0, c5.x, r1, r0
+ mov oC0, r0
+
+ // approximately 37 instruction slots used (9 texture, 28 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[9], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ dcl_temps 4
+ mov r0.xz, v1.xxxx
+ add r1.xyzw, v1.yyyy, cb0[3].xzyw
+ mov r0.yw, r1.xxxz
+ sample r2.xyzw, r0.zwzz, t0.xyzw, s0
+ sample r0.xyzw, r0.xyxx, t0.xyzw, s0
+ mul r2.xyzw, r2.xyzw, cb0[6].yyyy
+ mad r0.xyzw, cb0[6].xxxx, r0.xyzw, r2.xyzw
+ mov r1.xz, v1.xxxx
+ sample r2.xyzw, r1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, r1.zwzz, t0.xyzw, s0
+ mad r0.xyzw, cb0[6].zzzz, r2.xyzw, r0.xyzw
+ mad r0.xyzw, cb0[6].wwww, r1.xyzw, r0.xyzw
+ mov r1.xz, v1.xxxx
+ add r2.xyzw, v1.yyyy, cb0[4].xzyw
+ mov r1.yw, r2.xxxz
+ sample r3.xyzw, r1.xyxx, t0.xyzw, s0
+ sample r1.xyzw, r1.zwzz, t0.xyzw, s0
+ mad r0.xyzw, cb0[7].xxxx, r3.xyzw, r0.xyzw
+ mad r0.xyzw, cb0[7].yyyy, r1.xyzw, r0.xyzw
+ mov r2.xz, v1.xxxx
+ sample r1.xyzw, r2.xyxx, t0.xyzw, s0
+ sample r2.xyzw, r2.zwzz, t0.xyzw, s0
+ mad r0.xyzw, cb0[7].zzzz, r1.xyzw, r0.xyzw
+ mad r0.xyzw, cb0[7].wwww, r2.xyzw, r0.xyzw
+ add r1.y, v1.y, cb0[5].x
+ mov r1.x, v1.x
+ sample r1.xyzw, r1.xyxx, t0.xyzw, s0
+ mad o0.xyzw, cb0[8].xxxx, r1.xyzw, r0.xyzw
+ ret
+ // Approximately 29 instruction slots used
+
+ };
+ }
+
+ pass P2
+ {
+ RasterizerState = TextureRast;
+ AB_BlendFactor = float4(1, 1, 1, 1);
+ AB_SampleMask = uint(0xffffffff);
+ BlendState = ShadowBlendV;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb1
+ // {
+ //
+ // float4 BlurOffsetsH[3]; // Offset: 0 Size: 48 [unused]
+ // float4 BlurOffsetsV[3]; // Offset: 48 Size: 48
+ // float4 BlurWeights[3]; // Offset: 96 Size: 48
+ // float4 ShadowColor; // Offset: 144 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sMaskSampler sampler NA NA 0 1
+ // sShadowSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb1 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 6 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t1
+ // s1 s1 t0
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ add r0.y, t0.y, c0.y
+ mov r0.x, t0.x
+ add r1.y, t0.y, c0.x
+ mov r1.x, t0.x
+ texld r0, r0, s1
+ texld r1, r1, s1
+ mul r0, r0, c3.y
+ mad r0, c3.x, r1, r0
+ add r1.y, t0.y, c0.z
+ mov r1.x, t0.x
+ add r2.y, t0.y, c0.w
+ mov r2.x, t0.x
+ texld r1, r1, s1
+ texld r2, r2, s1
+ mad r0, c3.z, r1, r0
+ mad r0, c3.w, r2, r0
+ add r1.y, t0.y, c1.x
+ mov r1.x, t0.x
+ add r2.y, t0.y, c1.y
+ mov r2.x, t0.x
+ texld r1, r1, s1
+ texld r2, r2, s1
+ mad r0, c4.x, r1, r0
+ mad r0, c4.y, r2, r0
+ add r1.y, t0.y, c1.z
+ mov r1.x, t0.x
+ add r2.y, t0.y, c1.w
+ mov r2.x, t0.x
+ texld r1, r1, s1
+ texld r2, r2, s1
+ mad r0, c4.z, r1, r0
+ mad r0, c4.w, r2, r0
+ add r1.y, t0.y, c2.x
+ mov r1.x, t0.x
+ mov r2.xy, t0.wzzw
+ texld r1, r1, s1
+ texld r2, r2, s0
+ mad r0, c5.x, r1, r0
+ mul r0, r2.w, r0
+ mov oC0, r0
+
+ // approximately 40 instruction slots used (10 texture, 30 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[9], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_temps 4
+ mov r0.xz, v1.xxxx
+ add r1.xyzw, v1.yyyy, cb0[3].xzyw
+ mov r0.yw, r1.xxxz
+ sample r2.xyzw, r0.zwzz, t0.xyzw, s1
+ sample r0.xyzw, r0.xyxx, t0.xyzw, s1
+ mul r2.xyzw, r2.xyzw, cb0[6].yyyy
+ mad r0.xyzw, cb0[6].xxxx, r0.xyzw, r2.xyzw
+ mov r1.xz, v1.xxxx
+ sample r2.xyzw, r1.xyxx, t0.xyzw, s1
+ sample r1.xyzw, r1.zwzz, t0.xyzw, s1
+ mad r0.xyzw, cb0[6].zzzz, r2.xyzw, r0.xyzw
+ mad r0.xyzw, cb0[6].wwww, r1.xyzw, r0.xyzw
+ mov r1.xz, v1.xxxx
+ add r2.xyzw, v1.yyyy, cb0[4].xzyw
+ mov r1.yw, r2.xxxz
+ sample r3.xyzw, r1.xyxx, t0.xyzw, s1
+ sample r1.xyzw, r1.zwzz, t0.xyzw, s1
+ mad r0.xyzw, cb0[7].xxxx, r3.xyzw, r0.xyzw
+ mad r0.xyzw, cb0[7].yyyy, r1.xyzw, r0.xyzw
+ mov r2.xz, v1.xxxx
+ sample r1.xyzw, r2.xyxx, t0.xyzw, s1
+ sample r2.xyzw, r2.zwzz, t0.xyzw, s1
+ mad r0.xyzw, cb0[7].zzzz, r1.xyzw, r0.xyzw
+ mad r0.xyzw, cb0[7].wwww, r2.xyzw, r0.xyzw
+ add r1.y, v1.y, cb0[5].x
+ mov r1.x, v1.x
+ sample r1.xyzw, r1.xyxx, t0.xyzw, s1
+ mad r0.xyzw, cb0[8].xxxx, r1.xyzw, r0.xyzw
+ sample r1.xyzw, v1.zwzz, t1.xyzw, s0
+ mul o0.xyzw, r0.xyzw, r1.wwww
+ ret
+ // Approximately 31 instruction slots used
+
+ };
+ }
+
+}
+
+technique10 SampleTextTexture
+{
+ pass Unmasked
+ {
+ RasterizerState = TextureRast;
+ AB_BlendFactor = float4(0, 0, 0, 0);
+ AB_SampleMask = uint(0xffffffff);
+ BlendState = bTextBlend;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16 [unused]
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16 [unused]
+ // float4 TextColor; // Offset: 48 Size: 16
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // tex texture float4 2d 0 1
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ // SV_Target 1 xyzw 1 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c1, 1, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ mov r0.xyz, c0
+ mad r0, r0.xyzx, c1.xxxy, c1.yyyx
+ mov oC0, r0
+ texld r0, t0, s0
+ mul r0, r0.zyxy, c0.w
+ mov oC1, r0
+
+ // approximately 6 instruction slots used (1 texture, 5 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[4], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_input_ps linear v1.xy
+ dcl_output o0.xyzw
+ dcl_output o1.xyzw
+ dcl_temps 1
+ mov o0.xyz, cb0[3].xyzx
+ mov o0.w, l(1.000000)
+ sample r0.xyzw, v1.xyxx, t0.xyzw, s0
+ mul o1.xyzw, r0.zyxy, cb0[3].wwww
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ }
+
+ pass Masked
+ {
+ RasterizerState = TextureRast;
+ AB_BlendFactor = float4(0, 0, 0, 0);
+ AB_SampleMask = uint(0xffffffff);
+ BlendState = bTextBlend;
+ VertexShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16
+ // float4 TexCoords; // Offset: 16 Size: 16
+ // float4 MaskTexCoords; // Offset: 32 Size: 16
+ // float4 TextColor; // Offset: 48 Size: 16 [unused]
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // POSITION 0 xyz 0 NONE float xy
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float xyzw
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c1 cb0 0 3 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Runtime generated constant mappings:
+ //
+ // Target Reg Constant Description
+ // ---------- --------------------------------------------------
+ // c0 Vertex Shader position offset
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ vs_2_x
+ def c4, 0, 1, 0, 0
+ dcl_texcoord v0
+ mad oT0.xy, v0, c2.zwzw, c2
+ mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx
+ mad r0.xy, v0, c1.zwzw, c1
+ add oPos.xy, r0, c0
+ mov oPos.zw, c4.xyxy
+
+ // approximately 5 instruction slots used
+ vs_4_0
+ dcl_constantbuffer cb0[3], immediateIndexed
+ dcl_input v0.xy
+ dcl_output_siv o0.xyzw, position
+ dcl_output o1.xy
+ dcl_output o1.zw
+ mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx
+ mov o0.zw, l(0,0,0,1.000000)
+ mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx
+ mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy
+ ret
+ // Approximately 5 instruction slots used
+
+ };
+ GeometryShader = NULL;
+ PixelShader = asm {
+ //
+ // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+ //
+ //
+ // Buffer Definitions:
+ //
+ // cbuffer cb0
+ // {
+ //
+ // float4 QuadDesc; // Offset: 0 Size: 16 [unused]
+ // float4 TexCoords; // Offset: 16 Size: 16 [unused]
+ // float4 MaskTexCoords; // Offset: 32 Size: 16 [unused]
+ // float4 TextColor; // Offset: 48 Size: 16
+ //
+ // }
+ //
+ //
+ // Resource Bindings:
+ //
+ // Name Type Format Dim Slot Elements
+ // ------------------------------ ---------- ------- ----------- ---- --------
+ // sSampler sampler NA NA 0 1
+ // sMaskSampler sampler NA NA 1 1
+ // tex texture float4 2d 0 1
+ // mask texture float4 2d 1 1
+ // cb0 cbuffer NA NA 0 1
+ //
+ //
+ //
+ // Input signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Position 0 xyzw 0 POS float
+ // TEXCOORD 0 xy 1 NONE float xy
+ // TEXCOORD 1 zw 1 NONE float zw
+ //
+ //
+ // Output signature:
+ //
+ // Name Index Mask Register SysValue Format Used
+ // -------------------- ----- ------ -------- -------- ------- ------
+ // SV_Target 0 xyzw 0 TARGET float xyzw
+ // SV_Target 1 xyzw 1 TARGET float xyzw
+ //
+ //
+ // Constant buffer to DX9 shader constant mappings:
+ //
+ // Target Reg Buffer Start Reg # of Regs Data Conversion
+ // ---------- ------- --------- --------- ----------------------
+ // c0 cb0 3 1 ( FLT, FLT, FLT, FLT)
+ //
+ //
+ // Sampler/Resource to DX9 shader sampler mappings:
+ //
+ // Target Sampler Source Sampler Source Resource
+ // -------------- --------------- ----------------
+ // s0 s0 t0
+ // s1 s1 t1
+ //
+ //
+ // Level9 shader bytecode:
+ //
+ ps_2_x
+ def c1, 1, 0, 0, 0
+ dcl t0
+ dcl_2d s0
+ dcl_2d s1
+ mov r0.xyz, c0
+ mad r0, r0.xyzx, c1.xxxy, c1.yyyx
+ mov oC0, r0
+ mov r0.xy, t0.wzzw
+ texld r1, t0, s0
+ texld r0, r0, s1
+ mul r1, r1.zyxy, c0.w
+ mul r0, r0.w, r1
+ mov oC1, r0
+
+ // approximately 9 instruction slots used (2 texture, 7 arithmetic)
+ ps_4_0
+ dcl_constantbuffer cb0[4], immediateIndexed
+ dcl_sampler s0, mode_default
+ dcl_sampler s1, mode_default
+ dcl_resource_texture2d (float,float,float,float) t0
+ dcl_resource_texture2d (float,float,float,float) t1
+ dcl_input_ps linear v1.xy
+ dcl_input_ps linear v1.zw
+ dcl_output o0.xyzw
+ dcl_output o1.xyzw
+ dcl_temps 2
+ mov o0.xyz, cb0[3].xyzx
+ mov o0.w, l(1.000000)
+ sample r0.xyzw, v1.xyxx, t0.xyzw, s0
+ mul r0.xyzw, r0.zyxy, cb0[3].wwww
+ sample r1.xyzw, v1.zwzz, t1.xyzw, s1
+ mul o1.xyzw, r0.xyzw, r1.wwww
+ ret
+ // Approximately 7 instruction slots used
+
+ };
+ }
+
+}
+
+#endif
+
+const BYTE d2deffect[] = {
+ 68, 88, 66, 67, 90, 71, 243, 245, 168, 88, 153, 105, 108, 146, 135,
+ 174, 199, 125, 74, 149, 1, 0, 0, 0, 137, 80, 1, 0, 1, 0,
+ 0, 0, 36, 0, 0, 0, 70, 88, 49, 48, 93, 80, 1, 0, 1,
+ 16, 255, 254, 5, 0, 0, 0, 22, 0, 0, 0, 13, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0,
+ 0, 37, 66, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0,
+ 0, 0, 0, 0, 36, 71, 108, 111, 98, 97, 108, 115, 0, 117, 105,
+ 110, 116, 0, 13, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 25, 9, 0,
+ 0, 98, 108, 101, 110, 100, 111, 112, 0, 99, 98, 48, 0, 102, 108,
+ 111, 97, 116, 52, 0, 58, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 10,
+ 33, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 84, 101, 120,
+ 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67,
+ 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114,
+ 0, 99, 98, 49, 0, 58, 0, 0, 0, 1, 0, 0, 0, 3, 0,
+ 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 10,
+ 33, 0, 0, 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 72,
+ 0, 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, 66,
+ 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, 0, 83, 104, 97, 100,
+ 111, 119, 67, 111, 108, 111, 114, 0, 99, 98, 50, 0, 102, 108, 111,
+ 97, 116, 51, 120, 51, 0, 222, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 44, 0, 0, 0, 48, 0, 0, 0, 36, 0, 0, 0,
+ 11, 91, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101,
+ 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 102, 108, 111,
+ 97, 116, 50, 0, 26, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 8, 0, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, 10, 17,
+ 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, 102, 108,
+ 111, 97, 116, 51, 0, 72, 1, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 12, 0, 0, 0, 16, 0, 0, 0, 12, 0, 0, 0, 10,
+ 25, 0, 0, 100, 105, 102, 102, 0, 99, 101, 110, 116, 101, 114, 49,
+ 0, 102, 108, 111, 97, 116, 0, 120, 1, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0,
+ 0, 9, 9, 0, 0, 65, 0, 114, 97, 100, 105, 117, 115, 49, 0,
+ 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, 0, 99, 98, 51, 0,
+ 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115,
+ 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, 0, 100, 105, 109,
+ 101, 110, 115, 105, 111, 110, 115, 95, 99, 98, 51, 0, 99, 101, 110,
+ 116, 101, 114, 0, 97, 110, 103, 108, 101, 0, 115, 116, 97, 114, 116,
+ 95, 111, 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102,
+ 115, 101, 116, 0, 84, 101, 120, 116, 117, 114, 101, 50, 68, 0, 2,
+ 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 116, 101, 120,
+ 0, 98, 99, 107, 116, 101, 120, 0, 109, 97, 115, 107, 0, 83, 97,
+ 109, 112, 108, 101, 114, 83, 116, 97, 116, 101, 0, 56, 2, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 21, 0, 0, 0, 115, 83, 97, 109, 112, 108,
+ 101, 114, 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, 0, 0, 0,
+ 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0,
+ 0, 2, 0, 0, 0, 3, 0, 0, 0, 115, 66, 99, 107, 83, 97,
+ 109, 112, 108, 101, 114, 0, 1, 0, 0, 0, 2, 0, 0, 0, 21,
+ 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 115, 87, 114,
+ 97, 112, 83, 97, 109, 112, 108, 101, 114, 0, 1, 0, 0, 0, 2,
+ 0, 0, 0, 21, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0,
+ 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0,
+ 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, 108, 101, 114,
+ 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, 0, 0, 0, 1, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2,
+ 0, 0, 0, 2, 0, 0, 0, 115, 77, 97, 115, 107, 83, 97, 109,
+ 112, 108, 101, 114, 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, 0,
+ 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 115, 83, 104, 97,
+ 100, 111, 119, 83, 97, 109, 112, 108, 101, 114, 0, 1, 0, 0, 0,
+ 2, 0, 0, 0, 21, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0,
+ 0, 4, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0,
+ 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 82, 97, 115, 116, 101, 114, 105,
+ 122, 101, 114, 83, 116, 97, 116, 101, 0, 170, 3, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 84, 101, 120, 116, 117, 114, 101, 82,
+ 97, 115, 116, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0,
+ 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 66, 108,
+ 101, 110, 100, 83, 116, 97, 116, 101, 0, 250, 3, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 83, 104, 97, 100, 111, 119, 66, 108,
+ 101, 110, 100, 72, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 15, 0, 0, 0, 83,
+ 104, 97, 100, 111, 119, 66, 108, 101, 110, 100, 86, 0, 1, 0, 0,
+ 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6,
+ 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0,
+ 0, 2, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 2, 0,
+ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 15,
+ 0, 0, 0, 98, 84, 101, 120, 116, 66, 108, 101, 110, 100, 0, 1,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0,
+ 0, 16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 0,
+ 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1,
+ 0, 0, 0, 2, 0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0,
+ 2, 0, 0, 0, 19, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0,
+ 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 15, 0,
+ 0, 0, 83, 97, 109, 112, 108, 101, 84, 101, 120, 116, 117, 114, 101,
+ 0, 80, 48, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167,
+ 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0,
+ 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248,
+ 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0,
+ 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0,
+ 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0,
+ 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1,
+ 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15,
+ 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0,
+ 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4,
+ 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160,
+ 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20,
+ 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0,
+ 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2,
+ 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160,
+ 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0,
+ 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0,
+ 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0,
+ 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0,
+ 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50,
+ 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0,
+ 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32,
+ 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11,
+ 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0,
+ 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128,
+ 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194,
+ 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0,
+ 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84,
+ 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64,
+ 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255,
+ 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60,
+ 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0,
+ 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0,
+ 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2,
+ 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0,
+ 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0,
+ 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81,
+ 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0,
+ 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120,
+ 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67,
+ 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114,
+ 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32,
+ 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46,
+ 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1,
+ 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0,
+ 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83,
+ 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12,
+ 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80,
+ 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82,
+ 68, 0, 171, 171, 171, 59, 5, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 212, 2, 0, 0, 68,
+ 88, 66, 67, 17, 106, 69, 218, 119, 68, 79, 85, 211, 176, 27, 183,
+ 77, 210, 131, 41, 1, 0, 0, 0, 212, 2, 0, 0, 6, 0, 0,
+ 0, 56, 0, 0, 0, 164, 0, 0, 0, 16, 1, 0, 0, 140, 1,
+ 0, 0, 48, 2, 0, 0, 160, 2, 0, 0, 65, 111, 110, 57, 100,
+ 0, 0, 0, 100, 0, 0, 0, 0, 2, 255, 255, 60, 0, 0, 0,
+ 40, 0, 0, 0, 0, 0, 40, 0, 0, 0, 40, 0, 0, 0, 40,
+ 0, 1, 0, 36, 0, 0, 0, 40, 0, 0, 0, 0, 0, 1, 2,
+ 255, 255, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31,
+ 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 66, 0, 0, 3,
+ 0, 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 1, 0, 0,
+ 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72,
+ 68, 82, 100, 0, 0, 0, 64, 0, 0, 0, 25, 0, 0, 0, 90,
+ 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, 24, 0, 4,
+ 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0,
+ 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32,
+ 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 32, 16, 0, 0,
+ 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0,
+ 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 62, 0, 0,
+ 1, 83, 84, 65, 84, 116, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 82, 68, 69, 70, 156, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0,
+ 4, 255, 255, 0, 1, 0, 0, 105, 0, 0, 0, 92, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 101, 0,
+ 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255,
+ 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0,
+ 115, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 77, 105,
+ 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108,
+ 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51,
+ 56, 52, 0, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0,
+ 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 0,
+ 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84,
+ 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78,
+ 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116,
+ 0, 171, 171, 151, 9, 0, 0, 0, 0, 0, 0, 83, 97, 109, 112,
+ 108, 101, 84, 101, 120, 116, 117, 114, 101, 70, 111, 114, 83, 101, 112,
+ 97, 114, 97, 98, 108, 101, 66, 108, 101, 110, 100, 105, 110, 103, 95,
+ 49, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56,
+ 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0,
+ 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0,
+ 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3,
+ 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0,
+ 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0,
+ 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48,
+ 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0,
+ 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0,
+ 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0,
+ 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0,
+ 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3,
+ 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128,
+ 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0,
+ 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0,
+ 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83,
+ 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0,
+ 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0,
+ 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101,
+ 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3,
+ 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16,
+ 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0,
+ 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32,
+ 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230,
+ 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16,
+ 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142,
+ 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84,
+ 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0,
+ 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1,
+ 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0,
+ 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16,
+ 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0,
+ 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0,
+ 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0,
+ 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0,
+ 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0,
+ 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97,
+ 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111,
+ 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111,
+ 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77,
+ 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76,
+ 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105,
+ 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54,
+ 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0,
+ 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80,
+ 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78,
+ 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0,
+ 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115,
+ 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0,
+ 171, 171, 171, 155, 12, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 72, 13, 0, 0, 68, 88, 66,
+ 67, 193, 65, 249, 15, 188, 209, 36, 123, 179, 111, 3, 63, 40, 10,
+ 7, 98, 1, 0, 0, 0, 72, 13, 0, 0, 6, 0, 0, 0, 56,
+ 0, 0, 0, 172, 4, 0, 0, 188, 10, 0, 0, 56, 11, 0, 0,
+ 164, 12, 0, 0, 20, 13, 0, 0, 65, 111, 110, 57, 108, 4, 0,
+ 0, 108, 4, 0, 0, 0, 2, 255, 255, 52, 4, 0, 0, 56, 0,
+ 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2,
+ 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 2, 255,
+ 255, 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, 128, 191, 0, 0,
+ 0, 192, 0, 0, 64, 192, 0, 0, 128, 192, 81, 0, 0, 5, 2,
+ 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 63,
+ 0, 0, 0, 192, 81, 0, 0, 5, 3, 0, 15, 160, 0, 0, 160,
+ 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0,
+ 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0,
+ 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144,
+ 1, 8, 15, 160, 1, 0, 0, 2, 0, 0, 8, 128, 0, 0, 0,
+ 160, 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 255, 128, 3, 0,
+ 0, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0,
+ 0, 0, 128, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176,
+ 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, 228,
+ 176, 0, 8, 228, 160, 6, 0, 0, 2, 0, 0, 2, 128, 2, 0,
+ 255, 128, 4, 0, 0, 4, 3, 0, 7, 128, 2, 0, 228, 128, 0,
+ 0, 85, 128, 2, 0, 0, 161, 5, 0, 0, 3, 3, 0, 7, 128,
+ 3, 0, 228, 128, 3, 0, 228, 128, 4, 0, 0, 4, 4, 0, 7,
+ 128, 2, 0, 228, 128, 0, 0, 85, 129, 2, 0, 0, 160, 6, 0,
+ 0, 2, 3, 0, 8, 128, 4, 0, 0, 128, 6, 0, 0, 2, 4,
+ 0, 8, 128, 1, 0, 255, 128, 5, 0, 0, 3, 5, 0, 7, 128,
+ 1, 0, 228, 128, 4, 0, 255, 128, 4, 0, 0, 4, 1, 0, 7,
+ 128, 1, 0, 228, 128, 4, 0, 255, 129, 2, 0, 170, 160, 5, 0,
+ 0, 3, 3, 0, 8, 128, 3, 0, 255, 128, 5, 0, 0, 128, 10,
+ 0, 0, 3, 4, 0, 8, 128, 3, 0, 255, 128, 2, 0, 0, 160,
+ 88, 0, 0, 4, 4, 0, 8, 128, 3, 0, 0, 129, 2, 0, 0,
+ 160, 4, 0, 255, 128, 5, 0, 0, 3, 6, 0, 7, 128, 5, 0,
+ 228, 128, 5, 0, 228, 128, 88, 0, 0, 4, 7, 0, 1, 128, 6,
+ 0, 0, 129, 2, 0, 85, 160, 4, 0, 255, 128, 6, 0, 0, 2,
+ 4, 0, 8, 128, 4, 0, 85, 128, 5, 0, 0, 3, 4, 0, 8,
+ 128, 4, 0, 255, 128, 5, 0, 85, 128, 10, 0, 0, 3, 5, 0,
+ 8, 128, 4, 0, 255, 128, 2, 0, 0, 160, 88, 0, 0, 4, 4,
+ 0, 8, 128, 3, 0, 85, 129, 2, 0, 0, 160, 5, 0, 255, 128,
+ 88, 0, 0, 4, 7, 0, 2, 128, 6, 0, 85, 129, 2, 0, 85,
+ 160, 4, 0, 255, 128, 6, 0, 0, 2, 4, 0, 8, 128, 4, 0,
+ 170, 128, 5, 0, 0, 3, 4, 0, 8, 128, 4, 0, 255, 128, 5,
+ 0, 170, 128, 10, 0, 0, 3, 5, 0, 8, 128, 4, 0, 255, 128,
+ 2, 0, 0, 160, 88, 0, 0, 4, 4, 0, 8, 128, 3, 0, 170,
+ 129, 2, 0, 0, 160, 5, 0, 255, 128, 88, 0, 0, 4, 7, 0,
+ 4, 128, 6, 0, 170, 129, 2, 0, 85, 160, 4, 0, 255, 128, 5,
+ 0, 0, 3, 3, 0, 7, 128, 0, 0, 85, 128, 2, 0, 228, 128,
+ 4, 0, 0, 4, 6, 0, 7, 128, 2, 0, 228, 128, 0, 0, 85,
+ 128, 5, 0, 228, 128, 4, 0, 0, 4, 6, 0, 7, 128, 3, 0,
+ 228, 128, 5, 0, 228, 129, 6, 0, 228, 128, 11, 0, 0, 3, 8,
+ 0, 7, 128, 3, 0, 228, 128, 5, 0, 228, 128, 88, 0, 0, 4,
+ 0, 0, 7, 128, 0, 0, 0, 129, 8, 0, 228, 128, 7, 0, 228,
+ 128, 2, 0, 0, 3, 7, 0, 15, 128, 0, 0, 255, 128, 1, 0,
+ 228, 160, 5, 0, 0, 3, 7, 0, 15, 128, 7, 0, 228, 128, 7,
+ 0, 228, 128, 10, 0, 0, 3, 8, 0, 7, 128, 5, 0, 228, 128,
+ 3, 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 7, 0, 255,
+ 129, 8, 0, 228, 128, 0, 0, 228, 128, 4, 0, 0, 4, 8, 0,
+ 7, 128, 5, 0, 228, 128, 2, 0, 255, 161, 2, 0, 0, 161, 2,
+ 0, 0, 3, 8, 0, 7, 128, 8, 0, 228, 129, 2, 0, 0, 160,
+ 4, 0, 0, 4, 4, 0, 7, 128, 4, 0, 228, 128, 8, 0, 228,
+ 129, 2, 0, 0, 160, 2, 0, 0, 3, 8, 0, 7, 128, 5, 0,
+ 228, 128, 5, 0, 228, 128, 5, 0, 0, 3, 5, 0, 7, 128, 5,
+ 0, 228, 128, 3, 0, 228, 128, 5, 0, 0, 3, 8, 0, 7, 128,
+ 3, 0, 228, 128, 8, 0, 228, 128, 88, 0, 0, 4, 1, 0, 7,
+ 128, 1, 0, 228, 128, 8, 0, 228, 128, 4, 0, 228, 128, 88, 0,
+ 0, 4, 0, 0, 7, 128, 7, 0, 170, 129, 1, 0, 228, 128, 0,
+ 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 7, 0, 85, 129,
+ 6, 0, 228, 128, 0, 0, 228, 128, 88, 0, 0, 4, 0, 0, 7,
+ 128, 7, 0, 0, 129, 5, 0, 228, 128, 0, 0, 228, 128, 18, 0,
+ 0, 4, 4, 0, 7, 128, 1, 0, 255, 128, 0, 0, 228, 128, 3,
+ 0, 228, 128, 5, 0, 0, 3, 4, 0, 8, 128, 1, 0, 255, 128,
+ 1, 0, 255, 128, 88, 0, 0, 4, 4, 0, 8, 128, 4, 0, 255,
+ 129, 2, 0, 0, 160, 2, 0, 85, 160, 5, 0, 0, 3, 0, 0,
+ 7, 128, 2, 0, 255, 128, 4, 0, 228, 128, 5, 0, 0, 3, 0,
+ 0, 8, 128, 2, 0, 255, 128, 2, 0, 255, 128, 88, 0, 0, 4,
+ 0, 0, 8, 128, 0, 0, 255, 129, 2, 0, 0, 160, 2, 0, 85,
+ 160, 2, 0, 0, 3, 0, 0, 8, 128, 4, 0, 255, 128, 0, 0,
+ 255, 128, 88, 0, 0, 4, 2, 0, 7, 128, 0, 0, 255, 129, 0,
+ 0, 228, 128, 2, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128,
+ 2, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 8, 6, 0,
+ 0, 64, 0, 0, 0, 130, 1, 0, 0, 89, 0, 0, 4, 70, 142,
+ 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 90, 0, 0, 3, 0,
+ 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0,
+ 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0,
+ 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0,
+ 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1,
+ 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0,
+ 104, 0, 0, 2, 7, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16,
+ 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126,
+ 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69,
+ 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0,
+ 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16,
+ 0, 1, 0, 0, 0, 24, 0, 0, 7, 18, 0, 16, 0, 2, 0,
+ 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0,
+ 0, 0, 0, 24, 0, 0, 7, 34, 0, 16, 0, 2, 0, 0, 0,
+ 58, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 0, 60, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0,
+ 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 31,
+ 0, 4, 3, 10, 0, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5,
+ 242, 32, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0,
+ 0, 62, 0, 0, 1, 21, 0, 0, 1, 14, 0, 0, 7, 114, 0,
+ 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 246,
+ 15, 16, 0, 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0,
+ 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 246, 15, 16,
+ 0, 1, 0, 0, 0, 32, 0, 0, 8, 18, 0, 16, 0, 2, 0,
+ 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 64, 0, 0, 1, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, 0,
+ 2, 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0,
+ 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 1, 0,
+ 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, 0, 2,
+ 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 64, 0, 0, 2, 0, 0, 0, 31, 0, 4, 3, 58, 0, 16,
+ 0, 2, 0, 0, 0, 0, 0, 0, 7, 114, 0, 16, 0, 3, 0,
+ 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 1,
+ 0, 0, 0, 50, 0, 0, 10, 114, 0, 16, 0, 2, 0, 0, 0,
+ 70, 2, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 70, 2, 16,
+ 0, 1, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 18, 0,
+ 0, 1, 32, 0, 0, 8, 130, 0, 16, 0, 2, 0, 0, 0, 10,
+ 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0,
+ 3, 0, 0, 0, 31, 0, 4, 3, 58, 0, 16, 0, 2, 0, 0,
+ 0, 29, 0, 0, 10, 114, 0, 16, 0, 3, 0, 0, 0, 2, 64,
+ 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 63, 0,
+ 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 0, 0, 0, 7,
+ 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0,
+ 0, 70, 2, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114, 0,
+ 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70,
+ 2, 16, 0, 4, 0, 0, 0, 50, 0, 0, 15, 114, 0, 16, 0,
+ 5, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0,
+ 0, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 128, 191, 0, 0, 128, 191, 0,
+ 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 11, 114, 0, 16, 0,
+ 6, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 0, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
+ 128, 63, 0, 0, 0, 0, 0, 0, 0, 11, 114, 0, 16, 0, 5,
+ 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 5, 0, 0, 0,
+ 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
+ 63, 0, 0, 0, 0, 50, 0, 0, 13, 114, 0, 16, 0, 5, 0,
+ 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 6, 0, 0, 0, 70,
+ 2, 16, 0, 5, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63,
+ 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 55, 0, 0,
+ 9, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0,
+ 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 5,
+ 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, 0,
+ 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 64, 0, 0, 4, 0, 0, 0, 31, 0, 4, 3, 58, 0,
+ 16, 0, 2, 0, 0, 0, 51, 0, 0, 7, 114, 0, 16, 0, 2,
+ 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0,
+ 1, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16,
+ 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 5, 0, 0, 0, 31, 0, 4, 3, 58,
+ 0, 16, 0, 2, 0, 0, 0, 52, 0, 0, 7, 114, 0, 16, 0,
+ 2, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16,
+ 0, 1, 0, 0, 0, 18, 0, 0, 1, 24, 0, 0, 10, 114, 0,
+ 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 24, 0, 0, 10, 114, 0, 16, 0, 4, 0, 0,
+ 0, 70, 2, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0,
+ 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0,
+ 0, 0, 11, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128,
+ 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 14, 0,
+ 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1,
+ 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 51, 0, 0, 10,
+ 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
+ 128, 63, 0, 0, 0, 0, 55, 0, 0, 12, 114, 0, 16, 0, 1,
+ 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 2, 64, 0, 0,
+ 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0,
+ 0, 70, 2, 16, 0, 1, 0, 0, 0, 55, 0, 0, 12, 114, 0,
+ 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 21, 0, 0,
+ 1, 21, 0, 0, 1, 21, 0, 0, 1, 21, 0, 0, 1, 21, 0,
+ 0, 1, 0, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0, 0, 58,
+ 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 1, 0, 0,
+ 0, 246, 15, 16, 0, 1, 0, 0, 0, 6, 9, 16, 0, 2, 0,
+ 0, 0, 50, 0, 0, 9, 114, 0, 16, 0, 0, 0, 0, 0, 6,
+ 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0,
+ 150, 7, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114, 32, 16,
+ 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 2,
+ 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, 0, 0,
+ 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1,
+ 83, 84, 65, 84, 116, 0, 0, 0, 57, 0, 0, 0, 7, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 25, 0, 0, 0, 5, 0,
+ 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 82, 68, 69, 70, 100, 1, 0, 0, 1, 0, 0,
+ 0, 232, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4,
+ 255, 255, 0, 1, 0, 0, 48, 1, 0, 0, 188, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 197, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 209,
+ 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0,
+ 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0,
+ 0, 213, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0,
+ 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12,
+ 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 115, 83, 97, 109, 112, 108, 101, 114, 0, 115,
+ 66, 99, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0,
+ 98, 99, 107, 116, 101, 120, 0, 36, 71, 108, 111, 98, 97, 108, 115,
+ 0, 171, 171, 171, 220, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+ 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 1,
+ 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 32,
+ 1, 0, 0, 0, 0, 0, 0, 98, 108, 101, 110, 100, 111, 112, 0,
+ 0, 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32,
+ 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46,
+ 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0,
+ 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15,
+ 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0,
+ 0, 0, 12, 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105,
+ 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171,
+ 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0,
+ 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97,
+ 114, 103, 101, 116, 0, 171, 171, 247, 16, 0, 0, 0, 0, 0, 0,
+ 83, 97, 109, 112, 108, 101, 84, 101, 120, 116, 117, 114, 101, 70, 111,
+ 114, 83, 101, 112, 97, 114, 97, 98, 108, 101, 66, 108, 101, 110, 100,
+ 105, 110, 103, 95, 50, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77,
+ 85, 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62,
+ 1, 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0,
+ 0, 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3,
+ 0, 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184,
+ 0, 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0,
+ 1, 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36,
+ 0, 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4,
+ 0, 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0,
+ 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15,
+ 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0,
+ 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0,
+ 0, 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4,
+ 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228,
+ 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0,
+ 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255,
+ 255, 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0,
+ 61, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0,
+ 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0,
+ 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0,
+ 11, 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0,
+ 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70,
+ 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8,
+ 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0,
+ 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0,
+ 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 70, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0,
+ 11, 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0,
+ 0, 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6,
+ 132, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1,
+ 83, 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0,
+ 0, 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4,
+ 254, 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48,
+ 0, 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0,
+ 0, 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0,
+ 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0,
+ 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196,
+ 0, 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0,
+ 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0,
+ 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84,
+ 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101,
+ 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108,
+ 111, 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82,
+ 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67,
+ 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48,
+ 48, 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0,
+ 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7,
+ 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171,
+ 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0,
+ 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86,
+ 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79,
+ 79, 82, 68, 0, 171, 171, 171, 111, 30, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 88, 17, 0,
+ 0, 68, 88, 66, 67, 62, 116, 36, 238, 73, 63, 158, 95, 222, 192,
+ 91, 113, 112, 55, 55, 145, 1, 0, 0, 0, 88, 17, 0, 0, 6,
+ 0, 0, 0, 56, 0, 0, 0, 88, 6, 0, 0, 204, 14, 0, 0,
+ 72, 15, 0, 0, 180, 16, 0, 0, 36, 17, 0, 0, 65, 111, 110,
+ 57, 24, 6, 0, 0, 24, 6, 0, 0, 0, 2, 255, 255, 224, 5,
+ 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0,
+ 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0,
+ 0, 1, 2, 255, 255, 81, 0, 0, 5, 1, 0, 15, 160, 0, 0,
+ 224, 192, 0, 0, 0, 193, 0, 0, 16, 193, 0, 0, 32, 193, 81,
+ 0, 0, 5, 2, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 0,
+ 0, 0, 128, 191, 0, 0, 128, 62, 81, 0, 0, 5, 3, 0, 15,
+ 160, 0, 0, 0, 63, 0, 0, 0, 64, 0, 0, 128, 191, 0, 0,
+ 128, 64, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 128, 65, 0,
+ 0, 64, 193, 0, 0, 0, 64, 0, 0, 128, 63, 31, 0, 0, 2,
+ 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0,
+ 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8,
+ 15, 160, 1, 0, 0, 2, 0, 0, 8, 128, 0, 0, 0, 160, 2,
+ 0, 0, 3, 0, 0, 15, 128, 0, 0, 255, 128, 1, 0, 228, 160,
+ 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 0, 0, 228,
+ 128, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176, 0, 8,
+ 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, 228, 176, 1,
+ 8, 228, 160, 6, 0, 0, 2, 3, 0, 8, 128, 2, 0, 255, 128,
+ 4, 0, 0, 4, 3, 0, 3, 128, 2, 0, 233, 128, 3, 0, 255,
+ 129, 2, 0, 255, 160, 5, 0, 0, 3, 4, 0, 7, 128, 2, 0,
+ 228, 128, 3, 0, 255, 128, 4, 0, 0, 4, 5, 0, 7, 128, 4,
+ 0, 228, 128, 4, 0, 0, 160, 4, 0, 85, 160, 4, 0, 0, 4,
+ 5, 0, 7, 128, 5, 0, 228, 128, 4, 0, 228, 128, 3, 0, 255,
+ 160, 5, 0, 0, 3, 5, 0, 7, 128, 4, 0, 228, 128, 5, 0,
+ 228, 128, 7, 0, 0, 2, 4, 0, 8, 128, 4, 0, 85, 128, 6,
+ 0, 0, 2, 4, 0, 8, 128, 4, 0, 255, 128, 88, 0, 0, 4,
+ 4, 0, 8, 128, 3, 0, 0, 128, 5, 0, 85, 128, 4, 0, 255,
+ 128, 4, 0, 0, 4, 4, 0, 8, 128, 2, 0, 85, 128, 3, 0,
+ 255, 129, 4, 0, 255, 128, 6, 0, 0, 2, 3, 0, 1, 128, 1,
+ 0, 255, 128, 5, 0, 0, 3, 6, 0, 7, 128, 1, 0, 228, 128,
+ 3, 0, 0, 128, 4, 0, 0, 4, 7, 0, 7, 128, 6, 0, 228,
+ 128, 3, 0, 85, 160, 3, 0, 170, 160, 4, 0, 0, 4, 4, 0,
+ 8, 128, 7, 0, 85, 128, 4, 0, 255, 128, 4, 0, 85, 128, 4,
+ 0, 0, 4, 8, 0, 7, 128, 1, 0, 228, 128, 3, 0, 0, 129,
+ 3, 0, 0, 160, 4, 0, 0, 4, 9, 0, 15, 128, 2, 0, 36,
+ 128, 3, 0, 255, 129, 2, 0, 192, 160, 4, 0, 0, 4, 10, 0,
+ 7, 128, 6, 0, 228, 128, 4, 0, 170, 161, 4, 0, 255, 160, 5,
+ 0, 0, 3, 10, 0, 7, 128, 4, 0, 228, 128, 10, 0, 228, 128,
+ 4, 0, 0, 4, 10, 0, 7, 128, 10, 0, 228, 128, 9, 0, 228,
+ 129, 4, 0, 228, 128, 88, 0, 0, 4, 11, 0, 2, 128, 8, 0,
+ 85, 128, 10, 0, 85, 128, 4, 0, 255, 128, 7, 0, 0, 2, 4,
+ 0, 8, 128, 4, 0, 170, 128, 6, 0, 0, 2, 4, 0, 8, 128,
+ 4, 0, 255, 128, 88, 0, 0, 4, 4, 0, 8, 128, 3, 0, 85,
+ 128, 5, 0, 170, 128, 4, 0, 255, 128, 4, 0, 0, 4, 4, 0,
+ 8, 128, 2, 0, 170, 128, 3, 0, 255, 129, 4, 0, 255, 128, 4,
+ 0, 0, 4, 4, 0, 8, 128, 7, 0, 170, 128, 4, 0, 255, 128,
+ 4, 0, 170, 128, 88, 0, 0, 4, 11, 0, 4, 128, 8, 0, 170,
+ 128, 10, 0, 170, 128, 4, 0, 255, 128, 7, 0, 0, 2, 4, 0,
+ 8, 128, 4, 0, 0, 128, 6, 0, 0, 2, 4, 0, 8, 128, 4,
+ 0, 255, 128, 88, 0, 0, 4, 4, 0, 8, 128, 9, 0, 255, 128,
+ 5, 0, 0, 128, 4, 0, 255, 128, 4, 0, 0, 4, 4, 0, 8,
+ 128, 2, 0, 0, 128, 3, 0, 255, 129, 4, 0, 255, 128, 4, 0,
+ 0, 4, 2, 0, 7, 128, 2, 0, 228, 128, 3, 0, 255, 128, 2,
+ 0, 170, 160, 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 228, 128,
+ 2, 0, 228, 128, 4, 0, 0, 4, 4, 0, 8, 128, 7, 0, 0,
+ 128, 4, 0, 255, 128, 4, 0, 0, 128, 2, 0, 0, 3, 3, 0,
+ 14, 128, 7, 0, 144, 129, 2, 0, 0, 160, 4, 0, 0, 4, 3,
+ 0, 14, 128, 9, 0, 144, 128, 3, 0, 228, 129, 2, 0, 0, 160,
+ 88, 0, 0, 4, 11, 0, 1, 128, 8, 0, 0, 128, 10, 0, 0,
+ 128, 4, 0, 255, 128, 4, 0, 0, 4, 5, 0, 7, 128, 1, 0,
+ 228, 128, 3, 0, 0, 128, 4, 0, 228, 129, 4, 0, 0, 4, 7,
+ 0, 7, 128, 1, 0, 228, 128, 3, 0, 0, 128, 4, 0, 228, 128,
+ 35, 0, 0, 2, 5, 0, 7, 128, 5, 0, 228, 128, 5, 0, 0,
+ 3, 10, 0, 7, 128, 4, 0, 228, 128, 6, 0, 228, 128, 4, 0,
+ 0, 4, 7, 0, 7, 128, 10, 0, 228, 128, 3, 0, 85, 161, 7,
+ 0, 228, 128, 88, 0, 0, 4, 5, 0, 7, 128, 0, 0, 255, 129,
+ 5, 0, 228, 128, 7, 0, 228, 128, 88, 0, 0, 4, 5, 0, 7,
+ 128, 0, 0, 170, 129, 11, 0, 228, 128, 5, 0, 228, 128, 2, 0,
+ 0, 3, 7, 0, 7, 128, 6, 0, 228, 128, 6, 0, 228, 128, 5,
+ 0, 0, 3, 4, 0, 7, 128, 4, 0, 228, 128, 7, 0, 228, 128,
+ 88, 0, 0, 4, 3, 0, 7, 128, 8, 0, 228, 128, 4, 0, 228,
+ 128, 3, 0, 249, 128, 88, 0, 0, 4, 0, 0, 14, 128, 0, 0,
+ 85, 129, 3, 0, 144, 128, 5, 0, 144, 128, 6, 0, 0, 2, 6,
+ 0, 8, 128, 6, 0, 0, 128, 4, 0, 0, 4, 6, 0, 8, 128,
+ 9, 0, 0, 128, 6, 0, 255, 129, 2, 0, 0, 160, 11, 0, 0,
+ 3, 3, 0, 1, 128, 6, 0, 255, 128, 2, 0, 85, 160, 5, 0,
+ 0, 3, 3, 0, 14, 128, 6, 0, 144, 128, 6, 0, 144, 128, 88,
+ 0, 0, 4, 6, 0, 8, 128, 3, 0, 85, 129, 2, 0, 85, 160,
+ 3, 0, 0, 128, 88, 0, 0, 4, 4, 0, 1, 128, 2, 0, 0,
+ 129, 2, 0, 0, 160, 6, 0, 255, 128, 6, 0, 0, 2, 4, 0,
+ 8, 128, 6, 0, 85, 128, 4, 0, 0, 4, 4, 0, 8, 128, 9,
+ 0, 85, 128, 4, 0, 255, 129, 2, 0, 0, 160, 11, 0, 0, 3,
+ 6, 0, 8, 128, 4, 0, 255, 128, 2, 0, 85, 160, 88, 0, 0,
+ 4, 4, 0, 8, 128, 3, 0, 170, 129, 2, 0, 85, 160, 6, 0,
+ 255, 128, 88, 0, 0, 4, 4, 0, 2, 128, 2, 0, 85, 129, 2,
+ 0, 0, 160, 4, 0, 255, 128, 6, 0, 0, 2, 4, 0, 8, 128,
+ 6, 0, 170, 128, 4, 0, 0, 4, 4, 0, 8, 128, 9, 0, 170,
+ 128, 4, 0, 255, 129, 2, 0, 0, 160, 11, 0, 0, 3, 6, 0,
+ 8, 128, 4, 0, 255, 128, 2, 0, 85, 160, 88, 0, 0, 4, 4,
+ 0, 8, 128, 3, 0, 255, 129, 2, 0, 85, 160, 6, 0, 255, 128,
+ 88, 0, 0, 4, 4, 0, 4, 128, 2, 0, 170, 129, 2, 0, 0,
+ 160, 4, 0, 255, 128, 88, 0, 0, 4, 0, 0, 7, 128, 0, 0,
+ 0, 129, 4, 0, 228, 128, 0, 0, 249, 128, 18, 0, 0, 4, 3,
+ 0, 7, 128, 2, 0, 255, 128, 0, 0, 228, 128, 6, 0, 228, 128,
+ 5, 0, 0, 3, 3, 0, 8, 128, 2, 0, 255, 128, 2, 0, 255,
+ 128, 88, 0, 0, 4, 3, 0, 8, 128, 3, 0, 255, 129, 2, 0,
+ 0, 160, 2, 0, 85, 160, 5, 0, 0, 3, 0, 0, 7, 128, 1,
+ 0, 255, 128, 3, 0, 228, 128, 5, 0, 0, 3, 0, 0, 8, 128,
+ 1, 0, 255, 128, 1, 0, 255, 128, 88, 0, 0, 4, 0, 0, 8,
+ 128, 0, 0, 255, 129, 2, 0, 0, 160, 2, 0, 85, 160, 2, 0,
+ 0, 3, 0, 0, 8, 128, 3, 0, 255, 128, 0, 0, 255, 128, 88,
+ 0, 0, 4, 1, 0, 7, 128, 0, 0, 255, 129, 0, 0, 228, 128,
+ 1, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 1, 0, 228,
+ 128, 255, 255, 0, 0, 83, 72, 68, 82, 108, 8, 0, 0, 64, 0,
+ 0, 0, 27, 2, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0,
+ 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0,
+ 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85,
+ 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85,
+ 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0,
+ 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0,
+ 2, 7, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0,
+ 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0,
+ 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9,
+ 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0,
+ 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, 0,
+ 0, 0, 24, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 58,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0,
+ 24, 0, 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16,
+ 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 60, 0,
+ 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0, 16, 0, 2,
+ 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 31, 0, 4, 3,
+ 10, 0, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, 242, 32, 16,
+ 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62, 0,
+ 0, 1, 21, 0, 0, 1, 14, 0, 0, 7, 114, 0, 16, 0, 0,
+ 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0,
+ 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0,
+ 0, 70, 2, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, 0,
+ 0, 0, 32, 0, 0, 8, 18, 0, 16, 0, 2, 0, 0, 0, 10,
+ 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0,
+ 7, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, 0, 2, 0, 0,
+ 0, 24, 0, 0, 10, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2,
+ 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0,
+ 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 24, 0, 0, 10,
+ 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 114, 0, 16, 0, 4,
+ 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0,
+ 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
+ 63, 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0,
+ 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 0,
+ 0, 0, 0, 51, 0, 0, 10, 114, 0, 16, 0, 4, 0, 0, 0,
+ 70, 2, 16, 0, 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128,
+ 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0,
+ 0, 11, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 128, 65,
+ 0, 0, 0, 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63,
+ 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 55, 0, 0,
+ 12, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 3, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0,
+ 55, 0, 0, 12, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16,
+ 0, 2, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0,
+ 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 70, 2, 16, 0, 3,
+ 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, 0,
+ 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 64, 0, 0, 8, 0, 0, 0, 31, 0, 4, 3, 58, 0,
+ 16, 0, 2, 0, 0, 0, 29, 0, 0, 10, 114, 0, 16, 0, 3,
+ 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63,
+ 0, 0, 0, 63, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0,
+ 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2,
+ 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 56,
+ 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0,
+ 1, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 50, 0, 0,
+ 15, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 64, 0, 0, 0, 64, 0,
+ 0, 0, 64, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 191,
+ 0, 0, 128, 191, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0,
+ 11, 114, 0, 16, 0, 6, 0, 0, 0, 70, 2, 16, 128, 65, 0,
+ 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0,
+ 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 11,
+ 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0,
+ 0, 5, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0,
+ 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 50, 0, 0, 13, 114,
+ 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0,
+ 6, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 2, 64, 0,
+ 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
+ 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 70,
+ 2, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0,
+ 70, 2, 16, 0, 5, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0,
+ 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 9, 0, 0, 0, 31,
+ 0, 4, 3, 58, 0, 16, 0, 2, 0, 0, 0, 29, 0, 0, 10,
+ 114, 0, 16, 0, 3, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128,
+ 62, 0, 0, 128, 62, 0, 0, 128, 62, 0, 0, 0, 0, 70, 2,
+ 16, 0, 1, 0, 0, 0, 50, 0, 0, 15, 114, 0, 16, 0, 4,
+ 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0,
+ 0, 0, 128, 65, 0, 0, 128, 65, 0, 0, 128, 65, 0, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 64, 193, 0, 0, 64, 193, 0, 0,
+ 64, 193, 0, 0, 0, 0, 50, 0, 0, 12, 114, 0, 16, 0, 4,
+ 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0,
+ 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 64, 0, 0, 128,
+ 64, 0, 0, 128, 64, 0, 0, 0, 0, 56, 0, 0, 7, 114, 0,
+ 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70,
+ 2, 16, 0, 4, 0, 0, 0, 75, 0, 0, 5, 114, 0, 16, 0,
+ 5, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 55, 0, 0,
+ 9, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 3, 0,
+ 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 5,
+ 0, 0, 0, 29, 0, 0, 10, 114, 0, 16, 0, 4, 0, 0, 0,
+ 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0,
+ 63, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 50, 0,
+ 0, 16, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 64,
+ 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 0, 2, 64, 0,
+ 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
+ 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 70,
+ 2, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0,
+ 0, 0, 0, 11, 114, 0, 16, 0, 6, 0, 0, 0, 70, 2, 16,
+ 128, 65, 0, 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0,
+ 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 50,
+ 0, 0, 10, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128,
+ 65, 0, 0, 0, 5, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0,
+ 0, 70, 2, 16, 0, 1, 0, 0, 0, 50, 0, 0, 15, 114, 0,
+ 16, 0, 6, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64,
+ 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 191, 0, 0, 128,
+ 191, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 8, 114, 0,
+ 16, 0, 3, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 1,
+ 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 50, 0, 0, 9,
+ 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0,
+ 0, 70, 2, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 1, 0,
+ 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 70,
+ 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0,
+ 70, 2, 16, 0, 3, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0,
+ 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 10, 0, 0, 0, 0,
+ 0, 0, 8, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0,
+ 0, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2,
+ 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 56,
+ 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0,
+ 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 50, 0, 0,
+ 13, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 128, 65, 0,
+ 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 64, 0,
+ 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 0, 70, 2, 16, 0,
+ 4, 0, 0, 0, 55, 0, 0, 10, 114, 0, 16, 0, 2, 0, 0,
+ 0, 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 128, 129, 0,
+ 0, 0, 3, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 21,
+ 0, 0, 1, 21, 0, 0, 1, 21, 0, 0, 1, 0, 0, 0, 8,
+ 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 128, 65, 0, 0,
+ 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0,
+ 0, 7, 226, 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1,
+ 0, 0, 0, 6, 9, 16, 0, 2, 0, 0, 0, 50, 0, 0, 9,
+ 114, 0, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, 1, 0, 0,
+ 0, 70, 2, 16, 0, 0, 0, 0, 0, 150, 7, 16, 0, 1, 0,
+ 0, 0, 56, 0, 0, 7, 114, 32, 16, 0, 0, 0, 0, 0, 246,
+ 15, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0,
+ 54, 0, 0, 5, 130, 32, 16, 0, 0, 0, 0, 0, 58, 0, 16,
+ 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 66, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 38, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0,
+ 5, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 100, 1, 0, 0, 1, 0, 0, 0, 232, 0, 0, 0, 5,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0,
+ 48, 1, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 209, 0, 0, 0, 2, 0, 0,
+ 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0,
+ 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 213, 0, 0, 0, 2,
+ 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255,
+ 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115,
+ 83, 97, 109, 112, 108, 101, 114, 0, 115, 66, 99, 107, 83, 97, 109,
+ 112, 108, 101, 114, 0, 116, 101, 120, 0, 98, 99, 107, 116, 101, 120,
+ 0, 36, 71, 108, 111, 98, 97, 108, 115, 0, 171, 171, 171, 220, 0,
+ 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 16, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 2, 0, 0, 0, 32, 1, 0, 0, 0, 0, 0,
+ 0, 98, 108, 101, 110, 100, 111, 112, 0, 0, 0, 19, 0, 1, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, 111,
+ 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83,
+ 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32,
+ 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0,
+ 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8,
+ 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0,
+ 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0,
+ 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88,
+ 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0,
+ 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171,
+ 171, 203, 34, 0, 0, 0, 0, 0, 0, 83, 97, 109, 112, 108, 101,
+ 84, 101, 120, 116, 117, 114, 101, 70, 111, 114, 78, 111, 110, 83, 101,
+ 112, 97, 114, 97, 98, 108, 101, 66, 108, 101, 110, 100, 105, 110, 103,
+ 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56,
+ 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68,
+ 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0,
+ 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0,
+ 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2,
+ 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0,
+ 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0,
+ 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0,
+ 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31,
+ 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4,
+ 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228,
+ 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0,
+ 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0,
+ 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3,
+ 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0,
+ 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72,
+ 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89,
+ 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0,
+ 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0,
+ 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194,
+ 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0,
+ 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0,
+ 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16,
+ 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138,
+ 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0,
+ 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116,
+ 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82,
+ 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0,
+ 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0,
+ 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0,
+ 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0,
+ 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212,
+ 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0,
+ 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0,
+ 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0,
+ 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0,
+ 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100,
+ 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111,
+ 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114,
+ 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105,
+ 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108,
+ 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51,
+ 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0,
+ 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79,
+ 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104,
+ 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105,
+ 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171,
+ 171, 171, 84, 52, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 216, 37, 0, 0, 68, 88, 66, 67,
+ 205, 124, 125, 227, 208, 119, 203, 250, 120, 38, 135, 194, 158, 189, 85,
+ 176, 1, 0, 0, 0, 216, 37, 0, 0, 6, 0, 0, 0, 56, 0,
+ 0, 0, 72, 13, 0, 0, 76, 35, 0, 0, 200, 35, 0, 0, 52,
+ 37, 0, 0, 164, 37, 0, 0, 65, 111, 110, 57, 8, 13, 0, 0,
+ 8, 13, 0, 0, 0, 2, 255, 255, 208, 12, 0, 0, 56, 0, 0,
+ 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0,
+ 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 2, 255, 255,
+ 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, 64, 193, 0, 0, 80,
+ 193, 0, 0, 96, 193, 0, 0, 0, 0, 81, 0, 0, 5, 2, 0,
+ 15, 160, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 81, 0, 0, 5, 3, 0, 15, 160, 154, 153, 153, 62,
+ 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 31, 0, 0,
+ 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0,
+ 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1,
+ 8, 15, 160, 1, 0, 0, 2, 0, 0, 2, 128, 2, 0, 85, 160,
+ 1, 0, 0, 2, 1, 0, 2, 128, 2, 0, 85, 160, 1, 0, 0,
+ 2, 2, 0, 4, 128, 2, 0, 85, 160, 66, 0, 0, 3, 3, 0,
+ 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 4,
+ 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 6, 0, 0, 2,
+ 0, 0, 8, 128, 4, 0, 255, 128, 5, 0, 0, 3, 5, 0, 7,
+ 128, 0, 0, 255, 128, 4, 0, 228, 128, 4, 0, 0, 4, 6, 0,
+ 3, 128, 4, 0, 225, 128, 0, 0, 255, 128, 5, 0, 230, 129, 88,
+ 0, 0, 4, 7, 0, 3, 128, 6, 0, 0, 128, 5, 0, 233, 128,
+ 5, 0, 230, 128, 11, 0, 0, 3, 1, 0, 8, 128, 5, 0, 0,
+ 128, 7, 0, 0, 128, 10, 0, 0, 3, 2, 0, 8, 128, 7, 0,
+ 85, 128, 5, 0, 0, 128, 2, 0, 0, 3, 7, 0, 8, 128, 1,
+ 0, 255, 128, 2, 0, 255, 129, 6, 0, 0, 2, 1, 0, 8, 128,
+ 3, 0, 255, 128, 5, 0, 0, 3, 8, 0, 7, 128, 1, 0, 255,
+ 128, 3, 0, 228, 128, 4, 0, 0, 4, 9, 0, 3, 128, 3, 0,
+ 0, 128, 1, 0, 255, 128, 8, 0, 230, 129, 6, 0, 0, 2, 2,
+ 0, 8, 128, 9, 0, 85, 128, 5, 0, 0, 3, 2, 0, 8, 128,
+ 2, 0, 255, 128, 7, 0, 255, 128, 4, 0, 0, 4, 10, 0, 15,
+ 128, 3, 0, 150, 128, 1, 0, 255, 128, 8, 0, 96, 129, 5, 0,
+ 0, 3, 7, 0, 2, 128, 2, 0, 255, 128, 10, 0, 255, 128, 1,
+ 0, 0, 2, 9, 0, 12, 128, 10, 0, 228, 128, 88, 0, 0, 4,
+ 1, 0, 5, 128, 9, 0, 85, 129, 9, 0, 245, 128, 7, 0, 215,
+ 128, 6, 0, 0, 2, 2, 0, 8, 128, 9, 0, 0, 128, 5, 0,
+ 0, 3, 2, 0, 8, 128, 2, 0, 255, 128, 7, 0, 255, 128, 5,
+ 0, 0, 3, 7, 0, 1, 128, 2, 0, 255, 128, 9, 0, 170, 128,
+ 88, 0, 0, 4, 2, 0, 3, 128, 9, 0, 0, 129, 9, 0, 232,
+ 128, 7, 0, 227, 128, 88, 0, 0, 4, 1, 0, 7, 128, 9, 0,
+ 255, 128, 1, 0, 228, 128, 2, 0, 228, 128, 6, 0, 0, 2, 5,
+ 0, 8, 128, 9, 0, 255, 128, 5, 0, 0, 3, 5, 0, 8, 128,
+ 5, 0, 255, 128, 7, 0, 255, 128, 5, 0, 0, 3, 7, 0, 4,
+ 128, 5, 0, 255, 128, 9, 0, 85, 128, 88, 0, 0, 4, 0, 0,
+ 5, 128, 10, 0, 255, 129, 9, 0, 245, 128, 7, 0, 246, 128, 88,
+ 0, 0, 4, 0, 0, 7, 128, 10, 0, 0, 128, 0, 0, 228, 128,
+ 1, 0, 228, 128, 1, 0, 0, 2, 1, 0, 1, 128, 2, 0, 85,
+ 160, 1, 0, 0, 2, 2, 0, 1, 128, 2, 0, 85, 160, 1, 0,
+ 0, 2, 11, 0, 4, 128, 2, 0, 85, 160, 6, 0, 0, 2, 2,
+ 0, 8, 128, 9, 0, 170, 128, 5, 0, 0, 3, 2, 0, 8, 128,
+ 2, 0, 255, 128, 7, 0, 255, 128, 5, 0, 0, 3, 7, 0, 1,
+ 128, 2, 0, 255, 128, 9, 0, 0, 128, 88, 0, 0, 4, 11, 0,
+ 3, 128, 10, 0, 170, 129, 9, 0, 232, 128, 7, 0, 236, 128, 6,
+ 0, 0, 2, 2, 0, 8, 128, 10, 0, 85, 128, 5, 0, 0, 3,
+ 2, 0, 8, 128, 2, 0, 255, 128, 7, 0, 255, 128, 5, 0, 0,
+ 3, 7, 0, 2, 128, 2, 0, 255, 128, 10, 0, 0, 128, 88, 0,
+ 0, 4, 2, 0, 6, 128, 10, 0, 85, 129, 10, 0, 196, 128, 7,
+ 0, 220, 128, 88, 0, 0, 4, 2, 0, 7, 128, 10, 0, 0, 128,
+ 2, 0, 228, 128, 11, 0, 228, 128, 6, 0, 0, 2, 2, 0, 8,
+ 128, 10, 0, 0, 128, 5, 0, 0, 3, 2, 0, 8, 128, 2, 0,
+ 255, 128, 7, 0, 255, 128, 5, 0, 0, 3, 7, 0, 4, 128, 2,
+ 0, 255, 128, 10, 0, 85, 128, 88, 0, 0, 4, 1, 0, 6, 128,
+ 10, 0, 0, 129, 10, 0, 196, 128, 7, 0, 248, 128, 88, 0, 0,
+ 4, 1, 0, 7, 128, 9, 0, 255, 128, 1, 0, 228, 128, 2, 0,
+ 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 10, 0, 85, 128, 1,
+ 0, 228, 128, 0, 0, 228, 128, 88, 0, 0, 4, 1, 0, 3, 128,
+ 9, 0, 170, 128, 8, 0, 233, 128, 8, 0, 230, 128, 8, 0, 0,
+ 3, 5, 0, 8, 128, 0, 0, 228, 128, 3, 0, 228, 160, 8, 0,
+ 0, 3, 1, 0, 4, 128, 8, 0, 228, 128, 3, 0, 228, 160, 2,
+ 0, 0, 3, 5, 0, 8, 128, 5, 0, 255, 129, 1, 0, 170, 128,
+ 2, 0, 0, 3, 0, 0, 7, 128, 0, 0, 228, 128, 5, 0, 255,
+ 128, 2, 0, 0, 3, 5, 0, 8, 128, 0, 0, 85, 129, 0, 0,
+ 0, 128, 88, 0, 0, 4, 2, 0, 3, 128, 5, 0, 255, 128, 0,
+ 0, 225, 128, 0, 0, 228, 128, 10, 0, 0, 3, 5, 0, 8, 128,
+ 0, 0, 170, 128, 2, 0, 0, 128, 11, 0, 0, 3, 7, 0, 1,
+ 128, 2, 0, 85, 128, 0, 0, 170, 128, 8, 0, 0, 3, 2, 0,
+ 1, 128, 0, 0, 228, 128, 3, 0, 228, 160, 2, 0, 0, 3, 2,
+ 0, 2, 128, 5, 0, 255, 129, 2, 0, 0, 128, 6, 0, 0, 2,
+ 2, 0, 2, 128, 2, 0, 85, 128, 2, 0, 0, 3, 7, 0, 14,
+ 128, 0, 0, 144, 128, 2, 0, 0, 129, 5, 0, 0, 3, 7, 0,
+ 14, 128, 2, 0, 0, 128, 7, 0, 228, 128, 4, 0, 0, 4, 2,
+ 0, 14, 128, 7, 0, 228, 128, 2, 0, 85, 128, 2, 0, 0, 128,
+ 88, 0, 0, 4, 0, 0, 7, 128, 5, 0, 255, 128, 0, 0, 228,
+ 128, 2, 0, 249, 128, 2, 0, 0, 3, 2, 0, 14, 128, 2, 0,
+ 0, 129, 0, 0, 144, 128, 2, 0, 0, 3, 5, 0, 8, 128, 2,
+ 0, 0, 129, 2, 0, 0, 160, 5, 0, 0, 3, 2, 0, 14, 128,
+ 2, 0, 228, 128, 5, 0, 255, 128, 2, 0, 0, 3, 5, 0, 8,
+ 128, 2, 0, 0, 129, 7, 0, 0, 128, 2, 0, 0, 3, 7, 0,
+ 1, 128, 7, 0, 0, 129, 2, 0, 0, 160, 6, 0, 0, 2, 5,
+ 0, 8, 128, 5, 0, 255, 128, 4, 0, 0, 4, 2, 0, 7, 128,
+ 2, 0, 249, 128, 5, 0, 255, 128, 2, 0, 0, 128, 88, 0, 0,
+ 4, 0, 0, 7, 128, 7, 0, 0, 128, 0, 0, 228, 128, 2, 0,
+ 228, 128, 8, 0, 0, 3, 5, 0, 8, 128, 5, 0, 228, 128, 3,
+ 0, 228, 160, 2, 0, 0, 3, 2, 0, 1, 128, 1, 0, 170, 128,
+ 5, 0, 255, 129, 2, 0, 0, 3, 5, 0, 8, 128, 1, 0, 170,
+ 129, 5, 0, 255, 128, 4, 0, 0, 4, 2, 0, 14, 128, 3, 0,
+ 144, 128, 1, 0, 255, 128, 5, 0, 255, 128, 4, 0, 0, 4, 3,
+ 0, 7, 128, 4, 0, 228, 128, 0, 0, 255, 128, 2, 0, 0, 128,
+ 4, 0, 0, 4, 7, 0, 15, 128, 4, 0, 38, 128, 0, 0, 255,
+ 128, 5, 0, 144, 129, 2, 0, 0, 3, 0, 0, 8, 128, 3, 0,
+ 85, 129, 3, 0, 0, 128, 88, 0, 0, 4, 8, 0, 6, 128, 0,
+ 0, 255, 128, 3, 0, 196, 128, 3, 0, 208, 128, 10, 0, 0, 3,
+ 0, 0, 8, 128, 3, 0, 170, 128, 8, 0, 85, 128, 11, 0, 0,
+ 3, 1, 0, 8, 128, 8, 0, 170, 128, 3, 0, 170, 128, 8, 0,
+ 0, 3, 5, 0, 8, 128, 3, 0, 228, 128, 3, 0, 228, 160, 2,
+ 0, 0, 3, 2, 0, 1, 128, 0, 0, 255, 129, 5, 0, 255, 128,
+ 6, 0, 0, 2, 2, 0, 1, 128, 2, 0, 0, 128, 2, 0, 0,
+ 3, 8, 0, 14, 128, 3, 0, 144, 128, 5, 0, 255, 129, 5, 0,
+ 0, 3, 8, 0, 14, 128, 5, 0, 255, 128, 8, 0, 228, 128, 4,
+ 0, 0, 4, 8, 0, 14, 128, 8, 0, 228, 128, 2, 0, 0, 128,
+ 5, 0, 255, 128, 88, 0, 0, 4, 3, 0, 7, 128, 0, 0, 255,
+ 128, 3, 0, 228, 128, 8, 0, 249, 128, 2, 0, 0, 3, 8, 0,
+ 14, 128, 5, 0, 255, 129, 3, 0, 144, 128, 2, 0, 0, 3, 0,
+ 0, 8, 128, 5, 0, 255, 129, 2, 0, 0, 160, 5, 0, 0, 3,
+ 8, 0, 14, 128, 0, 0, 255, 128, 8, 0, 228, 128, 2, 0, 0,
+ 3, 0, 0, 8, 128, 1, 0, 255, 128, 5, 0, 255, 129, 2, 0,
+ 0, 3, 1, 0, 8, 128, 1, 0, 255, 129, 2, 0, 0, 160, 6,
+ 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 4, 0, 0, 4,
+ 8, 0, 14, 128, 8, 0, 228, 128, 0, 0, 255, 128, 5, 0, 255,
+ 128, 88, 0, 0, 4, 3, 0, 7, 128, 1, 0, 255, 128, 3, 0,
+ 228, 128, 8, 0, 249, 128, 2, 0, 0, 3, 0, 0, 8, 128, 2,
+ 0, 170, 129, 2, 0, 85, 128, 88, 0, 0, 4, 8, 0, 6, 128,
+ 0, 0, 255, 128, 2, 0, 216, 128, 2, 0, 228, 128, 10, 0, 0,
+ 3, 0, 0, 8, 128, 2, 0, 255, 128, 8, 0, 85, 128, 11, 0,
+ 0, 3, 1, 0, 8, 128, 8, 0, 170, 128, 2, 0, 255, 128, 8,
+ 0, 0, 3, 5, 0, 8, 128, 2, 0, 249, 128, 3, 0, 228, 160,
+ 2, 0, 0, 3, 2, 0, 1, 128, 0, 0, 255, 129, 5, 0, 255,
+ 128, 6, 0, 0, 2, 2, 0, 1, 128, 2, 0, 0, 128, 2, 0,
+ 0, 3, 8, 0, 14, 128, 2, 0, 228, 128, 5, 0, 255, 129, 5,
+ 0, 0, 3, 8, 0, 14, 128, 5, 0, 255, 128, 8, 0, 228, 128,
+ 4, 0, 0, 4, 8, 0, 14, 128, 8, 0, 228, 128, 2, 0, 0,
+ 128, 5, 0, 255, 128, 88, 0, 0, 4, 2, 0, 7, 128, 0, 0,
+ 255, 128, 2, 0, 249, 128, 8, 0, 249, 128, 2, 0, 0, 3, 8,
+ 0, 14, 128, 5, 0, 255, 129, 2, 0, 144, 128, 2, 0, 0, 3,
+ 0, 0, 8, 128, 5, 0, 255, 129, 2, 0, 0, 160, 5, 0, 0,
+ 3, 8, 0, 14, 128, 0, 0, 255, 128, 8, 0, 228, 128, 2, 0,
+ 0, 3, 0, 0, 8, 128, 1, 0, 255, 128, 5, 0, 255, 129, 2,
+ 0, 0, 3, 1, 0, 8, 128, 1, 0, 255, 129, 2, 0, 0, 160,
+ 6, 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 4, 0, 0,
+ 4, 8, 0, 14, 128, 8, 0, 228, 128, 0, 0, 255, 128, 5, 0,
+ 255, 128, 88, 0, 0, 4, 2, 0, 7, 128, 1, 0, 255, 128, 2,
+ 0, 228, 128, 8, 0, 249, 128, 1, 0, 0, 2, 0, 0, 8, 128,
+ 0, 0, 0, 160, 2, 0, 0, 3, 8, 0, 14, 128, 0, 0, 255,
+ 128, 1, 0, 144, 160, 5, 0, 0, 3, 8, 0, 14, 128, 8, 0,
+ 228, 128, 8, 0, 228, 128, 88, 0, 0, 4, 2, 0, 7, 128, 8,
+ 0, 255, 129, 3, 0, 228, 128, 2, 0, 228, 128, 88, 0, 0, 4,
+ 0, 0, 7, 128, 8, 0, 170, 129, 0, 0, 228, 128, 2, 0, 228,
+ 128, 1, 0, 0, 2, 2, 0, 2, 128, 2, 0, 85, 160, 1, 0,
+ 0, 2, 3, 0, 2, 128, 2, 0, 85, 160, 1, 0, 0, 2, 9,
+ 0, 4, 128, 2, 0, 85, 160, 11, 0, 0, 3, 0, 0, 8, 128,
+ 8, 0, 0, 128, 1, 0, 0, 128, 10, 0, 0, 3, 2, 0, 8,
+ 128, 1, 0, 85, 128, 8, 0, 0, 128, 2, 0, 0, 3, 10, 0,
+ 8, 128, 0, 0, 255, 128, 2, 0, 255, 129, 6, 0, 0, 2, 0,
+ 0, 8, 128, 7, 0, 255, 128, 5, 0, 0, 3, 0, 0, 8, 128,
+ 0, 0, 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, 0, 1,
+ 128, 0, 0, 255, 128, 6, 0, 0, 128, 1, 0, 0, 2, 6, 0,
+ 12, 128, 7, 0, 180, 128, 88, 0, 0, 4, 9, 0, 3, 128, 7,
+ 0, 255, 129, 6, 0, 226, 128, 10, 0, 227, 128, 6, 0, 0, 2,
+ 0, 0, 8, 128, 6, 0, 85, 128, 5, 0, 0, 3, 0, 0, 8,
+ 128, 0, 0, 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, 0,
+ 2, 128, 0, 0, 255, 128, 7, 0, 170, 128, 88, 0, 0, 4, 3,
+ 0, 5, 128, 6, 0, 85, 129, 6, 0, 245, 128, 10, 0, 215, 128,
+ 88, 0, 0, 4, 1, 0, 11, 128, 7, 0, 170, 128, 3, 0, 164,
+ 128, 9, 0, 164, 128, 6, 0, 0, 2, 0, 0, 8, 128, 7, 0,
+ 170, 128, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 10,
+ 0, 255, 128, 5, 0, 0, 3, 10, 0, 4, 128, 0, 0, 255, 128,
+ 6, 0, 85, 128, 88, 0, 0, 4, 2, 0, 5, 128, 7, 0, 170,
+ 129, 6, 0, 245, 128, 10, 0, 246, 128, 88, 0, 0, 4, 1, 0,
+ 11, 128, 7, 0, 0, 128, 2, 0, 164, 128, 1, 0, 228, 128, 1,
+ 0, 0, 2, 2, 0, 1, 128, 2, 0, 85, 160, 1, 0, 0, 2,
+ 3, 0, 4, 128, 2, 0, 85, 160, 6, 0, 0, 2, 0, 0, 8,
+ 128, 6, 0, 0, 128, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0,
+ 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, 0, 1, 128, 0,
+ 0, 255, 128, 7, 0, 255, 128, 88, 0, 0, 4, 3, 0, 3, 128,
+ 6, 0, 0, 129, 6, 0, 226, 128, 10, 0, 236, 128, 6, 0, 0,
+ 2, 0, 0, 8, 128, 7, 0, 85, 128, 5, 0, 0, 3, 0, 0,
+ 8, 128, 0, 0, 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10,
+ 0, 2, 128, 0, 0, 255, 128, 7, 0, 0, 128, 88, 0, 0, 4,
+ 2, 0, 6, 128, 7, 0, 85, 129, 7, 0, 196, 128, 10, 0, 220,
+ 128, 88, 0, 0, 4, 2, 0, 7, 128, 7, 0, 0, 128, 2, 0,
+ 228, 128, 3, 0, 228, 128, 1, 0, 0, 2, 3, 0, 1, 128, 2,
+ 0, 85, 160, 6, 0, 0, 2, 0, 0, 8, 128, 7, 0, 0, 128,
+ 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 10, 0, 255,
+ 128, 5, 0, 0, 3, 10, 0, 4, 128, 0, 0, 255, 128, 7, 0,
+ 85, 128, 88, 0, 0, 4, 3, 0, 6, 128, 7, 0, 0, 129, 7,
+ 0, 196, 128, 10, 0, 248, 128, 88, 0, 0, 4, 2, 0, 7, 128,
+ 7, 0, 170, 128, 3, 0, 228, 128, 2, 0, 228, 128, 88, 0, 0,
+ 4, 1, 0, 11, 128, 7, 0, 85, 128, 2, 0, 164, 128, 1, 0,
+ 228, 128, 8, 0, 0, 3, 0, 0, 8, 128, 1, 0, 244, 128, 3,
+ 0, 228, 160, 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 129,
+ 1, 0, 170, 128, 2, 0, 0, 3, 1, 0, 7, 128, 0, 0, 255,
+ 128, 1, 0, 244, 128, 2, 0, 0, 3, 0, 0, 8, 128, 1, 0,
+ 85, 129, 1, 0, 0, 128, 88, 0, 0, 4, 2, 0, 3, 128, 0,
+ 0, 255, 128, 1, 0, 225, 128, 1, 0, 228, 128, 10, 0, 0, 3,
+ 0, 0, 8, 128, 1, 0, 170, 128, 2, 0, 0, 128, 11, 0, 0,
+ 3, 5, 0, 8, 128, 2, 0, 85, 128, 1, 0, 170, 128, 8, 0,
+ 0, 3, 1, 0, 8, 128, 1, 0, 228, 128, 3, 0, 228, 160, 2,
+ 0, 0, 3, 2, 0, 7, 128, 1, 0, 255, 129, 1, 0, 228, 128,
+ 5, 0, 0, 3, 2, 0, 7, 128, 1, 0, 255, 128, 2, 0, 228,
+ 128, 2, 0, 0, 3, 2, 0, 8, 128, 0, 0, 255, 129, 1, 0,
+ 255, 128, 6, 0, 0, 2, 2, 0, 8, 128, 2, 0, 255, 128, 4,
+ 0, 0, 4, 2, 0, 7, 128, 2, 0, 228, 128, 2, 0, 255, 128,
+ 1, 0, 255, 128, 88, 0, 0, 4, 1, 0, 7, 128, 0, 0, 255,
+ 128, 1, 0, 228, 128, 2, 0, 228, 128, 2, 0, 0, 3, 2, 0,
+ 7, 128, 1, 0, 255, 129, 1, 0, 228, 128, 2, 0, 0, 3, 0,
+ 0, 8, 128, 1, 0, 255, 129, 2, 0, 0, 160, 5, 0, 0, 3,
+ 2, 0, 7, 128, 0, 0, 255, 128, 2, 0, 228, 128, 2, 0, 0,
+ 3, 0, 0, 8, 128, 1, 0, 255, 129, 5, 0, 255, 128, 2, 0,
+ 0, 3, 2, 0, 8, 128, 5, 0, 255, 129, 2, 0, 0, 160, 6,
+ 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 4, 0, 0, 4,
+ 2, 0, 7, 128, 2, 0, 228, 128, 0, 0, 255, 128, 1, 0, 255,
+ 128, 88, 0, 0, 4, 1, 0, 7, 128, 2, 0, 255, 128, 1, 0,
+ 228, 128, 2, 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 8,
+ 0, 85, 129, 1, 0, 228, 128, 0, 0, 228, 128, 18, 0, 0, 4,
+ 1, 0, 7, 128, 3, 0, 255, 128, 0, 0, 228, 128, 5, 0, 228,
+ 128, 5, 0, 0, 3, 1, 0, 8, 128, 3, 0, 255, 128, 3, 0,
+ 255, 128, 88, 0, 0, 4, 1, 0, 8, 128, 1, 0, 255, 129, 2,
+ 0, 0, 160, 2, 0, 85, 160, 5, 0, 0, 3, 0, 0, 7, 128,
+ 4, 0, 255, 128, 1, 0, 228, 128, 5, 0, 0, 3, 0, 0, 8,
+ 128, 4, 0, 255, 128, 4, 0, 255, 128, 88, 0, 0, 4, 0, 0,
+ 8, 128, 0, 0, 255, 129, 2, 0, 0, 160, 2, 0, 85, 160, 2,
+ 0, 0, 3, 0, 0, 8, 128, 1, 0, 255, 128, 0, 0, 255, 128,
+ 88, 0, 0, 4, 4, 0, 7, 128, 0, 0, 255, 129, 0, 0, 228,
+ 128, 4, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 4, 0,
+ 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 252, 21, 0, 0, 64,
+ 0, 0, 0, 127, 5, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16,
+ 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0,
+ 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85,
+ 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0,
+ 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0,
+ 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0,
+ 0, 2, 9, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0,
+ 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0,
+ 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, 0, 0,
+ 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 1, 0,
+ 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1,
+ 0, 0, 0, 24, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0,
+ 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 0, 24, 0, 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, 58, 0,
+ 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 60,
+ 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0, 16, 0,
+ 2, 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 31, 0, 4,
+ 3, 10, 0, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, 242, 32,
+ 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62,
+ 0, 0, 1, 21, 0, 0, 1, 14, 0, 0, 7, 114, 0, 16, 0,
+ 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 246, 15, 16,
+ 0, 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 1, 0,
+ 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1,
+ 0, 0, 0, 32, 0, 0, 8, 18, 0, 16, 0, 2, 0, 0, 0,
+ 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0,
+ 0, 12, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, 0, 2, 0,
+ 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 42,
+ 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0,
+ 52, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16,
+ 0, 1, 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 51, 0,
+ 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, 42, 0, 16, 0, 1,
+ 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 51, 0, 0, 7,
+ 34, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0,
+ 0, 26, 0, 16, 0, 2, 0, 0, 0, 0, 0, 0, 8, 130, 0,
+ 16, 0, 2, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 2,
+ 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 29, 0, 0, 7,
+ 18, 0, 16, 0, 3, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 10, 0,
+ 16, 0, 3, 0, 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 3,
+ 0, 0, 0, 6, 10, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 150, 4, 16, 0, 0, 0, 0, 0, 49, 0, 0, 10, 114, 0, 16,
+ 0, 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 7, 16, 0, 3,
+ 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0,
+ 246, 15, 16, 0, 2, 0, 0, 0, 22, 7, 16, 0, 3, 0, 0,
+ 0, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2,
+ 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 55,
+ 0, 0, 9, 98, 0, 16, 0, 5, 0, 0, 0, 6, 0, 16, 0,
+ 4, 0, 0, 0, 6, 3, 16, 0, 2, 0, 0, 0, 6, 1, 16,
+ 0, 3, 0, 0, 0, 29, 0, 0, 7, 146, 0, 16, 0, 4, 0,
+ 0, 0, 166, 10, 16, 0, 0, 0, 0, 0, 86, 1, 16, 0, 0,
+ 0, 0, 0, 55, 0, 0, 9, 98, 0, 16, 0, 6, 0, 0, 0,
+ 86, 5, 16, 0, 4, 0, 0, 0, 246, 13, 16, 0, 2, 0, 0,
+ 0, 6, 1, 16, 0, 3, 0, 0, 0, 55, 0, 0, 9, 50, 0,
+ 16, 0, 3, 0, 0, 0, 166, 10, 16, 0, 4, 0, 0, 0, 230,
+ 10, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, 3, 0, 0, 0,
+ 54, 0, 0, 5, 18, 0, 16, 0, 6, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, 3, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 55, 0, 0, 9, 114,
+ 0, 16, 0, 3, 0, 0, 0, 246, 15, 16, 0, 4, 0, 0, 0,
+ 70, 2, 16, 0, 6, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0,
+ 0, 54, 0, 0, 5, 18, 0, 16, 0, 5, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 3,
+ 0, 0, 0, 6, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0,
+ 5, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 18, 0, 0,
+ 1, 0, 0, 0, 8, 242, 0, 16, 0, 4, 0, 0, 0, 86, 10,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 134, 1, 16, 0, 0,
+ 0, 0, 0, 49, 0, 0, 10, 114, 0, 16, 0, 5, 0, 0, 0,
+ 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 14, 0,
+ 0, 7, 114, 0, 16, 0, 6, 0, 0, 0, 246, 15, 16, 0, 2,
+ 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 56, 0, 0, 7,
+ 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0,
+ 0, 70, 2, 16, 0, 6, 0, 0, 0, 55, 0, 0, 9, 82, 0,
+ 16, 0, 6, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, 0, 6,
+ 3, 16, 0, 2, 0, 0, 0, 6, 1, 16, 0, 4, 0, 0, 0,
+ 29, 0, 0, 7, 146, 0, 16, 0, 5, 0, 0, 0, 166, 10, 16,
+ 0, 0, 0, 0, 0, 6, 4, 16, 0, 0, 0, 0, 0, 55, 0,
+ 0, 9, 82, 0, 16, 0, 7, 0, 0, 0, 86, 5, 16, 0, 5,
+ 0, 0, 0, 246, 13, 16, 0, 2, 0, 0, 0, 6, 1, 16, 0,
+ 4, 0, 0, 0, 55, 0, 0, 9, 50, 0, 16, 0, 2, 0, 0,
+ 0, 166, 10, 16, 0, 5, 0, 0, 0, 182, 15, 16, 0, 2, 0,
+ 0, 0, 182, 15, 16, 0, 4, 0, 0, 0, 54, 0, 0, 5, 34,
+ 0, 16, 0, 7, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0,
+ 54, 0, 0, 5, 66, 0, 16, 0, 2, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0,
+ 0, 0, 246, 15, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 7,
+ 0, 0, 0, 70, 2, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5,
+ 34, 0, 16, 0, 6, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 0, 55, 0, 0, 9, 114, 0, 16, 0, 3, 0, 0, 0, 6, 0,
+ 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0, 0, 70,
+ 2, 16, 0, 2, 0, 0, 0, 21, 0, 0, 1, 16, 0, 0, 10,
+ 18, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0,
+ 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71,
+ 225, 61, 0, 0, 0, 0, 16, 0, 0, 10, 34, 0, 16, 0, 2,
+ 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 2, 64, 0, 0,
+ 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0,
+ 0, 0, 0, 0, 8, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0,
+ 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 10, 0, 16, 0, 2,
+ 0, 0, 0, 0, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0,
+ 6, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0,
+ 0, 16, 0, 0, 10, 130, 0, 16, 0, 2, 0, 0, 0, 70, 2,
+ 16, 0, 2, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61,
+ 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 51, 0, 0, 7,
+ 18, 0, 16, 0, 3, 0, 0, 0, 26, 0, 16, 0, 2, 0, 0,
+ 0, 10, 0, 16, 0, 2, 0, 0, 0, 51, 0, 0, 7, 18, 0,
+ 16, 0, 3, 0, 0, 0, 42, 0, 16, 0, 2, 0, 0, 0, 10,
+ 0, 16, 0, 3, 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0,
+ 3, 0, 0, 0, 26, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16,
+ 0, 2, 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, 3, 0,
+ 0, 0, 42, 0, 16, 0, 2, 0, 0, 0, 26, 0, 16, 0, 3,
+ 0, 0, 0, 49, 0, 0, 7, 66, 0, 16, 0, 3, 0, 0, 0,
+ 10, 0, 16, 0, 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15,
+ 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 70, 2, 16, 0, 2,
+ 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0,
+ 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0,
+ 0, 0, 0, 0, 8, 18, 0, 16, 0, 3, 0, 0, 0, 58, 0,
+ 16, 0, 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3,
+ 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0,
+ 70, 2, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 3, 0, 0,
+ 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15,
+ 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55,
+ 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 166, 10, 16, 0,
+ 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16,
+ 0, 2, 0, 0, 0, 49, 0, 0, 7, 18, 0, 16, 0, 3, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 26, 0, 16, 0, 3,
+ 0, 0, 0, 0, 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0,
+ 246, 15, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 70, 2, 16,
+ 0, 2, 0, 0, 0, 0, 0, 0, 8, 66, 0, 16, 0, 3, 0,
+ 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 1,
+ 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 7, 114, 0, 16, 0,
+ 4, 0, 0, 0, 166, 10, 16, 0, 3, 0, 0, 0, 70, 2, 16,
+ 0, 4, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 3, 0,
+ 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 26,
+ 0, 16, 0, 3, 0, 0, 0, 14, 0, 0, 7, 226, 0, 16, 0,
+ 3, 0, 0, 0, 6, 9, 16, 0, 4, 0, 0, 0, 86, 5, 16,
+ 0, 3, 0, 0, 0, 0, 0, 0, 7, 226, 0, 16, 0, 3, 0,
+ 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 86, 14, 16, 0, 3,
+ 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0,
+ 6, 0, 16, 0, 3, 0, 0, 0, 150, 7, 16, 0, 3, 0, 0,
+ 0, 70, 2, 16, 0, 2, 0, 0, 0, 18, 0, 0, 1, 32, 0,
+ 0, 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, 128, 32, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 13, 0, 0, 0,
+ 31, 0, 4, 3, 58, 0, 16, 0, 2, 0, 0, 0, 52, 0, 0,
+ 7, 130, 0, 16, 0, 2, 0, 0, 0, 42, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, 130,
+ 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 58, 0, 16, 0, 2, 0, 0, 0, 51, 0, 0, 7, 18, 0, 16,
+ 0, 3, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 26, 0,
+ 16, 0, 0, 0, 0, 0, 51, 0, 0, 7, 18, 0, 16, 0, 3,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 3, 0, 0, 0, 0, 0, 0, 8, 130, 0, 16, 0, 3, 0, 0,
+ 0, 58, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, 128, 65, 0,
+ 0, 0, 3, 0, 0, 0, 29, 0, 0, 7, 130, 0, 16, 0, 2,
+ 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0,
+ 1, 0, 0, 0, 31, 0, 4, 3, 58, 0, 16, 0, 2, 0, 0,
+ 0, 0, 0, 0, 8, 242, 0, 16, 0, 4, 0, 0, 0, 6, 10,
+ 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 150, 4, 16, 0, 1,
+ 0, 0, 0, 49, 0, 0, 10, 114, 0, 16, 0, 5, 0, 0, 0,
+ 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 14, 0,
+ 0, 7, 114, 0, 16, 0, 6, 0, 0, 0, 246, 15, 16, 0, 3,
+ 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 56, 0, 0, 7,
+ 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0,
+ 0, 70, 2, 16, 0, 6, 0, 0, 0, 55, 0, 0, 9, 98, 0,
+ 16, 0, 6, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, 0, 6,
+ 3, 16, 0, 3, 0, 0, 0, 6, 1, 16, 0, 4, 0, 0, 0,
+ 29, 0, 0, 7, 146, 0, 16, 0, 5, 0, 0, 0, 166, 10, 16,
+ 0, 1, 0, 0, 0, 86, 1, 16, 0, 1, 0, 0, 0, 55, 0,
+ 0, 9, 98, 0, 16, 0, 7, 0, 0, 0, 86, 5, 16, 0, 5,
+ 0, 0, 0, 246, 13, 16, 0, 3, 0, 0, 0, 6, 1, 16, 0,
+ 4, 0, 0, 0, 55, 0, 0, 9, 50, 0, 16, 0, 4, 0, 0,
+ 0, 166, 10, 16, 0, 5, 0, 0, 0, 230, 10, 16, 0, 3, 0,
+ 0, 0, 230, 10, 16, 0, 4, 0, 0, 0, 54, 0, 0, 5, 18,
+ 0, 16, 0, 7, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0,
+ 54, 0, 0, 5, 66, 0, 16, 0, 4, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 4, 0,
+ 0, 0, 246, 15, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 7,
+ 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 54, 0, 0, 5,
+ 18, 0, 16, 0, 6, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 0, 55, 0, 0, 9, 114, 0, 16, 0, 4, 0, 0, 0, 6, 0,
+ 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0, 0, 70,
+ 2, 16, 0, 4, 0, 0, 0, 18, 0, 0, 1, 0, 0, 0, 8,
+ 242, 0, 16, 0, 5, 0, 0, 0, 86, 10, 16, 128, 65, 0, 0,
+ 0, 1, 0, 0, 0, 134, 1, 16, 0, 1, 0, 0, 0, 49, 0,
+ 0, 10, 114, 0, 16, 0, 6, 0, 0, 0, 2, 64, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 22, 7, 16, 0, 5, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16,
+ 0, 7, 0, 0, 0, 246, 15, 16, 0, 3, 0, 0, 0, 22, 7,
+ 16, 0, 5, 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 3,
+ 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0,
+ 7, 0, 0, 0, 55, 0, 0, 9, 82, 0, 16, 0, 7, 0, 0,
+ 0, 6, 0, 16, 0, 6, 0, 0, 0, 6, 3, 16, 0, 3, 0,
+ 0, 0, 6, 1, 16, 0, 5, 0, 0, 0, 29, 0, 0, 7, 146,
+ 0, 16, 0, 6, 0, 0, 0, 166, 10, 16, 0, 1, 0, 0, 0,
+ 6, 4, 16, 0, 1, 0, 0, 0, 55, 0, 0, 9, 82, 0, 16,
+ 0, 8, 0, 0, 0, 86, 5, 16, 0, 6, 0, 0, 0, 246, 13,
+ 16, 0, 3, 0, 0, 0, 6, 1, 16, 0, 5, 0, 0, 0, 55,
+ 0, 0, 9, 50, 0, 16, 0, 3, 0, 0, 0, 166, 10, 16, 0,
+ 6, 0, 0, 0, 182, 15, 16, 0, 3, 0, 0, 0, 182, 15, 16,
+ 0, 5, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 8, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66,
+ 0, 16, 0, 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0,
+ 55, 0, 0, 9, 114, 0, 16, 0, 3, 0, 0, 0, 246, 15, 16,
+ 0, 6, 0, 0, 0, 70, 2, 16, 0, 8, 0, 0, 0, 70, 2,
+ 16, 0, 3, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 7,
+ 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 55, 0, 0, 9,
+ 114, 0, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 6, 0, 0,
+ 0, 70, 2, 16, 0, 7, 0, 0, 0, 70, 2, 16, 0, 3, 0,
+ 0, 0, 21, 0, 0, 1, 16, 0, 0, 10, 130, 0, 16, 0, 2,
+ 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0,
+ 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0,
+ 0, 16, 0, 0, 10, 18, 0, 16, 0, 3, 0, 0, 0, 70, 2,
+ 16, 0, 4, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61,
+ 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 0, 0, 0, 8,
+ 130, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0,
+ 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 7, 114, 0, 16, 0, 3, 0, 0, 0, 246, 15, 16, 0, 2,
+ 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 16, 0, 0, 10,
+ 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0,
+ 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71,
+ 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 3,
+ 0, 0, 0, 26, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0,
+ 3, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 3, 0, 0,
+ 0, 42, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 3, 0,
+ 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, 26,
+ 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, 3, 0, 0, 0,
+ 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, 42, 0, 16,
+ 0, 3, 0, 0, 0, 10, 0, 16, 0, 4, 0, 0, 0, 49, 0,
+ 0, 7, 34, 0, 16, 0, 4, 0, 0, 0, 58, 0, 16, 0, 3,
+ 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
+ 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 128, 65, 0, 0,
+ 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 56, 0,
+ 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, 2,
+ 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 0, 0, 0, 8,
+ 130, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0,
+ 0, 58, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 14, 0,
+ 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 5,
+ 0, 0, 0, 246, 15, 16, 0, 3, 0, 0, 0, 0, 0, 0, 7,
+ 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, 2, 0, 0,
+ 0, 70, 2, 16, 0, 5, 0, 0, 0, 55, 0, 0, 9, 114, 0,
+ 16, 0, 3, 0, 0, 0, 86, 5, 16, 0, 4, 0, 0, 0, 70,
+ 2, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0,
+ 49, 0, 0, 7, 130, 0, 16, 0, 3, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 128, 63, 10, 0, 16, 0, 4, 0, 0, 0, 0, 0,
+ 0, 8, 226, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, 128, 65,
+ 0, 0, 0, 2, 0, 0, 0, 6, 9, 16, 0, 3, 0, 0, 0,
+ 0, 0, 0, 8, 18, 0, 16, 0, 5, 0, 0, 0, 58, 0, 16,
+ 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0,
+ 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 4, 0, 0, 0, 86,
+ 14, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, 0,
+ 0, 0, 0, 8, 18, 0, 16, 0, 4, 0, 0, 0, 58, 0, 16,
+ 128, 65, 0, 0, 0, 2, 0, 0, 0, 10, 0, 16, 0, 4, 0,
+ 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 150,
+ 7, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 4, 0, 0, 0,
+ 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16,
+ 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55, 0,
+ 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 246, 15, 16, 0, 3,
+ 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0,
+ 3, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16,
+ 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 14, 0, 0, 0, 31, 0, 4, 3, 58,
+ 0, 16, 0, 2, 0, 0, 0, 16, 0, 0, 10, 130, 0, 16, 0,
+ 2, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0,
+ 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0,
+ 0, 0, 16, 0, 0, 10, 18, 0, 16, 0, 3, 0, 0, 0, 70,
+ 2, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62,
+ 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 0, 0, 0,
+ 8, 130, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, 0, 2, 0,
+ 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 7, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0,
+ 0, 0, 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 16, 0, 0,
+ 10, 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0,
+ 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174,
+ 71, 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0,
+ 3, 0, 0, 0, 26, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16,
+ 0, 3, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 3, 0,
+ 0, 0, 42, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 3,
+ 0, 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0,
+ 26, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, 3, 0, 0,
+ 0, 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, 42, 0,
+ 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, 4, 0, 0, 0, 49,
+ 0, 0, 7, 34, 0, 16, 0, 4, 0, 0, 0, 58, 0, 16, 0,
+ 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 8, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 128, 65, 0,
+ 0, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 56,
+ 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0,
+ 2, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 0, 0, 0,
+ 8, 130, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 2, 0,
+ 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 14,
+ 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0,
+ 5, 0, 0, 0, 246, 15, 16, 0, 3, 0, 0, 0, 0, 0, 0,
+ 7, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, 2, 0,
+ 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 55, 0, 0, 9, 114,
+ 0, 16, 0, 3, 0, 0, 0, 86, 5, 16, 0, 4, 0, 0, 0,
+ 70, 2, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0,
+ 0, 49, 0, 0, 7, 130, 0, 16, 0, 3, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 128, 63, 10, 0, 16, 0, 4, 0, 0, 0, 0,
+ 0, 0, 8, 226, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, 128,
+ 65, 0, 0, 0, 2, 0, 0, 0, 6, 9, 16, 0, 3, 0, 0,
+ 0, 0, 0, 0, 8, 18, 0, 16, 0, 5, 0, 0, 0, 58, 0,
+ 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0,
+ 0, 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 4, 0, 0, 0,
+ 86, 14, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0,
+ 0, 0, 0, 0, 8, 18, 0, 16, 0, 4, 0, 0, 0, 58, 0,
+ 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 10, 0, 16, 0, 4,
+ 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0,
+ 150, 7, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 4, 0, 0,
+ 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15,
+ 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55,
+ 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 246, 15, 16, 0,
+ 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16,
+ 0, 3, 0, 0, 0, 18, 0, 0, 1, 16, 0, 0, 10, 130, 0,
+ 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 2,
+ 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61,
+ 0, 0, 0, 0, 16, 0, 0, 10, 18, 0, 16, 0, 3, 0, 0,
+ 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, 154, 153,
+ 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 0,
+ 0, 0, 8, 130, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, 0,
+ 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2,
+ 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 16,
+ 0, 0, 10, 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0,
+ 1, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23,
+ 63, 174, 71, 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, 18, 0,
+ 16, 0, 3, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10,
+ 0, 16, 0, 1, 0, 0, 0, 51, 0, 0, 7, 18, 0, 16, 0,
+ 3, 0, 0, 0, 42, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16,
+ 0, 3, 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, 3, 0,
+ 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1,
+ 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, 3, 0, 0, 0,
+ 42, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 3, 0, 0,
+ 0, 49, 0, 0, 7, 66, 0, 16, 0, 3, 0, 0, 0, 10, 0,
+ 16, 0, 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0,
+ 1, 0, 0, 0, 246, 15, 16, 128, 65, 0, 0, 0, 2, 0, 0,
+ 0, 56, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15,
+ 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 0,
+ 0, 0, 8, 18, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0,
+ 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0,
+ 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2,
+ 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 3, 0, 0, 0, 0,
+ 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, 0,
+ 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55, 0, 0,
+ 9, 114, 0, 16, 0, 1, 0, 0, 0, 166, 10, 16, 0, 3, 0,
+ 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 1,
+ 0, 0, 0, 49, 0, 0, 7, 18, 0, 16, 0, 3, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0, 128, 63, 26, 0, 16, 0, 3, 0, 0,
+ 0, 0, 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15,
+ 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 70, 2, 16, 0, 1,
+ 0, 0, 0, 0, 0, 0, 8, 66, 0, 16, 0, 3, 0, 0, 0,
+ 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 128, 63, 56, 0, 0, 7, 114, 0, 16, 0, 4, 0,
+ 0, 0, 166, 10, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 4,
+ 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 3, 0, 0, 0,
+ 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 26, 0, 16,
+ 0, 3, 0, 0, 0, 14, 0, 0, 7, 226, 0, 16, 0, 3, 0,
+ 0, 0, 6, 9, 16, 0, 4, 0, 0, 0, 86, 5, 16, 0, 3,
+ 0, 0, 0, 0, 0, 0, 7, 226, 0, 16, 0, 3, 0, 0, 0,
+ 246, 15, 16, 0, 2, 0, 0, 0, 86, 14, 16, 0, 3, 0, 0,
+ 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 6, 0,
+ 16, 0, 3, 0, 0, 0, 150, 7, 16, 0, 3, 0, 0, 0, 70,
+ 2, 16, 0, 1, 0, 0, 0, 21, 0, 0, 1, 21, 0, 0, 1,
+ 21, 0, 0, 1, 0, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0,
+ 0, 58, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 1,
+ 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, 6, 9, 16, 0,
+ 2, 0, 0, 0, 50, 0, 0, 9, 114, 0, 16, 0, 0, 0, 0,
+ 0, 6, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 0, 0,
+ 0, 0, 150, 7, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114,
+ 32, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0,
+ 70, 2, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16,
+ 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 62, 0,
+ 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 195, 0, 0, 0, 9,
+ 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 128, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0,
+ 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 100, 1, 0, 0, 1,
+ 0, 0, 0, 232, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0,
+ 0, 4, 255, 255, 0, 1, 0, 0, 48, 1, 0, 0, 188, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 197,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 209, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0,
+ 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12,
+ 0, 0, 0, 213, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0,
+ 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0,
+ 0, 12, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, 109, 112, 108, 101, 114,
+ 0, 115, 66, 99, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101,
+ 120, 0, 98, 99, 107, 116, 101, 120, 0, 36, 71, 108, 111, 98, 97,
+ 108, 115, 0, 171, 171, 171, 220, 0, 0, 0, 1, 0, 0, 0, 0,
+ 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 24, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0,
+ 0, 32, 1, 0, 0, 0, 0, 0, 0, 98, 108, 101, 110, 100, 111,
+ 112, 0, 0, 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82,
+ 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67,
+ 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48,
+ 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104,
+ 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 12, 0, 0, 0, 83, 86, 95, 80, 111, 115, 105,
+ 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171,
+ 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8,
+ 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95,
+ 84, 97, 114, 103, 101, 116, 0, 171, 171, 176, 56, 0, 0, 0, 0,
+ 0, 0, 83, 97, 109, 112, 108, 101, 82, 97, 100, 105, 97, 108, 71,
+ 114, 97, 100, 105, 101, 110, 116, 0, 65, 80, 111, 115, 0, 44, 7,
+ 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, 176, 254, 27, 44, 22,
+ 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, 0, 44, 7, 0, 0,
+ 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, 0,
+ 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, 6, 0, 0, 65, 111,
+ 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, 252,
+ 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, 0,
+ 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, 0,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0,
+ 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, 0,
+ 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, 15,
+ 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0,
+ 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4,
+ 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160,
+ 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228,
+ 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0,
+ 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, 0,
+ 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, 3,
+ 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, 0,
+ 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, 0,
+ 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 5,
+ 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, 160,
+ 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, 85,
+ 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, 0,
+ 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, 8,
+ 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, 160,
+ 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, 0,
+ 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, 0,
+ 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, 0,
+ 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0,
+ 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101,
+ 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, 2,
+ 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, 0,
+ 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, 0,
+ 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, 18,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, 16,
+ 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, 50,
+ 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0,
+ 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, 0,
+ 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0,
+ 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0,
+ 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, 0,
+ 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0,
+ 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16,
+ 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0,
+ 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, 0,
+ 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16,
+ 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 156, 2, 0,
+ 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, 0,
+ 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 103, 2, 0, 0, 92,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, 0, 92, 0, 0, 0,
+ 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, 0, 0, 0, 52, 1,
+ 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244,
+ 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, 0,
+ 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
+ 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171,
+ 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97,
+ 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120,
+ 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, 1, 0, 0, 0, 0,
+ 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 244, 1, 0, 0, 0,
+ 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0,
+ 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0,
+ 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 40, 2,
+ 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 80, 0, 0, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0,
+ 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, 2, 0, 0, 92, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0,
+ 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 68, 101, 118,
+ 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, 114, 83,
+ 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111,
+ 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0,
+ 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99,
+ 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, 3, 0,
+ 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100,
+ 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49,
+ 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32,
+ 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46,
+ 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 44, 0,
+ 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171,
+ 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0,
+ 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0,
+ 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83,
+ 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67,
+ 79, 79, 82, 68, 0, 171, 171, 171, 174, 94, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 224, 9,
+ 0, 0, 68, 88, 66, 67, 76, 106, 34, 250, 169, 50, 124, 43, 130,
+ 255, 198, 178, 126, 127, 40, 188, 1, 0, 0, 0, 224, 9, 0, 0,
+ 6, 0, 0, 0, 56, 0, 0, 0, 128, 2, 0, 0, 88, 6, 0,
+ 0, 212, 6, 0, 0, 60, 9, 0, 0, 172, 9, 0, 0, 65, 111,
+ 110, 57, 64, 2, 0, 0, 64, 2, 0, 0, 0, 2, 255, 255, 8,
+ 2, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0,
+ 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 3, 0, 15, 160, 0,
+ 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 128, 63, 0, 0, 128,
+ 191, 0, 0, 0, 0, 0, 0, 0, 128, 31, 0, 0, 2, 0, 0,
+ 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0,
+ 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160,
+ 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, 1, 0, 228,
+ 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0,
+ 228, 128, 2, 0, 0, 161, 5, 0, 0, 3, 0, 0, 8, 128, 0,
+ 0, 255, 128, 1, 0, 170, 160, 1, 0, 0, 2, 0, 0, 4, 128,
+ 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228,
+ 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, 0, 2, 128, 0, 0,
+ 0, 128, 0, 0, 0, 128, 0, 0, 255, 129, 35, 0, 0, 2, 0,
+ 0, 4, 128, 0, 0, 85, 128, 7, 0, 0, 2, 0, 0, 4, 128,
+ 0, 0, 170, 128, 6, 0, 0, 2, 1, 0, 1, 128, 0, 0, 170,
+ 128, 1, 0, 0, 2, 1, 0, 6, 128, 1, 0, 0, 129, 2, 0,
+ 0, 3, 0, 0, 13, 128, 0, 0, 0, 128, 1, 0, 148, 128, 6,
+ 0, 0, 2, 1, 0, 1, 128, 1, 0, 170, 160, 5, 0, 0, 3,
+ 0, 0, 13, 128, 0, 0, 228, 128, 1, 0, 0, 128, 1, 0, 0,
+ 2, 1, 0, 8, 128, 1, 0, 255, 160, 4, 0, 0, 4, 1, 0,
+ 7, 128, 0, 0, 248, 128, 0, 0, 170, 160, 1, 0, 255, 128, 88,
+ 0, 0, 4, 2, 0, 1, 128, 1, 0, 0, 128, 0, 0, 0, 128,
+ 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, 13, 128, 1, 0, 148,
+ 128, 4, 0, 68, 160, 4, 0, 230, 160, 1, 0, 0, 2, 2, 0,
+ 2, 128, 3, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 0,
+ 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128,
+ 2, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 2, 0, 7,
+ 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0,
+ 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, 2, 0, 0, 3, 0,
+ 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, 128, 88, 0, 0, 4,
+ 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, 0, 0, 170,
+ 128, 88, 0, 0, 4, 1, 0, 15, 128, 0, 0, 0, 129, 4, 0,
+ 170, 160, 1, 0, 228, 128, 88, 0, 0, 4, 0, 0, 15, 128, 0,
+ 0, 85, 128, 1, 0, 228, 128, 4, 0, 170, 160, 1, 0, 0, 2,
+ 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68,
+ 82, 208, 3, 0, 0, 64, 0, 0, 0, 244, 0, 0, 0, 89, 0,
+ 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 90,
+ 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3,
+ 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16,
+ 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112,
+ 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50,
+ 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0,
+ 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0,
+ 0, 104, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 9, 50, 0,
+ 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, 0, 0, 0, 70,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0,
+ 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32,
+ 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, 0, 8, 66, 0,
+ 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70,
+ 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 7,
+ 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0,
+ 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0,
+ 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 5, 0,
+ 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 42,
+ 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0,
+ 7, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 75, 0, 0, 6, 18,
+ 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0,
+ 0, 0, 0, 0, 54, 0, 0, 6, 34, 0, 16, 0, 1, 0, 0,
+ 0, 10, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 7, 82, 0, 16, 0, 0, 0, 0, 0, 166, 10, 16, 0, 0,
+ 0, 0, 0, 6, 1, 16, 0, 1, 0, 0, 0, 14, 0, 0, 8,
+ 82, 0, 16, 0, 0, 0, 0, 0, 6, 2, 16, 0, 0, 0, 0,
+ 0, 166, 138, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 56, 0,
+ 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, 134, 0, 16, 0, 0,
+ 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0,
+ 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16,
+ 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, 0, 0, 0, 0, 0,
+ 0, 0, 5, 0, 0, 0, 1, 0, 0, 10, 50, 0, 16, 0, 1,
+ 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0,
+ 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 42, 0,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 2, 0, 0, 0,
+ 10, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0,
+ 16, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69,
+ 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, 0,
+ 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16,
+ 0, 0, 0, 0, 0, 31, 0, 4, 3, 26, 0, 16, 0, 0, 0,
+ 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1, 52, 0, 0,
+ 7, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 1, 0,
+ 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 29, 0, 0, 7, 18,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16,
+ 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1,
+ 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0, 246, 15, 16,
+ 0, 2, 0, 0, 0, 70, 2, 16, 0, 2, 0, 0, 0, 69, 0,
+ 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1,
+ 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0,
+ 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, 0, 0, 0,
+ 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 2, 0,
+ 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 33,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 19, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 96,
+ 2, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0, 5, 0, 0, 0,
+ 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 43, 2, 0,
+ 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0, 0, 0, 5, 0,
+ 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1,
+ 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0, 0,
+ 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0,
+ 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, 109,
+ 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108,
+ 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 50,
+ 0, 171, 219, 0, 0, 0, 7, 0, 0, 0, 248, 0, 0, 0, 112,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 1, 0, 0,
+ 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 184, 1, 0,
+ 0, 0, 0, 0, 0, 200, 1, 0, 0, 48, 0, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0, 212, 1, 0, 0, 0, 0, 0, 0, 228,
+ 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0,
+ 236, 1, 0, 0, 0, 0, 0, 0, 252, 1, 0, 0, 80, 0, 0,
+ 0, 8, 0, 0, 0, 2, 0, 0, 0, 212, 1, 0, 0, 0, 0,
+ 0, 0, 4, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, 2,
+ 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 24, 2, 0, 0,
+ 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 8, 2, 0,
+ 0, 0, 0, 0, 0, 32, 2, 0, 0, 96, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 68,
+ 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101,
+ 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115,
+ 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171,
+ 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, 0,
+ 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114,
+ 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117,
+ 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82,
+ 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67,
+ 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48,
+ 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, 78,
+ 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0,
+ 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115,
+ 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0,
+ 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0,
+ 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86,
+ 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 242, 101, 0, 0, 0,
+ 0, 0, 0, 65, 48, 0, 44, 7, 0, 0, 68, 88, 66, 67, 172,
+ 27, 205, 113, 176, 254, 27, 44, 22, 107, 179, 112, 127, 38, 148, 161,
+ 1, 0, 0, 0, 44, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0,
+ 0, 148, 1, 0, 0, 104, 3, 0, 0, 228, 3, 0, 0, 136, 6,
+ 0, 0, 188, 6, 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84,
+ 1, 0, 0, 0, 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0,
+ 4, 0, 36, 0, 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36,
+ 0, 1, 0, 84, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0,
+ 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254,
+ 255, 81, 0, 0, 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0,
+ 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5,
+ 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224,
+ 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0,
+ 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0,
+ 228, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6,
+ 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128,
+ 5, 0, 0, 160, 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170,
+ 128, 6, 0, 85, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0,
+ 85, 129, 6, 0, 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0,
+ 0, 228, 128, 0, 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128,
+ 0, 0, 170, 128, 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2,
+ 128, 0, 0, 0, 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0,
+ 4, 128, 6, 0, 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1,
+ 0, 228, 128, 3, 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224,
+ 1, 0, 228, 128, 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12,
+ 192, 6, 0, 36, 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1,
+ 0, 0, 64, 0, 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70,
+ 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4,
+ 70, 142, 32, 0, 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0,
+ 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32,
+ 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50,
+ 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0,
+ 1, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0,
+ 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50,
+ 0, 0, 11, 50, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0,
+ 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0,
+ 0, 5, 50, 32, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128,
+ 63, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0,
+ 0, 128, 63, 56, 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0,
+ 70, 0, 16, 0, 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0,
+ 0, 3, 0, 0, 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0,
+ 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0,
+ 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0,
+ 54, 0, 0, 5, 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 128, 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0,
+ 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0,
+ 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32,
+ 0, 1, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32,
+ 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230,
+ 138, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65,
+ 84, 116, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 82, 68, 69, 70, 156, 2, 0, 0, 2, 0, 0, 0, 100, 0,
+ 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0,
+ 1, 0, 0, 103, 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0,
+ 99, 98, 50, 0, 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0,
+ 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0,
+ 0, 0, 7, 0, 0, 0, 52, 1, 0, 0, 112, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0,
+ 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 16, 1, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32,
+ 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117,
+ 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1,
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67,
+ 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111,
+ 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0,
+ 171, 171, 220, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2,
+ 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0,
+ 48, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0,
+ 0, 0, 0, 0, 0, 32, 2, 0, 0, 64, 0, 0, 0, 12, 0,
+ 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 0, 0, 0, 0, 56,
+ 2, 0, 0, 80, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
+ 16, 2, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 88, 0, 0,
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0,
+ 0, 0, 84, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 92, 2, 0, 0,
+ 96, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0,
+ 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99,
+ 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3,
+ 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3,
+ 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105,
+ 102, 102, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0,
+ 65, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113,
+ 95, 114, 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115,
+ 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104,
+ 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54,
+ 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171,
+ 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8,
+ 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83,
+ 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0,
+ 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116,
+ 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171,
+ 171, 225, 111, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 192, 7, 0, 0, 68, 88, 66, 67, 73,
+ 174, 125, 52, 147, 212, 172, 159, 223, 39, 1, 144, 137, 10, 201, 206,
+ 1, 0, 0, 0, 192, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0,
+ 0, 196, 1, 0, 0, 56, 4, 0, 0, 180, 4, 0, 0, 28, 7,
+ 0, 0, 140, 7, 0, 0, 65, 111, 110, 57, 132, 1, 0, 0, 132,
+ 1, 0, 0, 0, 2, 255, 255, 76, 1, 0, 0, 56, 0, 0, 0,
+ 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36,
+ 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
+ 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 81,
+ 0, 0, 5, 2, 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, 0,
+ 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8,
+ 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 5,
+ 0, 0, 3, 0, 0, 8, 128, 1, 0, 255, 160, 1, 0, 255, 160,
+ 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, 1, 0, 228,
+ 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0,
+ 228, 128, 0, 0, 255, 129, 5, 0, 0, 3, 0, 0, 8, 128, 0,
+ 0, 255, 128, 2, 0, 0, 160, 1, 0, 0, 2, 0, 0, 4, 128,
+ 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228,
+ 128, 0, 0, 228, 160, 6, 0, 0, 2, 0, 0, 1, 128, 0, 0,
+ 0, 128, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0,
+ 0, 255, 128, 1, 0, 0, 2, 0, 0, 2, 128, 2, 0, 0, 160,
+ 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228,
+ 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, 228, 128, 0, 8,
+ 228, 160, 1, 0, 0, 2, 0, 0, 8, 128, 1, 0, 255, 160, 4,
+ 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, 161,
+ 0, 0, 255, 129, 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 255,
+ 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0, 15, 128, 1, 0,
+ 255, 128, 2, 0, 228, 128, 88, 0, 0, 4, 0, 0, 15, 128, 0,
+ 0, 0, 128, 2, 0, 85, 160, 1, 0, 228, 128, 1, 0, 0, 2,
+ 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68,
+ 82, 108, 2, 0, 0, 64, 0, 0, 0, 155, 0, 0, 0, 89, 0,
+ 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 90,
+ 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3,
+ 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16,
+ 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112,
+ 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50,
+ 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0,
+ 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0,
+ 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, 0,
+ 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, 0, 0, 0, 70,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0,
+ 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32,
+ 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, 0, 8, 66, 0,
+ 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70,
+ 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 7,
+ 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0,
+ 0, 70, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 12, 18, 0,
+ 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0,
+ 0, 0, 0, 5, 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, 0,
+ 5, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0,
+ 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 14, 0, 0, 7, 18,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 8, 66, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128,
+ 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 29, 0, 0, 9, 66,
+ 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65, 0, 0, 0,
+ 0, 0, 0, 0, 5, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0,
+ 0, 54, 0, 0, 5, 34, 0, 16, 0, 0, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 1,
+ 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0,
+ 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4,
+ 3, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32,
+ 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1,
+ 21, 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0,
+ 0, 246, 15, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0,
+ 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70,
+ 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0,
+ 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16,
+ 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14,
+ 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116,
+ 0, 0, 0, 19, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82,
+ 68, 69, 70, 96, 2, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0,
+ 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0,
+ 0, 43, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0,
+ 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0,
+ 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0,
+ 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255,
+ 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 115, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83,
+ 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107,
+ 0, 99, 98, 50, 0, 171, 219, 0, 0, 0, 7, 0, 0, 0, 248,
+ 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 160, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0,
+ 0, 184, 1, 0, 0, 0, 0, 0, 0, 200, 1, 0, 0, 48, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 212, 1, 0, 0, 0,
+ 0, 0, 0, 228, 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0,
+ 2, 0, 0, 0, 236, 1, 0, 0, 0, 0, 0, 0, 252, 1, 0,
+ 0, 80, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 212, 1,
+ 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 88, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0,
+ 24, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0,
+ 0, 8, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 96, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0,
+ 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84,
+ 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3,
+ 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105,
+ 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102,
+ 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0,
+ 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114,
+ 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102,
+ 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100,
+ 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51,
+ 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171,
+ 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0,
+ 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86,
+ 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79,
+ 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0,
+ 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0,
+ 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 37,
+ 119, 0, 0, 0, 0, 0, 0, 65, 80, 111, 115, 87, 114, 97, 112,
+ 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, 176, 254,
+ 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, 0, 44,
+ 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, 0,
+ 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, 6, 0,
+ 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, 2,
+ 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, 0,
+ 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, 0,
+ 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0,
+ 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5,
+ 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 0,
+ 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0,
+ 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2,
+ 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, 128,
+ 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0,
+ 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, 0,
+ 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, 5,
+ 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, 160,
+ 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, 0,
+ 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0,
+ 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, 5,
+ 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, 128,
+ 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, 0,
+ 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, 0,
+ 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, 4,
+ 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, 160,
+ 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, 1,
+ 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 1,
+ 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0,
+ 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0,
+ 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 104,
+ 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0,
+ 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 0,
+ 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230,
+ 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, 16,
+ 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, 8,
+ 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0,
+ 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0,
+ 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, 0,
+ 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0,
+ 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66,
+ 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63,
+ 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16,
+ 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, 70,
+ 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0,
+ 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0,
+ 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70,
+ 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, 0,
+ 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 103, 2,
+ 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, 0, 92,
+ 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, 0, 0,
+ 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0,
+ 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, 0,
+ 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40,
+ 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115,
+ 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115,
+ 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0,
+ 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, 1, 0,
+ 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 244, 1,
+ 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, 0, 8,
+ 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0,
+ 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0,
+ 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 80, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0,
+ 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, 2, 0,
+ 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2,
+ 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0,
+ 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115,
+ 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110,
+ 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171,
+ 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0,
+ 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105,
+ 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40,
+ 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32,
+ 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54,
+ 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71,
+ 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78,
+ 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0,
+ 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3,
+ 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84,
+ 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 250, 126, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 228, 9, 0, 0, 68, 88, 66, 67, 193, 68, 83, 4, 120, 206,
+ 206, 65, 213, 56, 189, 186, 120, 85, 235, 59, 1, 0, 0, 0, 228,
+ 9, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 128, 2, 0, 0,
+ 88, 6, 0, 0, 212, 6, 0, 0, 64, 9, 0, 0, 176, 9, 0,
+ 0, 65, 111, 110, 57, 64, 2, 0, 0, 64, 2, 0, 0, 0, 2,
+ 255, 255, 8, 2, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0,
+ 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 4, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 3, 0,
+ 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 128, 63,
+ 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 128, 31, 0, 0,
+ 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0,
+ 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1,
+ 8, 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176,
+ 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228,
+ 128, 0, 0, 228, 128, 2, 0, 0, 161, 5, 0, 0, 3, 0, 0,
+ 8, 128, 0, 0, 255, 128, 1, 0, 170, 160, 1, 0, 0, 2, 0,
+ 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, 128,
+ 0, 0, 228, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, 0, 2,
+ 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 129, 35, 0,
+ 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 7, 0, 0, 2, 0,
+ 0, 4, 128, 0, 0, 170, 128, 6, 0, 0, 2, 1, 0, 1, 128,
+ 0, 0, 170, 128, 1, 0, 0, 2, 1, 0, 6, 128, 1, 0, 0,
+ 129, 2, 0, 0, 3, 0, 0, 13, 128, 0, 0, 0, 128, 1, 0,
+ 148, 128, 6, 0, 0, 2, 1, 0, 1, 128, 1, 0, 170, 160, 5,
+ 0, 0, 3, 0, 0, 13, 128, 0, 0, 228, 128, 1, 0, 0, 128,
+ 1, 0, 0, 2, 1, 0, 8, 128, 1, 0, 255, 160, 4, 0, 0,
+ 4, 1, 0, 7, 128, 0, 0, 248, 128, 0, 0, 170, 160, 1, 0,
+ 255, 128, 88, 0, 0, 4, 2, 0, 1, 128, 1, 0, 0, 128, 0,
+ 0, 0, 128, 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, 13, 128,
+ 1, 0, 148, 128, 4, 0, 68, 160, 4, 0, 230, 160, 1, 0, 0,
+ 2, 2, 0, 2, 128, 3, 0, 0, 160, 66, 0, 0, 3, 1, 0,
+ 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 2,
+ 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3,
+ 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0,
+ 3, 1, 0, 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, 2, 0,
+ 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, 128, 88,
+ 0, 0, 4, 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128,
+ 0, 0, 170, 128, 88, 0, 0, 4, 1, 0, 15, 128, 0, 0, 0,
+ 129, 4, 0, 170, 160, 1, 0, 228, 128, 88, 0, 0, 4, 0, 0,
+ 15, 128, 0, 0, 85, 128, 1, 0, 228, 128, 4, 0, 170, 160, 1,
+ 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0,
+ 83, 72, 68, 82, 208, 3, 0, 0, 64, 0, 0, 0, 244, 0, 0,
+ 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 7, 0,
+ 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90,
+ 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4,
+ 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0,
+ 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16,
+ 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194,
+ 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0,
+ 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0,
+ 9, 50, 0, 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, 0,
+ 0, 0, 70, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0,
+ 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, 0,
+ 8, 66, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0,
+ 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 15,
+ 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0,
+ 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0,
+ 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 6,
+ 0, 0, 0, 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0,
+ 0, 5, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0,
+ 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 49, 0, 0, 7, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 75, 0,
+ 0, 6, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 128, 129,
+ 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 6, 34, 0, 16, 0,
+ 1, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 7, 82, 0, 16, 0, 0, 0, 0, 0, 166, 10,
+ 16, 0, 0, 0, 0, 0, 6, 1, 16, 0, 1, 0, 0, 0, 14,
+ 0, 0, 8, 82, 0, 16, 0, 0, 0, 0, 0, 6, 2, 16, 0,
+ 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 5, 0, 0,
+ 0, 56, 0, 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, 134, 0,
+ 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, 0, 0,
+ 70, 0, 16, 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 10, 50, 0,
+ 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0,
+ 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 2,
+ 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0,
+ 5, 34, 0, 16, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0,
+ 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70,
+ 0, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0,
+ 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 26, 0, 16,
+ 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1,
+ 52, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16,
+ 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 29, 0,
+ 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3,
+ 10, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16,
+ 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21,
+ 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0,
+ 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 2, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 16,
+ 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0,
+ 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0,
+ 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, 16,
+ 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 33, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 100, 2, 0, 0, 1, 0, 0, 0, 228, 0, 0, 0, 5,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0,
+ 47, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0,
+ 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0,
+ 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 218, 0, 0, 0, 2,
+ 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255,
+ 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 223, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115,
+ 87, 114, 97, 112, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97,
+ 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109,
+ 97, 115, 107, 0, 99, 98, 50, 0, 171, 223, 0, 0, 0, 7, 0,
+ 0, 0, 252, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0,
+ 0, 0, 0, 0, 188, 1, 0, 0, 0, 0, 0, 0, 204, 1, 0,
+ 0, 48, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 216, 1,
+ 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, 64, 0, 0, 0, 12,
+ 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 80, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0,
+ 0, 216, 1, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 88, 0,
+ 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 12, 2, 0, 0, 0,
+ 0, 0, 0, 28, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 12, 2, 0, 0, 0, 0, 0, 0, 36, 2, 0,
+ 0, 96, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 12, 2,
+ 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97,
+ 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171,
+ 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0,
+ 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100,
+ 105, 102, 102, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49,
+ 0, 65, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115,
+ 113, 95, 114, 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111,
+ 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83,
+ 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32,
+ 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0,
+ 171, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0,
+ 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 12, 0,
+ 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69,
+ 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44,
+ 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0,
+ 171, 171, 62, 134, 0, 0, 0, 0, 0, 0, 65, 48, 87, 114, 97,
+ 112, 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, 176,
+ 254, 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, 0,
+ 44, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0,
+ 0, 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, 6,
+ 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0,
+ 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0,
+ 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84,
+ 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2,
+ 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0,
+ 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0,
+ 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0,
+ 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144,
+ 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3,
+ 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0,
+ 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5,
+ 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160,
+ 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85,
+ 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0,
+ 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0,
+ 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128,
+ 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0,
+ 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0,
+ 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3,
+ 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128,
+ 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36,
+ 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0,
+ 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0,
+ 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16,
+ 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1,
+ 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0,
+ 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16,
+ 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50,
+ 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0,
+ 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32,
+ 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0,
+ 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0,
+ 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56,
+ 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0,
+ 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0,
+ 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0,
+ 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0,
+ 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5,
+ 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128,
+ 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2,
+ 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0,
+ 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0,
+ 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0,
+ 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0,
+ 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69,
+ 70, 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0,
+ 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 103,
+ 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, 0,
+ 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, 0,
+ 0, 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0,
+ 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0,
+ 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16,
+ 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101,
+ 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100,
+ 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115,
+ 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, 1,
+ 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 244,
+ 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, 0,
+ 8, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0,
+ 0, 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, 0,
+ 0, 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 80,
+ 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0,
+ 0, 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, 2,
+ 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68,
+ 2, 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0,
+ 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85,
+ 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101,
+ 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171,
+ 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171,
+ 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100,
+ 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32,
+ 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114,
+ 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57,
+ 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83,
+ 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79,
+ 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0,
+ 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
+ 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12,
+ 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0,
+ 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 53, 144, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 196, 7, 0, 0, 68, 88, 66, 67, 223, 174, 80, 104, 241,
+ 52, 44, 173, 100, 134, 52, 219, 15, 210, 214, 245, 1, 0, 0, 0,
+ 196, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 196, 1, 0,
+ 0, 56, 4, 0, 0, 180, 4, 0, 0, 32, 7, 0, 0, 144, 7,
+ 0, 0, 65, 111, 110, 57, 132, 1, 0, 0, 132, 1, 0, 0, 0,
+ 2, 255, 255, 76, 1, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0,
+ 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56,
+ 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 4, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 2,
+ 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15,
+ 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 31, 0,
+ 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 5, 0, 0, 3, 0,
+ 0, 8, 128, 1, 0, 255, 160, 1, 0, 255, 160, 2, 0, 0, 3,
+ 0, 0, 3, 128, 0, 0, 235, 176, 1, 0, 228, 161, 90, 0, 0,
+ 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0, 228, 128, 0, 0,
+ 255, 129, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 2,
+ 0, 0, 160, 1, 0, 0, 2, 0, 0, 4, 128, 1, 0, 255, 160,
+ 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228, 128, 0, 0, 228,
+ 160, 6, 0, 0, 2, 0, 0, 1, 128, 0, 0, 0, 128, 5, 0,
+ 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 128, 1,
+ 0, 0, 2, 0, 0, 2, 128, 2, 0, 0, 160, 66, 0, 0, 3,
+ 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0,
+ 3, 2, 0, 15, 128, 0, 0, 228, 128, 0, 8, 228, 160, 1, 0,
+ 0, 2, 0, 0, 8, 128, 1, 0, 255, 160, 4, 0, 0, 4, 0,
+ 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, 161, 0, 0, 255, 129,
+ 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228,
+ 128, 5, 0, 0, 3, 1, 0, 15, 128, 1, 0, 255, 128, 2, 0,
+ 228, 128, 88, 0, 0, 4, 0, 0, 15, 128, 0, 0, 0, 128, 2,
+ 0, 85, 160, 1, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128,
+ 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 108, 2, 0,
+ 0, 64, 0, 0, 0, 155, 0, 0, 0, 89, 0, 0, 4, 70, 142,
+ 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 90, 0, 0, 3, 0,
+ 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0,
+ 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0,
+ 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0,
+ 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1,
+ 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, 1, 0, 0, 0,
+ 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0,
+ 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, 0, 16, 0, 0, 0,
+ 0, 0, 230, 26, 16, 0, 1, 0, 0, 0, 70, 128, 32, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 54, 0, 0, 6,
+ 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 0, 0, 0, 0,
+ 0, 5, 0, 0, 0, 16, 0, 0, 8, 66, 0, 16, 0, 0, 0,
+ 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 130, 32, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 7, 18, 0, 16, 0,
+ 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 50, 0, 0, 12, 18, 0, 16, 0, 0, 0,
+ 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 18, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 0, 63, 14, 0, 0, 7, 18, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0,
+ 0, 0, 0, 0, 56, 0, 0, 8, 66, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0,
+ 0, 0, 4, 0, 0, 0, 29, 0, 0, 9, 66, 0, 16, 0, 0,
+ 0, 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 5, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0,
+ 5, 34, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0,
+ 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70,
+ 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0,
+ 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 42, 0, 16,
+ 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1,
+ 56, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 246, 15, 16,
+ 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 69, 0,
+ 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1,
+ 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0,
+ 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, 0, 0, 0,
+ 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 1, 0,
+ 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 19,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 100,
+ 2, 0, 0, 1, 0, 0, 0, 228, 0, 0, 0, 5, 0, 0, 0,
+ 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 47, 2, 0,
+ 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0, 0, 5, 0,
+ 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1,
+ 0, 0, 0, 12, 0, 0, 0, 218, 0, 0, 0, 2, 0, 0, 0,
+ 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0,
+ 0, 1, 0, 0, 0, 12, 0, 0, 0, 223, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, 87, 114, 97,
+ 112, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83,
+ 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107,
+ 0, 99, 98, 50, 0, 171, 223, 0, 0, 0, 7, 0, 0, 0, 252,
+ 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 164, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0,
+ 0, 188, 1, 0, 0, 0, 0, 0, 0, 204, 1, 0, 0, 48, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 216, 1, 0, 0, 0,
+ 0, 0, 0, 232, 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0,
+ 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 80, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 216, 1,
+ 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 88, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 12, 2, 0, 0, 0, 0, 0, 0,
+ 28, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0,
+ 0, 12, 2, 0, 0, 0, 0, 0, 0, 36, 2, 0, 0, 96, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 12, 2, 0, 0, 0,
+ 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84,
+ 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3,
+ 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105,
+ 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102,
+ 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0,
+ 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114,
+ 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102,
+ 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100,
+ 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51,
+ 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171,
+ 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0,
+ 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86,
+ 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79,
+ 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0,
+ 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0,
+ 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 121,
+ 151, 0, 0, 0, 0, 0, 0, 65, 80, 111, 115, 77, 105, 114, 114,
+ 111, 114, 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, 27, 205, 113,
+ 176, 254, 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, 1, 0, 0,
+ 0, 44, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1,
+ 0, 0, 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, 0, 0, 188,
+ 6, 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0,
+ 0, 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36,
+ 0, 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0,
+ 84, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 2, 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0,
+ 0, 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0,
+ 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128,
+ 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228,
+ 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0,
+ 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2,
+ 0, 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160,
+ 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0,
+ 160, 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0,
+ 85, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6,
+ 0, 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128,
+ 0, 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170,
+ 128, 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0,
+ 0, 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6,
+ 0, 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128,
+ 3, 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228,
+ 128, 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0,
+ 36, 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64,
+ 0, 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32,
+ 0, 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16,
+ 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0,
+ 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0,
+ 0, 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32,
+ 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11,
+ 50, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0,
+ 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50,
+ 32, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0,
+ 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63,
+ 56, 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0,
+ 0, 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70,
+ 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63,
+ 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0,
+ 5, 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0,
+ 128, 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70,
+ 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0,
+ 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0,
+ 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1,
+ 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0,
+ 103, 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 50,
+ 0, 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 7,
+ 0, 0, 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0,
+ 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1,
+ 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0,
+ 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68,
+ 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114,
+ 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100,
+ 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 220,
+ 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0,
+ 244, 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 48, 0, 0,
+ 0, 8, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, 0,
+ 0, 0, 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 0,
+ 0, 0, 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0,
+ 80, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 2, 0,
+ 0, 0, 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 84,
+ 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
+ 68, 2, 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, 96, 0, 0,
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0,
+ 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111,
+ 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0,
+ 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109,
+ 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0,
+ 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171,
+ 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97,
+ 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116,
+ 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101,
+ 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46,
+ 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73,
+ 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73,
+ 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3,
+ 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0,
+ 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110,
+ 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 84, 159,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 232, 9, 0, 0, 68, 88, 66, 67, 48, 133, 157, 76,
+ 135, 209, 82, 153, 49, 138, 172, 57, 31, 63, 161, 231, 1, 0, 0,
+ 0, 232, 9, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 128, 2,
+ 0, 0, 88, 6, 0, 0, 212, 6, 0, 0, 68, 9, 0, 0, 180,
+ 9, 0, 0, 65, 111, 110, 57, 64, 2, 0, 0, 64, 2, 0, 0,
+ 0, 2, 255, 255, 8, 2, 0, 0, 56, 0, 0, 0, 1, 0, 44,
+ 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0,
+ 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 4, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5,
+ 3, 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0,
+ 128, 63, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 128, 31,
+ 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2,
+ 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0,
+ 144, 1, 8, 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0,
+ 235, 176, 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0,
+ 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 161, 5, 0, 0, 3,
+ 0, 0, 8, 128, 0, 0, 255, 128, 1, 0, 170, 160, 1, 0, 0,
+ 2, 0, 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0,
+ 1, 128, 0, 0, 228, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0,
+ 0, 2, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 129,
+ 35, 0, 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 7, 0, 0,
+ 2, 0, 0, 4, 128, 0, 0, 170, 128, 6, 0, 0, 2, 1, 0,
+ 1, 128, 0, 0, 170, 128, 1, 0, 0, 2, 1, 0, 6, 128, 1,
+ 0, 0, 129, 2, 0, 0, 3, 0, 0, 13, 128, 0, 0, 0, 128,
+ 1, 0, 148, 128, 6, 0, 0, 2, 1, 0, 1, 128, 1, 0, 170,
+ 160, 5, 0, 0, 3, 0, 0, 13, 128, 0, 0, 228, 128, 1, 0,
+ 0, 128, 1, 0, 0, 2, 1, 0, 8, 128, 1, 0, 255, 160, 4,
+ 0, 0, 4, 1, 0, 7, 128, 0, 0, 248, 128, 0, 0, 170, 160,
+ 1, 0, 255, 128, 88, 0, 0, 4, 2, 0, 1, 128, 1, 0, 0,
+ 128, 0, 0, 0, 128, 0, 0, 255, 128, 88, 0, 0, 4, 0, 0,
+ 13, 128, 1, 0, 148, 128, 4, 0, 68, 160, 4, 0, 230, 160, 1,
+ 0, 0, 2, 2, 0, 2, 128, 3, 0, 0, 160, 66, 0, 0, 3,
+ 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0,
+ 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 5, 0,
+ 0, 3, 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228, 128, 5,
+ 0, 0, 3, 1, 0, 15, 128, 1, 0, 255, 128, 2, 0, 228, 128,
+ 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 0, 0, 0,
+ 128, 88, 0, 0, 4, 0, 0, 1, 128, 0, 0, 255, 128, 0, 0,
+ 0, 128, 0, 0, 170, 128, 88, 0, 0, 4, 1, 0, 15, 128, 0,
+ 0, 0, 129, 4, 0, 170, 160, 1, 0, 228, 128, 88, 0, 0, 4,
+ 0, 0, 15, 128, 0, 0, 85, 128, 1, 0, 228, 128, 4, 0, 170,
+ 160, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255,
+ 0, 0, 83, 72, 68, 82, 208, 3, 0, 0, 64, 0, 0, 0, 244,
+ 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0,
+ 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24,
+ 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88,
+ 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0,
+ 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0,
+ 3, 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32,
+ 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0, 0, 0,
+ 0, 0, 9, 50, 0, 16, 0, 0, 0, 0, 0, 230, 26, 16, 0,
+ 1, 0, 0, 0, 70, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0,
+ 0, 5, 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, 0,
+ 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 16,
+ 0, 0, 8, 66, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0,
+ 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 4, 0, 0,
+ 0, 15, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 70, 0,
+ 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0,
+ 0, 6, 0, 0, 0, 56, 0, 0, 8, 18, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0,
+ 0, 0, 0, 5, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0,
+ 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0,
+ 0, 0, 49, 0, 0, 7, 34, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0,
+ 75, 0, 0, 6, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16,
+ 128, 129, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 6, 34, 0,
+ 16, 0, 1, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 7, 82, 0, 16, 0, 0, 0, 0, 0,
+ 166, 10, 16, 0, 0, 0, 0, 0, 6, 1, 16, 0, 1, 0, 0,
+ 0, 14, 0, 0, 8, 82, 0, 16, 0, 0, 0, 0, 0, 6, 2,
+ 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0, 56, 0, 0, 8, 50, 0, 16, 0, 1, 0, 0, 0,
+ 134, 0, 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0,
+ 0, 4, 0, 0, 0, 29, 0, 0, 9, 50, 0, 16, 0, 1, 0,
+ 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 246, 143, 32, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 10,
+ 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0,
+ 0, 0, 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16,
+ 0, 2, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 54,
+ 0, 0, 5, 34, 0, 16, 0, 2, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0,
+ 0, 70, 0, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0,
+ 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 26,
+ 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0,
+ 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0,
+ 0, 1, 52, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 26,
+ 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0,
+ 29, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 31, 0,
+ 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242,
+ 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0,
+ 1, 21, 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0,
+ 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 2,
+ 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0,
+ 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0,
+ 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32,
+ 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70,
+ 14, 16, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84,
+ 116, 0, 0, 0, 33, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82, 68, 69, 70, 104, 2, 0, 0, 1, 0, 0, 0, 232, 0, 0,
+ 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1,
+ 0, 0, 51, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 2,
+ 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255,
+ 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, 0,
+ 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255,
+ 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 225,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, 108, 101, 114,
+ 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116,
+ 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 50, 0, 171, 171, 171,
+ 225, 0, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0, 112, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 1, 0, 0, 0, 0,
+ 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 192, 1, 0, 0, 0,
+ 0, 0, 0, 208, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 220, 1, 0, 0, 0, 0, 0, 0, 236, 1, 0,
+ 0, 64, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 244, 1,
+ 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 80, 0, 0, 0, 8,
+ 0, 0, 0, 2, 0, 0, 0, 220, 1, 0, 0, 0, 0, 0, 0,
+ 12, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0,
+ 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 92, 0,
+ 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0,
+ 0, 0, 0, 40, 2, 0, 0, 96, 0, 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 68, 101, 118,
+ 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, 114, 83,
+ 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111,
+ 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0,
+ 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99,
+ 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, 3, 0,
+ 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100,
+ 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49,
+ 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32,
+ 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46,
+ 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 104, 0,
+ 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116,
+ 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171,
+ 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0,
+ 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84,
+ 97, 114, 103, 101, 116, 0, 171, 171, 152, 166, 0, 0, 0, 0, 0,
+ 0, 65, 48, 77, 105, 114, 114, 111, 114, 0, 44, 7, 0, 0, 68,
+ 88, 66, 67, 172, 27, 205, 113, 176, 254, 27, 44, 22, 107, 179, 112,
+ 127, 38, 148, 161, 1, 0, 0, 0, 44, 7, 0, 0, 6, 0, 0,
+ 0, 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, 0, 0, 228, 3,
+ 0, 0, 136, 6, 0, 0, 188, 6, 0, 0, 65, 111, 110, 57, 84,
+ 1, 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, 252, 0, 0, 0,
+ 88, 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, 0, 0, 0, 84,
+ 0, 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, 0, 0, 1, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0,
+ 1, 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, 15, 160, 0, 0,
+ 128, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31,
+ 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4,
+ 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228,
+ 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0,
+ 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0,
+ 0, 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128,
+ 0, 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, 3, 1, 0, 1,
+ 128, 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, 0, 3, 0, 0,
+ 4, 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, 0, 0, 3, 0,
+ 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 5, 0, 0, 3,
+ 0, 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, 160, 5, 0, 0,
+ 3, 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, 85, 160, 1, 0,
+ 0, 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, 0, 0, 3, 0,
+ 0, 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, 8, 0, 0, 3,
+ 0, 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, 160, 1, 0, 0,
+ 2, 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, 0, 0, 83, 72,
+ 68, 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, 0, 0, 0, 89,
+ 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 89, 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, 0, 4, 0, 0,
+ 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0,
+ 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101,
+ 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3,
+ 194, 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0,
+ 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, 0, 0, 0, 0,
+ 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, 0, 0, 0, 70,
+ 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0,
+ 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0,
+ 0, 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1,
+ 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, 50, 0, 16, 0,
+ 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 128, 32,
+ 0, 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, 0, 10, 50, 0,
+ 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0,
+ 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, 1, 0, 0,
+ 0, 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, 0, 8, 66, 32,
+ 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70,
+ 130, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8,
+ 130, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0,
+ 0, 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, 0, 0, 50, 0,
+ 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0,
+ 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0,
+ 1, 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 82, 68, 69, 70, 156, 2, 0, 0, 2, 0,
+ 0, 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0,
+ 4, 254, 255, 0, 1, 0, 0, 103, 2, 0, 0, 92, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 96, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 99, 98, 48, 0, 99, 98, 50, 0, 92, 0, 0, 0, 4, 0, 0,
+ 0, 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 96, 0, 0, 0, 7, 0, 0, 0, 52, 1, 0, 0, 112,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0,
+ 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, 0, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 26,
+ 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, 48, 0, 0,
+ 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1,
+ 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84,
+ 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111,
+ 108, 111, 114, 0, 171, 171, 220, 1, 0, 0, 0, 0, 0, 0, 44,
+ 0, 0, 0, 2, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0,
+ 4, 2, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0,
+ 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 64, 0,
+ 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 0,
+ 0, 0, 0, 56, 2, 0, 0, 80, 0, 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 64, 2, 0,
+ 0, 88, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2,
+ 0, 0, 0, 0, 0, 0, 84, 2, 0, 0, 92, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0,
+ 92, 2, 0, 0, 96, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 68, 2, 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101,
+ 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99,
+ 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0,
+ 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0, 3, 0, 1,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116,
+ 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100, 105, 117, 115,
+ 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, 0, 77, 105,
+ 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108,
+ 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51,
+ 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1,
+ 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0,
+ 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83,
+ 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12,
+ 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80,
+ 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82,
+ 68, 0, 171, 171, 171, 149, 176, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 200, 7, 0, 0, 68,
+ 88, 66, 67, 238, 212, 160, 43, 129, 11, 44, 225, 62, 162, 102, 35,
+ 9, 220, 80, 177, 1, 0, 0, 0, 200, 7, 0, 0, 6, 0, 0,
+ 0, 56, 0, 0, 0, 196, 1, 0, 0, 56, 4, 0, 0, 180, 4,
+ 0, 0, 36, 7, 0, 0, 148, 7, 0, 0, 65, 111, 110, 57, 132,
+ 1, 0, 0, 132, 1, 0, 0, 0, 2, 255, 255, 76, 1, 0, 0,
+ 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56,
+ 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1,
+ 1, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1,
+ 2, 255, 255, 81, 0, 0, 5, 2, 0, 15, 160, 0, 0, 0, 63,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0,
+ 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0,
+ 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1,
+ 8, 15, 160, 5, 0, 0, 3, 0, 0, 8, 128, 1, 0, 255, 160,
+ 1, 0, 255, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235,
+ 176, 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0,
+ 228, 128, 0, 0, 228, 128, 0, 0, 255, 129, 5, 0, 0, 3, 0,
+ 0, 8, 128, 0, 0, 255, 128, 2, 0, 0, 160, 1, 0, 0, 2,
+ 0, 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1,
+ 128, 0, 0, 228, 128, 0, 0, 228, 160, 6, 0, 0, 2, 0, 0,
+ 1, 128, 0, 0, 0, 128, 5, 0, 0, 3, 0, 0, 1, 128, 0,
+ 0, 0, 128, 0, 0, 255, 128, 1, 0, 0, 2, 0, 0, 2, 128,
+ 2, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228,
+ 176, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0,
+ 228, 128, 0, 8, 228, 160, 1, 0, 0, 2, 0, 0, 8, 128, 1,
+ 0, 255, 160, 4, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128,
+ 0, 0, 170, 161, 0, 0, 255, 129, 5, 0, 0, 3, 2, 0, 7,
+ 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0,
+ 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, 88, 0, 0, 4, 0,
+ 0, 15, 128, 0, 0, 0, 128, 2, 0, 85, 160, 1, 0, 228, 128,
+ 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0,
+ 0, 83, 72, 68, 82, 108, 2, 0, 0, 64, 0, 0, 0, 155, 0,
+ 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 6,
+ 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0,
+ 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0,
+ 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24,
+ 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98,
+ 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3,
+ 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16,
+ 0, 0, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0,
+ 0, 9, 50, 0, 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1,
+ 0, 0, 0, 70, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 5, 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0,
+ 0, 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0,
+ 0, 8, 66, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0,
+ 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0,
+ 15, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 50, 0,
+ 0, 12, 18, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 58, 128, 32, 0,
+ 0, 0, 0, 0, 5, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 56, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 14,
+ 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0,
+ 8, 66, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 29,
+ 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 42, 0, 16,
+ 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242,
+ 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 31, 0, 4, 3, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0,
+ 0, 8, 242, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 62, 0, 0, 1, 21, 0, 0, 1, 56, 0, 0, 7, 114, 0, 16,
+ 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, 70, 2,
+ 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0,
+ 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0,
+ 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0,
+ 7, 242, 32, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0,
+ 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83,
+ 84, 65, 84, 116, 0, 0, 0, 19, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 82, 68, 69, 70, 104, 2, 0, 0, 1, 0, 0, 0,
+ 232, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255,
+ 255, 0, 1, 0, 0, 51, 2, 0, 0, 188, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 216, 0,
+ 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255,
+ 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0,
+ 220, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0,
+ 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0,
+ 0, 0, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112,
+ 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101,
+ 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 50, 0,
+ 171, 171, 171, 225, 0, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0,
+ 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 1, 0,
+ 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 192, 1,
+ 0, 0, 0, 0, 0, 0, 208, 1, 0, 0, 48, 0, 0, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 220, 1, 0, 0, 0, 0, 0, 0,
+ 236, 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0,
+ 0, 244, 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 80, 0,
+ 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 220, 1, 0, 0, 0,
+ 0, 0, 0, 12, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0,
+ 0, 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 16, 2,
+ 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 96, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0,
+ 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115,
+ 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110,
+ 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171,
+ 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0,
+ 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105,
+ 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40,
+ 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32,
+ 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54,
+ 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71,
+ 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0,
+ 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111,
+ 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68,
+ 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0,
+ 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83,
+ 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 217, 183, 0, 0,
+ 0, 0, 0, 0, 83, 97, 109, 112, 108, 101, 67, 111, 110, 105, 99,
+ 71, 114, 97, 100, 105, 101, 110, 116, 0, 12, 7, 0, 0, 68, 88,
+ 66, 67, 139, 251, 38, 36, 124, 246, 203, 168, 214, 67, 77, 25, 142,
+ 114, 138, 15, 1, 0, 0, 0, 12, 7, 0, 0, 6, 0, 0, 0,
+ 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, 0, 0, 228, 3, 0,
+ 0, 104, 6, 0, 0, 156, 6, 0, 0, 65, 111, 110, 57, 84, 1,
+ 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, 252, 0, 0, 0, 88,
+ 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, 0, 0, 0, 84, 0,
+ 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, 0, 0, 1, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 1,
+ 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, 15, 160, 0, 0, 128,
+ 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0,
+ 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0,
+ 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160,
+ 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238,
+ 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0,
+ 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, 0,
+ 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, 3, 1, 0, 1, 128,
+ 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, 0, 3, 0, 0, 4,
+ 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, 0, 0, 3, 0, 0,
+ 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 5, 0, 0, 3, 0,
+ 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, 160, 5, 0, 0, 3,
+ 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, 85, 160, 1, 0, 0,
+ 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, 0, 0, 3, 0, 0,
+ 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, 8, 0, 0, 3, 0,
+ 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, 160, 1, 0, 0, 2,
+ 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, 0, 0, 83, 72, 68,
+ 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, 0, 0, 0, 89, 0,
+ 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 89,
+ 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, 0, 4, 0, 0, 0,
+ 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0,
+ 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0,
+ 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194,
+ 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0,
+ 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, 0, 0, 0, 0, 70,
+ 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, 0, 0, 0, 70, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0,
+ 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, 50, 0, 16, 0, 0,
+ 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 128, 32, 0,
+ 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, 0, 10, 50, 0, 16,
+ 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2, 64,
+ 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0,
+ 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, 1, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, 0, 8, 66, 32, 16,
+ 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130,
+ 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 130,
+ 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0,
+ 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0,
+ 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0,
+ 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 70,
+ 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1,
+ 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 82, 68, 69, 70, 124, 2, 0, 0, 2, 0, 0,
+ 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0, 4,
+ 254, 255, 0, 1, 0, 0, 72, 2, 0, 0, 92, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99,
+ 98, 48, 0, 99, 98, 51, 0, 92, 0, 0, 0, 4, 0, 0, 0,
+ 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 96, 0, 0, 0, 6, 0, 0, 0, 52, 1, 0, 0, 80, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, 0, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 26, 1,
+ 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, 48, 0, 0, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0,
+ 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84,
+ 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101,
+ 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108,
+ 111, 114, 0, 171, 171, 196, 1, 0, 0, 0, 0, 0, 0, 44, 0,
+ 0, 0, 2, 0, 0, 0, 224, 1, 0, 0, 0, 0, 0, 0, 240,
+ 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 56, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 23, 2, 0, 0, 64, 0, 0, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 48, 2, 0, 0,
+ 68, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0,
+ 0, 0, 0, 0, 0, 61, 2, 0, 0, 72, 0, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 68,
+ 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101,
+ 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, 0, 171, 3, 0, 3,
+ 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105,
+ 109, 101, 110, 115, 105, 111, 110, 115, 95, 99, 98, 51, 0, 171, 1,
+ 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 99, 101, 110, 116, 101, 114, 0, 97, 110, 103, 108, 101, 0, 171, 171,
+ 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 115, 116, 97, 114, 116, 95, 111, 102, 102, 115, 101, 116, 0,
+ 101, 110, 100, 95, 111, 102, 102, 115, 101, 116, 0, 77, 105, 99, 114,
+ 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32,
+ 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114,
+ 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52,
+ 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0,
+ 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79,
+ 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104,
+ 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105,
+ 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171,
+ 171, 171, 193, 191, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 76, 12, 0, 0, 68, 88, 66, 67,
+ 237, 135, 171, 29, 251, 164, 113, 72, 131, 168, 215, 155, 110, 76, 248,
+ 73, 1, 0, 0, 0, 76, 12, 0, 0, 6, 0, 0, 0, 56, 0,
+ 0, 0, 144, 3, 0, 0, 228, 8, 0, 0, 96, 9, 0, 0, 168,
+ 11, 0, 0, 24, 12, 0, 0, 65, 111, 110, 57, 80, 3, 0, 0,
+ 80, 3, 0, 0, 0, 2, 255, 255, 24, 3, 0, 0, 56, 0, 0,
+ 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0,
+ 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255,
+ 81, 0, 0, 5, 2, 0, 15, 160, 95, 174, 170, 60, 54, 90, 174,
+ 189, 226, 118, 56, 62, 4, 29, 169, 190, 81, 0, 0, 5, 3, 0,
+ 15, 160, 56, 247, 127, 63, 0, 0, 0, 0, 0, 0, 128, 63, 219,
+ 15, 73, 64, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, 192,
+ 219, 15, 201, 63, 216, 15, 201, 63, 134, 249, 34, 62, 81, 0, 0,
+ 5, 5, 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, 0, 128, 0,
+ 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160,
+ 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 2, 0, 0,
+ 3, 0, 0, 3, 128, 0, 0, 235, 176, 0, 0, 238, 161, 35, 0,
+ 0, 2, 0, 0, 12, 128, 0, 0, 68, 128, 2, 0, 0, 3, 1,
+ 0, 3, 128, 0, 0, 238, 129, 0, 0, 235, 128, 88, 0, 0, 4,
+ 0, 0, 12, 128, 1, 0, 0, 128, 0, 0, 228, 128, 0, 0, 180,
+ 128, 88, 0, 0, 4, 1, 0, 1, 128, 1, 0, 85, 128, 3, 0,
+ 85, 160, 3, 0, 170, 160, 6, 0, 0, 2, 0, 0, 8, 128, 0,
+ 0, 255, 128, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128,
+ 0, 0, 170, 128, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 170,
+ 128, 0, 0, 170, 128, 4, 0, 0, 4, 1, 0, 2, 128, 0, 0,
+ 255, 128, 2, 0, 0, 160, 2, 0, 85, 160, 4, 0, 0, 4, 1,
+ 0, 2, 128, 0, 0, 255, 128, 1, 0, 85, 128, 2, 0, 170, 160,
+ 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, 1, 0, 85,
+ 128, 2, 0, 255, 160, 4, 0, 0, 4, 0, 0, 8, 128, 0, 0,
+ 255, 128, 1, 0, 85, 128, 3, 0, 0, 160, 5, 0, 0, 3, 0,
+ 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, 128, 4, 0, 0, 4,
+ 0, 0, 8, 128, 0, 0, 170, 128, 4, 0, 0, 160, 4, 0, 85,
+ 160, 4, 0, 0, 4, 0, 0, 4, 128, 0, 0, 255, 128, 1, 0,
+ 0, 128, 0, 0, 170, 128, 88, 0, 0, 4, 0, 0, 8, 128, 0,
+ 0, 0, 128, 3, 0, 85, 161, 3, 0, 255, 161, 2, 0, 0, 3,
+ 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, 128, 2, 0, 0,
+ 3, 0, 0, 8, 128, 0, 0, 170, 128, 0, 0, 170, 128, 2, 0,
+ 0, 3, 1, 0, 1, 128, 0, 0, 0, 129, 0, 0, 85, 128, 88,
+ 0, 0, 4, 0, 0, 3, 128, 1, 0, 0, 128, 0, 0, 228, 128,
+ 0, 0, 225, 128, 88, 0, 0, 4, 0, 0, 2, 128, 0, 0, 85,
+ 128, 3, 0, 170, 160, 3, 0, 85, 160, 88, 0, 0, 4, 0, 0,
+ 1, 128, 0, 0, 0, 128, 3, 0, 85, 160, 0, 0, 85, 128, 4,
+ 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 129,
+ 0, 0, 170, 128, 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0,
+ 128, 1, 0, 0, 161, 2, 0, 0, 3, 0, 0, 1, 128, 0, 0,
+ 0, 128, 4, 0, 170, 160, 5, 0, 0, 3, 0, 0, 2, 128, 0,
+ 0, 0, 128, 4, 0, 255, 160, 35, 0, 0, 2, 0, 0, 2, 128,
+ 0, 0, 85, 128, 19, 0, 0, 2, 0, 0, 2, 128, 0, 0, 85,
+ 128, 88, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0,
+ 85, 128, 0, 0, 85, 129, 2, 0, 0, 3, 0, 0, 1, 128, 0,
+ 0, 0, 128, 1, 0, 85, 161, 2, 0, 0, 3, 0, 0, 2, 128,
+ 1, 0, 85, 161, 1, 0, 170, 160, 6, 0, 0, 2, 0, 0, 2,
+ 128, 0, 0, 85, 128, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0,
+ 85, 128, 0, 0, 0, 128, 1, 0, 0, 2, 0, 0, 2, 128, 5,
+ 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176,
+ 1, 8, 228, 160, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228,
+ 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0, 7, 128, 0, 0,
+ 255, 128, 0, 0, 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 1,
+ 0, 255, 128, 0, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128,
+ 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 76, 5, 0,
+ 0, 64, 0, 0, 0, 83, 1, 0, 0, 89, 0, 0, 4, 70, 142,
+ 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 90, 0, 0, 3, 0,
+ 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0,
+ 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0,
+ 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0,
+ 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1,
+ 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, 1, 0, 0, 0,
+ 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0,
+ 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, 0, 16, 0, 0, 0,
+ 0, 0, 182, 31, 16, 0, 1, 0, 0, 0, 182, 143, 32, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 52, 0, 0, 9,
+ 66, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 129, 0, 0,
+ 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0, 0,
+ 0, 0, 14, 0, 0, 10, 66, 0, 16, 0, 0, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
+ 0, 0, 128, 63, 42, 0, 16, 0, 0, 0, 0, 0, 51, 0, 0,
+ 9, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 129, 0,
+ 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0,
+ 0, 0, 0, 56, 0, 0, 7, 66, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 42, 0,
+ 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 50,
+ 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0,
+ 0, 0, 0, 0, 1, 64, 0, 0, 95, 174, 170, 60, 1, 64, 0,
+ 0, 54, 90, 174, 189, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0,
+ 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1,
+ 0, 0, 0, 1, 64, 0, 0, 226, 118, 56, 62, 50, 0, 0, 9,
+ 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 4, 29,
+ 169, 190, 50, 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 58,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0,
+ 1, 64, 0, 0, 56, 247, 127, 63, 56, 0, 0, 7, 18, 0, 16,
+ 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, 0,
+ 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 1,
+ 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 0, 192, 1, 64, 0, 0, 219, 15, 201, 63, 49, 0, 0,
+ 9, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0,
+ 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, 0, 1, 0, 0, 0,
+ 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0,
+ 0, 50, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 42, 0,
+ 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 0, 1, 0, 0, 0, 49, 0, 0, 8, 130, 0, 16, 0,
+ 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16,
+ 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, 130, 0,
+ 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 1,
+ 64, 0, 0, 219, 15, 73, 192, 0, 0, 0, 7, 66, 0, 16, 0,
+ 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16,
+ 0, 0, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0,
+ 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 29, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0,
+ 0, 0, 0, 49, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0,
+ 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0,
+ 0, 0, 0, 55, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 216, 15,
+ 201, 63, 56, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 134, 249, 34, 62,
+ 29, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0,
+ 0, 0, 26, 0, 0, 6, 18, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 10,
+ 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ 10, 34, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 128, 65, 0,
+ 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 42, 128, 32, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 14, 0, 0, 7, 18, 0, 16, 0,
+ 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16,
+ 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242,
+ 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 56, 0, 0, 7, 114, 0, 16, 0, 0, 0, 0, 0, 246, 15,
+ 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 69,
+ 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0,
+ 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16,
+ 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, 0, 0,
+ 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 1,
+ 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0,
+ 39, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 30, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70,
+ 64, 2, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0, 5, 0, 0,
+ 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 12, 2,
+ 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0, 0, 0, 5,
+ 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0,
+ 1, 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0,
+ 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 1, 0,
+ 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, 83, 97,
+ 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112,
+ 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98,
+ 51, 0, 171, 219, 0, 0, 0, 6, 0, 0, 0, 248, 0, 0, 0,
+ 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 1, 0,
+ 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 164, 1,
+ 0, 0, 0, 0, 0, 0, 180, 1, 0, 0, 48, 0, 0, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 196, 1, 0, 0, 0, 0, 0, 0,
+ 212, 1, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0,
+ 0, 196, 1, 0, 0, 0, 0, 0, 0, 219, 1, 0, 0, 64, 0,
+ 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 228, 1, 0, 0, 0,
+ 0, 0, 0, 244, 1, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 228, 1, 0, 0, 0, 0, 0, 0, 1, 2, 0,
+ 0, 72, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 228, 1,
+ 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97,
+ 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99,
+ 98, 51, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115,
+ 95, 99, 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97,
+ 110, 103, 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95,
+ 111, 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115,
+ 101, 116, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82,
+ 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67,
+ 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48,
+ 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104,
+ 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105,
+ 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171,
+ 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8,
+ 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95,
+ 84, 97, 114, 103, 101, 116, 0, 171, 171, 229, 198, 0, 0, 0, 0,
+ 0, 0, 12, 7, 0, 0, 68, 88, 66, 67, 139, 251, 38, 36, 124,
+ 246, 203, 168, 214, 67, 77, 25, 142, 114, 138, 15, 1, 0, 0, 0,
+ 12, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0,
+ 0, 104, 3, 0, 0, 228, 3, 0, 0, 104, 6, 0, 0, 156, 6,
+ 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0,
+ 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0,
+ 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84,
+ 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2,
+ 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0,
+ 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0,
+ 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0,
+ 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144,
+ 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3,
+ 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0,
+ 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5,
+ 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160,
+ 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85,
+ 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0,
+ 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0,
+ 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128,
+ 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0,
+ 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0,
+ 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3,
+ 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128,
+ 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36,
+ 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0,
+ 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0,
+ 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16,
+ 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1,
+ 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0,
+ 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16,
+ 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50,
+ 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0,
+ 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32,
+ 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0,
+ 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0,
+ 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56,
+ 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0,
+ 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0,
+ 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0,
+ 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0,
+ 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5,
+ 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128,
+ 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2,
+ 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0,
+ 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0,
+ 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0,
+ 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0,
+ 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69,
+ 70, 124, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0,
+ 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 72,
+ 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 51, 0,
+ 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 6, 0,
+ 0, 0, 52, 1, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0,
+ 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0,
+ 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16,
+ 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101,
+ 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100,
+ 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115,
+ 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 196, 1,
+ 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 224,
+ 1, 0, 0, 0, 0, 0, 0, 240, 1, 0, 0, 48, 0, 0, 0,
+ 8, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 16, 2, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 23, 2, 0, 0, 64,
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0,
+ 0, 0, 0, 0, 48, 2, 0, 0, 68, 0, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 61, 2,
+ 0, 0, 72, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32,
+ 2, 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112,
+ 97, 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95,
+ 99, 98, 51, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110,
+ 115, 95, 99, 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0,
+ 97, 110, 103, 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116,
+ 95, 111, 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102,
+ 115, 101, 116, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40,
+ 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32,
+ 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54,
+ 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78,
+ 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0,
+ 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0,
+ 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0,
+ 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69,
+ 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 61, 211, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 80, 12, 0, 0, 68, 88, 66, 67, 247, 165, 11, 199, 50, 224, 108,
+ 119, 183, 179, 87, 201, 53, 213, 28, 250, 1, 0, 0, 0, 80, 12,
+ 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 144, 3, 0, 0, 228,
+ 8, 0, 0, 96, 9, 0, 0, 172, 11, 0, 0, 28, 12, 0, 0,
+ 65, 111, 110, 57, 80, 3, 0, 0, 80, 3, 0, 0, 0, 2, 255,
+ 255, 24, 3, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0,
+ 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 3, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 2, 0, 15,
+ 160, 95, 174, 170, 60, 54, 90, 174, 189, 226, 118, 56, 62, 4, 29,
+ 169, 190, 81, 0, 0, 5, 3, 0, 15, 160, 56, 247, 127, 63, 0,
+ 0, 0, 0, 0, 0, 128, 63, 219, 15, 73, 64, 81, 0, 0, 5,
+ 4, 0, 15, 160, 0, 0, 0, 192, 219, 15, 201, 63, 216, 15, 201,
+ 63, 134, 249, 34, 62, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0,
+ 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31,
+ 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2,
+ 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0,
+ 144, 1, 8, 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0,
+ 235, 176, 0, 0, 238, 161, 35, 0, 0, 2, 0, 0, 12, 128, 0,
+ 0, 68, 128, 2, 0, 0, 3, 1, 0, 3, 128, 0, 0, 238, 129,
+ 0, 0, 235, 128, 88, 0, 0, 4, 0, 0, 12, 128, 1, 0, 0,
+ 128, 0, 0, 228, 128, 0, 0, 180, 128, 88, 0, 0, 4, 1, 0,
+ 1, 128, 1, 0, 85, 128, 3, 0, 85, 160, 3, 0, 170, 160, 6,
+ 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 5, 0, 0, 3,
+ 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, 128, 5, 0, 0,
+ 3, 0, 0, 8, 128, 0, 0, 170, 128, 0, 0, 170, 128, 4, 0,
+ 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, 2, 0, 0, 160, 2,
+ 0, 85, 160, 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, 255, 128,
+ 1, 0, 85, 128, 2, 0, 170, 160, 4, 0, 0, 4, 1, 0, 2,
+ 128, 0, 0, 255, 128, 1, 0, 85, 128, 2, 0, 255, 160, 4, 0,
+ 0, 4, 0, 0, 8, 128, 0, 0, 255, 128, 1, 0, 85, 128, 3,
+ 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128,
+ 0, 0, 170, 128, 4, 0, 0, 4, 0, 0, 8, 128, 0, 0, 170,
+ 128, 4, 0, 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, 0, 0,
+ 4, 128, 0, 0, 255, 128, 1, 0, 0, 128, 0, 0, 170, 128, 88,
+ 0, 0, 4, 0, 0, 8, 128, 0, 0, 0, 128, 3, 0, 85, 161,
+ 3, 0, 255, 161, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255,
+ 128, 0, 0, 170, 128, 2, 0, 0, 3, 0, 0, 8, 128, 0, 0,
+ 170, 128, 0, 0, 170, 128, 2, 0, 0, 3, 1, 0, 1, 128, 0,
+ 0, 0, 129, 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, 3, 128,
+ 1, 0, 0, 128, 0, 0, 228, 128, 0, 0, 225, 128, 88, 0, 0,
+ 4, 0, 0, 2, 128, 0, 0, 85, 128, 3, 0, 170, 160, 3, 0,
+ 85, 160, 88, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 3,
+ 0, 85, 160, 0, 0, 85, 128, 4, 0, 0, 4, 0, 0, 1, 128,
+ 0, 0, 0, 128, 0, 0, 255, 129, 0, 0, 170, 128, 2, 0, 0,
+ 3, 0, 0, 1, 128, 0, 0, 0, 128, 1, 0, 0, 161, 2, 0,
+ 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 4, 0, 170, 160, 5,
+ 0, 0, 3, 0, 0, 2, 128, 0, 0, 0, 128, 4, 0, 255, 160,
+ 35, 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 19, 0, 0,
+ 2, 0, 0, 2, 128, 0, 0, 85, 128, 88, 0, 0, 4, 0, 0,
+ 1, 128, 0, 0, 0, 128, 0, 0, 85, 128, 0, 0, 85, 129, 2,
+ 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 1, 0, 85, 161,
+ 2, 0, 0, 3, 0, 0, 2, 128, 1, 0, 85, 161, 1, 0, 170,
+ 160, 6, 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0,
+ 0, 3, 0, 0, 1, 128, 0, 0, 85, 128, 0, 0, 0, 128, 1,
+ 0, 0, 2, 0, 0, 2, 128, 5, 0, 0, 160, 66, 0, 0, 3,
+ 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0,
+ 3, 0, 0, 15, 128, 0, 0, 228, 128, 0, 8, 228, 160, 5, 0,
+ 0, 3, 0, 0, 7, 128, 0, 0, 255, 128, 0, 0, 228, 128, 5,
+ 0, 0, 3, 0, 0, 15, 128, 1, 0, 255, 128, 0, 0, 228, 128,
+ 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0,
+ 0, 83, 72, 68, 82, 76, 5, 0, 0, 64, 0, 0, 0, 83, 1,
+ 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0,
+ 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0,
+ 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24,
+ 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98,
+ 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3,
+ 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16,
+ 0, 0, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0,
+ 0, 9, 50, 0, 16, 0, 0, 0, 0, 0, 182, 31, 16, 0, 1,
+ 0, 0, 0, 182, 143, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 52, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0,
+ 0, 26, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0,
+ 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 10, 66,
+ 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63,
+ 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 42, 0, 16,
+ 0, 0, 0, 0, 0, 51, 0, 0, 9, 130, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10,
+ 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 7,
+ 66, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0,
+ 0, 58, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 130, 0,
+ 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42,
+ 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0,
+ 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0,
+ 0, 95, 174, 170, 60, 1, 64, 0, 0, 54, 90, 174, 189, 50, 0,
+ 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0,
+ 226, 118, 56, 62, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0,
+ 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0,
+ 0, 0, 1, 64, 0, 0, 4, 29, 169, 190, 50, 0, 0, 9, 130,
+ 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 56, 247, 127,
+ 63, 56, 0, 0, 7, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0,
+ 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 50,
+ 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0,
+ 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 192, 1, 64, 0,
+ 0, 219, 15, 201, 63, 49, 0, 0, 9, 34, 0, 16, 0, 1, 0,
+ 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10,
+ 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7,
+ 18, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0,
+ 0, 10, 0, 16, 0, 1, 0, 0, 0, 50, 0, 0, 9, 66, 0,
+ 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0,
+ 49, 0, 0, 8, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16,
+ 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 219, 15, 73, 192,
+ 0, 0, 0, 7, 66, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16,
+ 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 51, 0,
+ 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7,
+ 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 29, 0, 0, 8, 18, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 8,
+ 34, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 58, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 55, 0, 0, 10,
+ 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 42, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 128, 32, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 216, 15, 201, 63, 56, 0, 0, 7, 18,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 1, 64, 0, 0, 134, 249, 34, 62, 29, 0, 0, 8, 34, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 6, 18,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0,
+ 0, 0, 0, 0, 55, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0,
+ 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 26, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0,
+ 0, 4, 0, 0, 0, 0, 0, 0, 10, 34, 0, 16, 0, 0, 0,
+ 0, 0, 26, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0,
+ 14, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 54, 0,
+ 0, 5, 34, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0,
+ 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0,
+ 70, 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0,
+ 0, 0, 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 114, 0,
+ 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70,
+ 2, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0,
+ 1, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16,
+ 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0,
+ 0, 7, 242, 32, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0,
+ 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1,
+ 83, 84, 65, 84, 116, 0, 0, 0, 39, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 82, 68, 69, 70, 68, 2, 0, 0, 1, 0, 0,
+ 0, 228, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4,
+ 255, 255, 0, 1, 0, 0, 16, 2, 0, 0, 188, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 214,
+ 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0,
+ 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0,
+ 0, 218, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0,
+ 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12,
+ 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 115, 87, 114, 97, 112, 83, 97, 109, 112, 108,
+ 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114,
+ 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 51, 0, 171,
+ 223, 0, 0, 0, 6, 0, 0, 0, 252, 0, 0, 0, 80, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 1, 0, 0, 0, 0,
+ 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 168, 1, 0, 0, 0,
+ 0, 0, 0, 184, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 200, 1, 0, 0, 0, 0, 0, 0, 216, 1, 0,
+ 0, 56, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 200, 1,
+ 0, 0, 0, 0, 0, 0, 223, 1, 0, 0, 64, 0, 0, 0, 4,
+ 0, 0, 0, 2, 0, 0, 0, 232, 1, 0, 0, 0, 0, 0, 0,
+ 248, 1, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0,
+ 0, 232, 1, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 72, 0,
+ 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 232, 1, 0, 0, 0,
+ 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84,
+ 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, 0,
+ 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 95, 99, 98,
+ 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, 110, 103, 108,
+ 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, 102, 102,
+ 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, 116, 0,
+ 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72,
+ 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112,
+ 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49,
+ 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0,
+ 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0,
+ 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0,
+ 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111,
+ 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79,
+ 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114,
+ 103, 101, 116, 0, 171, 171, 97, 218, 0, 0, 0, 0, 0, 0, 12,
+ 7, 0, 0, 68, 88, 66, 67, 139, 251, 38, 36, 124, 246, 203, 168,
+ 214, 67, 77, 25, 142, 114, 138, 15, 1, 0, 0, 0, 12, 7, 0,
+ 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, 0, 104, 3,
+ 0, 0, 228, 3, 0, 0, 104, 6, 0, 0, 156, 6, 0, 0, 65,
+ 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, 2, 254, 255,
+ 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, 0, 0, 84,
+ 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, 0, 0, 0,
+ 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1,
+ 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0,
+ 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 6, 0,
+ 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0,
+ 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144,
+ 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238,
+ 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0,
+ 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0,
+ 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, 0, 0, 3,
+ 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, 5, 0, 0,
+ 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, 160, 2, 0,
+ 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, 0, 160, 2,
+ 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160,
+ 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, 5, 0, 85,
+ 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, 128, 6, 0,
+ 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, 0, 160, 8,
+ 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, 0, 228, 160,
+ 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, 4, 0, 228,
+ 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, 160, 255, 255,
+ 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, 1, 0, 115,
+ 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 1, 0, 0,
+ 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0,
+ 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0,
+ 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 104, 0, 0,
+ 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 0, 16, 0,
+ 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, 16, 0, 0,
+ 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 7,
+ 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, 8, 34, 0,
+ 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0,
+ 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 8,
+ 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0,
+ 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, 0, 56, 0,
+ 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0,
+ 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63,
+ 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16,
+ 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 16, 0,
+ 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1,
+ 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16,
+ 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 1, 0,
+ 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70,
+ 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 12, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 124, 2,
+ 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, 0, 0, 28,
+ 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 72, 2, 0, 0,
+ 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 51, 0, 92, 0, 0,
+ 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 6, 0, 0, 0, 52,
+ 1, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, 16, 0,
+ 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0,
+ 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0,
+ 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0,
+ 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77,
+ 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101,
+ 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 196, 1, 0, 0, 0,
+ 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 224, 1, 0, 0,
+ 0, 0, 0, 0, 240, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0,
+ 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 16, 2,
+ 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 23, 2, 0, 0, 64, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0,
+ 0, 48, 2, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, 0, 0,
+ 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 61, 2, 0, 0, 72,
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0,
+ 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101,
+ 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51,
+ 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 95, 99,
+ 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, 110, 103,
+ 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, 102,
+ 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, 116,
+ 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32,
+ 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46,
+ 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0,
+ 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7,
+ 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171,
+ 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0,
+ 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86,
+ 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79,
+ 79, 82, 68, 0, 171, 171, 171, 189, 230, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 84, 12, 0,
+ 0, 68, 88, 66, 67, 176, 243, 97, 211, 20, 121, 26, 93, 252, 11,
+ 132, 198, 181, 186, 97, 15, 1, 0, 0, 0, 84, 12, 0, 0, 6,
+ 0, 0, 0, 56, 0, 0, 0, 144, 3, 0, 0, 228, 8, 0, 0,
+ 96, 9, 0, 0, 176, 11, 0, 0, 32, 12, 0, 0, 65, 111, 110,
+ 57, 80, 3, 0, 0, 80, 3, 0, 0, 0, 2, 255, 255, 24, 3,
+ 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0,
+ 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0,
+ 1, 1, 1, 0, 0, 0, 3, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 1, 2, 255, 255, 81, 0, 0, 5, 2, 0, 15, 160, 95, 174,
+ 170, 60, 54, 90, 174, 189, 226, 118, 56, 62, 4, 29, 169, 190, 81,
+ 0, 0, 5, 3, 0, 15, 160, 56, 247, 127, 63, 0, 0, 0, 0,
+ 0, 0, 128, 63, 219, 15, 73, 64, 81, 0, 0, 5, 4, 0, 15,
+ 160, 0, 0, 0, 192, 219, 15, 201, 63, 216, 15, 201, 63, 134, 249,
+ 34, 62, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, 0, 63, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2,
+ 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0,
+ 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8,
+ 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, 0,
+ 0, 238, 161, 35, 0, 0, 2, 0, 0, 12, 128, 0, 0, 68, 128,
+ 2, 0, 0, 3, 1, 0, 3, 128, 0, 0, 238, 129, 0, 0, 235,
+ 128, 88, 0, 0, 4, 0, 0, 12, 128, 1, 0, 0, 128, 0, 0,
+ 228, 128, 0, 0, 180, 128, 88, 0, 0, 4, 1, 0, 1, 128, 1,
+ 0, 85, 128, 3, 0, 85, 160, 3, 0, 170, 160, 6, 0, 0, 2,
+ 0, 0, 8, 128, 0, 0, 255, 128, 5, 0, 0, 3, 0, 0, 4,
+ 128, 0, 0, 255, 128, 0, 0, 170, 128, 5, 0, 0, 3, 0, 0,
+ 8, 128, 0, 0, 170, 128, 0, 0, 170, 128, 4, 0, 0, 4, 1,
+ 0, 2, 128, 0, 0, 255, 128, 2, 0, 0, 160, 2, 0, 85, 160,
+ 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, 1, 0, 85,
+ 128, 2, 0, 170, 160, 4, 0, 0, 4, 1, 0, 2, 128, 0, 0,
+ 255, 128, 1, 0, 85, 128, 2, 0, 255, 160, 4, 0, 0, 4, 0,
+ 0, 8, 128, 0, 0, 255, 128, 1, 0, 85, 128, 3, 0, 0, 160,
+ 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, 170,
+ 128, 4, 0, 0, 4, 0, 0, 8, 128, 0, 0, 170, 128, 4, 0,
+ 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, 0, 0, 4, 128, 0,
+ 0, 255, 128, 1, 0, 0, 128, 0, 0, 170, 128, 88, 0, 0, 4,
+ 0, 0, 8, 128, 0, 0, 0, 128, 3, 0, 85, 161, 3, 0, 255,
+ 161, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128, 0, 0,
+ 170, 128, 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, 170, 128, 0,
+ 0, 170, 128, 2, 0, 0, 3, 1, 0, 1, 128, 0, 0, 0, 129,
+ 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, 3, 128, 1, 0, 0,
+ 128, 0, 0, 228, 128, 0, 0, 225, 128, 88, 0, 0, 4, 0, 0,
+ 2, 128, 0, 0, 85, 128, 3, 0, 170, 160, 3, 0, 85, 160, 88,
+ 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 3, 0, 85, 160,
+ 0, 0, 85, 128, 4, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0,
+ 128, 0, 0, 255, 129, 0, 0, 170, 128, 2, 0, 0, 3, 0, 0,
+ 1, 128, 0, 0, 0, 128, 1, 0, 0, 161, 2, 0, 0, 3, 0,
+ 0, 1, 128, 0, 0, 0, 128, 4, 0, 170, 160, 5, 0, 0, 3,
+ 0, 0, 2, 128, 0, 0, 0, 128, 4, 0, 255, 160, 35, 0, 0,
+ 2, 0, 0, 2, 128, 0, 0, 85, 128, 19, 0, 0, 2, 0, 0,
+ 2, 128, 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, 1, 128, 0,
+ 0, 0, 128, 0, 0, 85, 128, 0, 0, 85, 129, 2, 0, 0, 3,
+ 0, 0, 1, 128, 0, 0, 0, 128, 1, 0, 85, 161, 2, 0, 0,
+ 3, 0, 0, 2, 128, 1, 0, 85, 161, 1, 0, 170, 160, 6, 0,
+ 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 0, 3, 0,
+ 0, 1, 128, 0, 0, 85, 128, 0, 0, 0, 128, 1, 0, 0, 2,
+ 0, 0, 2, 128, 5, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15,
+ 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 0, 0,
+ 15, 128, 0, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 0,
+ 0, 7, 128, 0, 0, 255, 128, 0, 0, 228, 128, 5, 0, 0, 3,
+ 0, 0, 15, 128, 1, 0, 255, 128, 0, 0, 228, 128, 1, 0, 0,
+ 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72,
+ 68, 82, 76, 5, 0, 0, 64, 0, 0, 0, 83, 1, 0, 0, 89,
+ 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0,
+ 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0,
+ 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112,
+ 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0,
+ 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3,
+ 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16,
+ 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0,
+ 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 9, 50,
+ 0, 16, 0, 0, 0, 0, 0, 182, 31, 16, 0, 1, 0, 0, 0,
+ 182, 143, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 52, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 26, 0,
+ 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129,
+ 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 10, 66, 0, 16, 0,
+ 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128,
+ 63, 0, 0, 128, 63, 0, 0, 128, 63, 42, 0, 16, 0, 0, 0,
+ 0, 0, 51, 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 26,
+ 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128,
+ 129, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 7, 66, 0, 16,
+ 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, 0,
+ 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 130, 0, 16, 0, 0,
+ 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0,
+ 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0,
+ 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 95, 174,
+ 170, 60, 1, 64, 0, 0, 54, 90, 174, 189, 50, 0, 0, 9, 18,
+ 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 226, 118, 56,
+ 62, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1,
+ 64, 0, 0, 4, 29, 169, 190, 50, 0, 0, 9, 130, 0, 16, 0,
+ 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 1, 0, 0, 0, 1, 64, 0, 0, 56, 247, 127, 63, 56, 0,
+ 0, 7, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9,
+ 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0,
+ 0, 1, 64, 0, 0, 0, 0, 0, 192, 1, 64, 0, 0, 219, 15,
+ 201, 63, 49, 0, 0, 9, 34, 0, 16, 0, 1, 0, 0, 0, 26,
+ 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128,
+ 129, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16,
+ 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0,
+ 16, 0, 1, 0, 0, 0, 50, 0, 0, 9, 66, 0, 16, 0, 0,
+ 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0,
+ 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 49, 0, 0,
+ 8, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0,
+ 0, 0, 0, 0, 1, 64, 0, 0, 219, 15, 73, 192, 0, 0, 0,
+ 7, 66, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0,
+ 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 51, 0, 0, 7, 130,
+ 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, 18, 0, 16,
+ 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 29, 0, 0, 8, 18, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 8, 34, 0, 16,
+ 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 58, 0,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, 18,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 26, 0, 16, 0, 0, 0, 0, 0, 55, 0, 0, 10, 18, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 0,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 10, 128, 32, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 7, 18, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1,
+ 64, 0, 0, 216, 15, 201, 63, 56, 0, 0, 7, 18, 0, 16, 0,
+ 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0,
+ 0, 134, 249, 34, 62, 29, 0, 0, 8, 34, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 6, 18, 0, 16, 0,
+ 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0,
+ 0, 55, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
+ 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 26, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 10, 34, 0, 16, 0, 0, 0, 0, 0, 26,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0,
+ 42, 128, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 14, 0, 0,
+ 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 34,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63,
+ 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96,
+ 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 0,
+ 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0,
+ 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0,
+ 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0,
+ 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242,
+ 32, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0,
+ 246, 15, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65,
+ 84, 116, 0, 0, 0, 39, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 82, 68, 69, 70, 72, 2, 0, 0, 1, 0, 0, 0, 232, 0,
+ 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0,
+ 1, 0, 0, 20, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0,
+ 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255,
+ 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0,
+ 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255,
+ 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0,
+ 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, 108, 101,
+ 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, 0,
+ 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 51, 0, 171, 171,
+ 171, 225, 0, 0, 0, 6, 0, 0, 0, 0, 1, 0, 0, 80, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 144, 1, 0, 0, 0,
+ 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 172, 1, 0, 0,
+ 0, 0, 0, 0, 188, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0,
+ 0, 0, 0, 0, 0, 204, 1, 0, 0, 0, 0, 0, 0, 220, 1,
+ 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 204,
+ 1, 0, 0, 0, 0, 0, 0, 227, 1, 0, 0, 64, 0, 0, 0,
+ 4, 0, 0, 0, 2, 0, 0, 0, 236, 1, 0, 0, 0, 0, 0,
+ 0, 252, 1, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, 2, 0,
+ 0, 0, 236, 1, 0, 0, 0, 0, 0, 0, 9, 2, 0, 0, 72,
+ 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 236, 1, 0, 0,
+ 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101,
+ 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51,
+ 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 95, 99,
+ 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, 110, 103,
+ 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, 102,
+ 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, 116,
+ 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32,
+ 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46,
+ 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0,
+ 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15,
+ 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0,
+ 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105,
+ 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171,
+ 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0,
+ 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97,
+ 114, 103, 101, 116, 0, 171, 171, 225, 237, 0, 0, 0, 0, 0, 0,
+ 83, 97, 109, 112, 108, 101, 77, 97, 115, 107, 101, 100, 84, 101, 120,
+ 116, 117, 114, 101, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85,
+ 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1,
+ 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0,
+ 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0,
+ 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0,
+ 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1,
+ 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0,
+ 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0,
+ 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0,
+ 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144,
+ 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238,
+ 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0,
+ 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0,
+ 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160,
+ 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228,
+ 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255,
+ 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61,
+ 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0,
+ 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101,
+ 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11,
+ 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0,
+ 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194,
+ 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0,
+ 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0,
+ 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70,
+ 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11,
+ 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0,
+ 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132,
+ 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83,
+ 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0,
+ 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254,
+ 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0,
+ 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0,
+ 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0,
+ 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0,
+ 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0,
+ 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16,
+ 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0,
+ 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3,
+ 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101,
+ 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120,
+ 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111,
+ 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41,
+ 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111,
+ 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48,
+ 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0,
+ 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3,
+ 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79,
+ 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0,
+ 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3,
+ 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95,
+ 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79,
+ 82, 68, 0, 171, 171, 171, 85, 250, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 212, 3, 0, 0,
+ 68, 88, 66, 67, 98, 136, 224, 212, 103, 235, 205, 77, 125, 241, 101,
+ 150, 199, 56, 208, 85, 1, 0, 0, 0, 212, 3, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0, 224, 0, 0, 0, 188, 1, 0, 0, 56,
+ 2, 0, 0, 48, 3, 0, 0, 160, 3, 0, 0, 65, 111, 110, 57,
+ 160, 0, 0, 0, 160, 0, 0, 0, 0, 2, 255, 255, 116, 0, 0,
+ 0, 44, 0, 0, 0, 0, 0, 44, 0, 0, 0, 44, 0, 0, 0,
+ 44, 0, 2, 0, 36, 0, 0, 0, 44, 0, 0, 0, 0, 0, 1,
+ 1, 1, 0, 1, 2, 255, 255, 31, 0, 0, 2, 0, 0, 0, 128,
+ 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15,
+ 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 1, 0,
+ 0, 2, 0, 0, 3, 128, 0, 0, 235, 176, 66, 0, 0, 3, 1,
+ 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 66, 0, 0, 3,
+ 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, 228, 160, 5, 0, 0,
+ 3, 0, 0, 15, 128, 0, 0, 255, 128, 1, 0, 228, 128, 1, 0,
+ 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83,
+ 72, 68, 82, 212, 0, 0, 0, 64, 0, 0, 0, 53, 0, 0, 0,
+ 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0,
+ 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112,
+ 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0,
+ 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3,
+ 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16,
+ 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0,
+ 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 69, 0, 0, 9, 242,
+ 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 26,
+ 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0,
+ 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0,
+ 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, 16,
+ 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0,
+ 187, 0, 0, 0, 156, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 165, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 2, 0, 0,
+ 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0,
+ 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 182, 0, 0, 0, 2,
+ 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255,
+ 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 115, 83, 97,
+ 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112,
+ 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 77, 105,
+ 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108,
+ 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51,
+ 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3,
+ 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0,
+ 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110,
+ 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83,
+ 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103,
+ 101, 116, 0, 171, 171, 177, 254, 0, 0, 0, 0, 0, 0, 83, 97,
+ 109, 112, 108, 101, 84, 101, 120, 116, 117, 114, 101, 87, 105, 116, 104,
+ 83, 104, 97, 100, 111, 119, 0, 4, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0,
+ 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0,
+ 0, 0, 3, 0, 0, 0, 255, 255, 255, 255, 68, 4, 0, 0, 68,
+ 88, 66, 67, 77, 85, 167, 240, 56, 56, 155, 78, 125, 96, 49, 253,
+ 103, 100, 22, 62, 1, 0, 0, 0, 68, 4, 0, 0, 6, 0, 0,
+ 0, 56, 0, 0, 0, 248, 0, 0, 0, 244, 1, 0, 0, 112, 2,
+ 0, 0, 160, 3, 0, 0, 212, 3, 0, 0, 65, 111, 110, 57, 184,
+ 0, 0, 0, 184, 0, 0, 0, 0, 2, 254, 255, 132, 0, 0, 0,
+ 52, 0, 0, 0, 1, 0, 36, 0, 0, 0, 48, 0, 0, 0, 48,
+ 0, 0, 0, 36, 0, 1, 0, 48, 0, 0, 0, 0, 0, 3, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81,
+ 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, 0, 0, 0, 128, 63,
+ 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0,
+ 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0,
+ 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0,
+ 0, 12, 224, 0, 0, 20, 144, 3, 0, 180, 160, 3, 0, 20, 160,
+ 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238,
+ 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0,
+ 228, 128, 0, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 4,
+ 0, 68, 160, 255, 255, 0, 0, 83, 72, 68, 82, 244, 0, 0, 0,
+ 64, 0, 1, 0, 61, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 95, 0, 0, 3, 50, 16,
+ 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0,
+ 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0,
+ 0, 50, 0, 0, 11, 50, 32, 16, 0, 0, 0, 0, 0, 70, 16,
+ 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 128, 63, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70,
+ 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 50, 0, 0, 11, 194, 32, 16, 0, 1, 0, 0, 0, 6, 20,
+ 16, 0, 0, 0, 0, 0, 166, 142, 32, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 6, 132, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 40, 1, 0,
+ 0, 1, 0, 0, 0, 64, 0, 0, 0, 1, 0, 0, 0, 28, 0,
+ 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 246, 0, 0, 0, 60,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 99, 98, 48, 0, 60, 0, 0, 0, 4, 0, 0, 0, 88, 0,
+ 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184,
+ 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0,
+ 196, 0, 0, 0, 0, 0, 0, 0, 212, 0, 0, 0, 16, 0, 0,
+ 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0,
+ 0, 0, 222, 0, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2,
+ 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 236, 0, 0, 0,
+ 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0,
+ 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171,
+ 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97,
+ 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120,
+ 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, 114, 111, 115, 111, 102,
+ 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100,
+ 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51,
+ 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 73, 83, 71,
+ 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78,
+ 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0,
+ 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3,
+ 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84,
+ 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 217, 2, 1, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 232, 9, 0, 0, 68, 88, 66, 67, 128, 131, 241, 85, 199, 21,
+ 192, 89, 55, 255, 82, 94, 121, 175, 16, 184, 1, 0, 0, 0, 232,
+ 9, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 2, 0, 0,
+ 8, 7, 0, 0, 132, 7, 0, 0, 68, 9, 0, 0, 180, 9, 0,
+ 0, 65, 111, 110, 57, 184, 2, 0, 0, 184, 2, 0, 0, 0, 2,
+ 255, 255, 120, 2, 0, 0, 64, 0, 0, 0, 2, 0, 40, 0, 0,
+ 0, 64, 0, 0, 0, 64, 0, 1, 0, 36, 0, 0, 0, 64, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 6, 0, 4, 0, 3, 0, 0, 0, 0, 0, 1, 2,
+ 255, 255, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31,
+ 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 2, 0, 0, 3,
+ 0, 0, 1, 128, 0, 0, 0, 176, 0, 0, 85, 160, 1, 0, 0,
+ 2, 0, 0, 2, 128, 0, 0, 85, 176, 2, 0, 0, 3, 1, 0,
+ 1, 128, 0, 0, 0, 176, 0, 0, 0, 160, 1, 0, 0, 2, 1,
+ 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, 3, 0, 0, 15, 128,
+ 0, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, 1, 0, 15,
+ 128, 1, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0,
+ 1, 128, 0, 0, 255, 128, 3, 0, 85, 160, 4, 0, 0, 4, 0,
+ 0, 1, 128, 3, 0, 0, 160, 1, 0, 255, 128, 0, 0, 0, 128,
+ 2, 0, 0, 3, 1, 0, 1, 128, 0, 0, 0, 176, 0, 0, 170,
+ 160, 1, 0, 0, 2, 1, 0, 2, 128, 0, 0, 85, 176, 2, 0,
+ 0, 3, 2, 0, 1, 128, 0, 0, 0, 176, 0, 0, 255, 160, 1,
+ 0, 0, 2, 2, 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, 3,
+ 1, 0, 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0,
+ 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, 0,
+ 0, 4, 0, 0, 1, 128, 3, 0, 170, 160, 1, 0, 255, 128, 0,
+ 0, 0, 128, 4, 0, 0, 4, 0, 0, 1, 128, 3, 0, 255, 160,
+ 2, 0, 255, 128, 0, 0, 0, 128, 2, 0, 0, 3, 1, 0, 1,
+ 128, 0, 0, 0, 176, 1, 0, 0, 160, 1, 0, 0, 2, 1, 0,
+ 2, 128, 0, 0, 85, 176, 2, 0, 0, 3, 2, 0, 1, 128, 0,
+ 0, 0, 176, 1, 0, 85, 160, 1, 0, 0, 2, 2, 0, 2, 128,
+ 0, 0, 85, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228,
+ 128, 0, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 2, 0,
+ 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 1, 128, 4,
+ 0, 0, 160, 1, 0, 255, 128, 0, 0, 0, 128, 4, 0, 0, 4,
+ 0, 0, 1, 128, 4, 0, 85, 160, 2, 0, 255, 128, 0, 0, 0,
+ 128, 2, 0, 0, 3, 1, 0, 1, 128, 0, 0, 0, 176, 1, 0,
+ 170, 160, 1, 0, 0, 2, 1, 0, 2, 128, 0, 0, 85, 176, 2,
+ 0, 0, 3, 2, 0, 1, 128, 0, 0, 0, 176, 1, 0, 255, 160,
+ 1, 0, 0, 2, 2, 0, 2, 128, 0, 0, 85, 176, 66, 0, 0,
+ 3, 1, 0, 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0,
+ 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4,
+ 0, 0, 4, 0, 0, 1, 128, 4, 0, 170, 160, 1, 0, 255, 128,
+ 0, 0, 0, 128, 4, 0, 0, 4, 0, 0, 1, 128, 4, 0, 255,
+ 160, 2, 0, 255, 128, 0, 0, 0, 128, 2, 0, 0, 3, 1, 0,
+ 1, 128, 0, 0, 0, 176, 2, 0, 0, 160, 1, 0, 0, 2, 1,
+ 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, 3, 1, 0, 15, 128,
+ 1, 0, 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 1,
+ 128, 5, 0, 0, 160, 1, 0, 255, 128, 0, 0, 0, 128, 5, 0,
+ 0, 3, 0, 0, 15, 128, 0, 0, 0, 128, 6, 0, 228, 160, 1,
+ 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0,
+ 83, 72, 68, 82, 8, 4, 0, 0, 64, 0, 0, 0, 2, 1, 0,
+ 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 10, 0,
+ 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88,
+ 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0,
+ 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0,
+ 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 4, 0,
+ 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 0, 0, 0, 0, 6,
+ 16, 16, 0, 1, 0, 0, 0, 38, 135, 32, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 1, 0, 0,
+ 0, 86, 7, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 162, 0,
+ 16, 0, 1, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 69,
+ 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0,
+ 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16,
+ 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0,
+ 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0,
+ 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 8,
+ 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0,
+ 0, 26, 128, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 50, 0,
+ 0, 10, 18, 0, 16, 0, 1, 0, 0, 0, 10, 128, 32, 0, 0,
+ 0, 0, 0, 6, 0, 0, 0, 58, 0, 16, 0, 1, 0, 0, 0,
+ 10, 0, 16, 0, 1, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16,
+ 0, 0, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 69, 0,
+ 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, 0, 0,
+ 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0,
+ 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0,
+ 0, 230, 10, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0,
+ 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 18,
+ 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0,
+ 6, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16,
+ 0, 1, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0,
+ 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 58,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 0, 8, 242, 0, 16, 0, 1, 0, 0, 0, 6, 16, 16,
+ 0, 1, 0, 0, 0, 38, 135, 32, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 2, 0, 0, 0, 86,
+ 7, 16, 0, 1, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, 0,
+ 2, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 69, 0, 0,
+ 9, 242, 0, 16, 0, 3, 0, 0, 0, 70, 0, 16, 0, 2, 0,
+ 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0,
+ 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0,
+ 230, 10, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0,
+ 0, 0, 96, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 18, 0,
+ 16, 0, 0, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 7,
+ 0, 0, 0, 58, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0,
+ 0, 26, 128, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 58, 0,
+ 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 54,
+ 0, 0, 5, 162, 0, 16, 0, 1, 0, 0, 0, 86, 21, 16, 0,
+ 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0,
+ 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0,
+ 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242,
+ 0, 16, 0, 1, 0, 0, 0, 230, 10, 16, 0, 1, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 42, 128,
+ 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 58, 0, 16, 0, 2,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10,
+ 18, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 58, 0, 16, 0, 1, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 1,
+ 0, 0, 0, 10, 16, 16, 0, 1, 0, 0, 0, 10, 128, 32, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16,
+ 0, 1, 0, 0, 0, 26, 16, 16, 0, 1, 0, 0, 0, 69, 0,
+ 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1,
+ 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0,
+ 0, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0,
+ 0, 10, 128, 32, 0, 0, 0, 0, 0, 8, 0, 0, 0, 58, 0,
+ 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 56,
+ 0, 0, 8, 242, 32, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0,
+ 0, 0, 0, 0, 70, 142, 32, 0, 0, 0, 0, 0, 9, 0, 0,
+ 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 30, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 13,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 184, 1,
+ 0, 0, 1, 0, 0, 0, 148, 0, 0, 0, 3, 0, 0, 0, 28,
+ 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 132, 1, 0, 0,
+ 124, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 139, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4,
+ 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0,
+ 12, 0, 0, 0, 143, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 115, 83, 104, 97, 100, 111, 119, 83, 97,
+ 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 99, 98, 49, 0, 171,
+ 143, 0, 0, 0, 4, 0, 0, 0, 172, 0, 0, 0, 160, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0,
+ 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 28, 1, 0, 0, 0,
+ 0, 0, 0, 44, 1, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0,
+ 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 0, 0, 76, 1, 0,
+ 0, 96, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 88, 1,
+ 0, 0, 0, 0, 0, 0, 104, 1, 0, 0, 144, 0, 0, 0, 16,
+ 0, 0, 0, 2, 0, 0, 0, 116, 1, 0, 0, 0, 0, 0, 0,
+ 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 72, 0, 171, 171,
+ 171, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 86, 0,
+ 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 66, 108, 117, 114, 87, 101, 105, 103, 104, 116, 115,
+ 0, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 83, 104, 97, 100, 111, 119, 67, 111, 108, 111, 114, 0, 1,
+ 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72,
+ 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112,
+ 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49,
+ 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0,
+ 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0,
+ 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0,
+ 0, 12, 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111,
+ 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79,
+ 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114,
+ 103, 101, 116, 0, 171, 171, 53, 7, 1, 0, 0, 0, 0, 0, 80,
+ 49, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 128, 63, 1,
+ 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 128, 63,
+ 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 3, 0, 0,
+ 0, 255, 255, 255, 255, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85,
+ 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1,
+ 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0,
+ 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0,
+ 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0,
+ 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1,
+ 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0,
+ 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0,
+ 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0,
+ 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144,
+ 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238,
+ 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0,
+ 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0,
+ 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160,
+ 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228,
+ 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255,
+ 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61,
+ 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0,
+ 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101,
+ 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11,
+ 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0,
+ 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194,
+ 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0,
+ 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0,
+ 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70,
+ 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11,
+ 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0,
+ 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132,
+ 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83,
+ 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0,
+ 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254,
+ 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0,
+ 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0,
+ 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0,
+ 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0,
+ 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0,
+ 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16,
+ 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0,
+ 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3,
+ 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101,
+ 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120,
+ 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111,
+ 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41,
+ 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111,
+ 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48,
+ 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0,
+ 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3,
+ 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79,
+ 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0,
+ 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3,
+ 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95,
+ 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79,
+ 82, 68, 0, 171, 171, 171, 92, 17, 1, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 172, 9, 0, 0,
+ 68, 88, 66, 67, 67, 47, 1, 244, 0, 102, 246, 41, 38, 220, 84,
+ 204, 156, 139, 96, 25, 1, 0, 0, 0, 172, 9, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0, 220, 2, 0, 0, 204, 6, 0, 0, 72,
+ 7, 0, 0, 8, 9, 0, 0, 120, 9, 0, 0, 65, 111, 110, 57,
+ 156, 2, 0, 0, 156, 2, 0, 0, 0, 2, 255, 255, 104, 2, 0,
+ 0, 52, 0, 0, 0, 1, 0, 40, 0, 0, 0, 52, 0, 0, 0,
+ 52, 0, 1, 0, 36, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255,
+ 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0,
+ 2, 0, 0, 0, 144, 0, 8, 15, 160, 2, 0, 0, 3, 0, 0,
+ 2, 128, 0, 0, 85, 176, 0, 0, 85, 160, 1, 0, 0, 2, 0,
+ 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, 1, 0, 2, 128,
+ 0, 0, 85, 176, 0, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1,
+ 128, 0, 0, 0, 176, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0,
+ 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, 1, 0, 15, 128, 1,
+ 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0, 15, 128,
+ 0, 0, 228, 128, 3, 0, 85, 160, 4, 0, 0, 4, 0, 0, 15,
+ 128, 3, 0, 0, 160, 1, 0, 228, 128, 0, 0, 228, 128, 2, 0,
+ 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, 0, 0, 170, 160, 1,
+ 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3,
+ 2, 0, 2, 128, 0, 0, 85, 176, 0, 0, 255, 160, 1, 0, 0,
+ 2, 2, 0, 1, 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0,
+ 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, 2,
+ 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, 0, 0, 4,
+ 0, 0, 15, 128, 3, 0, 170, 160, 1, 0, 228, 128, 0, 0, 228,
+ 128, 4, 0, 0, 4, 0, 0, 15, 128, 3, 0, 255, 160, 2, 0,
+ 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, 0,
+ 0, 85, 176, 1, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128,
+ 0, 0, 0, 176, 2, 0, 0, 3, 2, 0, 2, 128, 0, 0, 85,
+ 176, 1, 0, 85, 160, 1, 0, 0, 2, 2, 0, 1, 128, 0, 0,
+ 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 0,
+ 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128,
+ 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, 0,
+ 160, 1, 0, 228, 128, 0, 0, 228, 128, 4, 0, 0, 4, 0, 0,
+ 15, 128, 4, 0, 85, 160, 2, 0, 228, 128, 0, 0, 228, 128, 2,
+ 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, 1, 0, 170, 160,
+ 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, 176, 2, 0, 0,
+ 3, 2, 0, 2, 128, 0, 0, 85, 176, 1, 0, 255, 160, 1, 0,
+ 0, 2, 2, 0, 1, 128, 0, 0, 0, 176, 66, 0, 0, 3, 1,
+ 0, 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, 3,
+ 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, 0, 0,
+ 4, 0, 0, 15, 128, 4, 0, 170, 160, 1, 0, 228, 128, 0, 0,
+ 228, 128, 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, 255, 160, 2,
+ 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, 0, 2, 128,
+ 0, 0, 85, 176, 2, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1,
+ 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0,
+ 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, 5,
+ 0, 0, 160, 1, 0, 228, 128, 0, 0, 228, 128, 1, 0, 0, 2,
+ 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68,
+ 82, 232, 3, 0, 0, 64, 0, 0, 0, 250, 0, 0, 0, 89, 0,
+ 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 9, 0, 0, 0, 90,
+ 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, 24, 0, 4,
+ 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0,
+ 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32,
+ 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 4, 0, 0, 0, 54,
+ 0, 0, 5, 82, 0, 16, 0, 0, 0, 0, 0, 6, 16, 16, 0,
+ 1, 0, 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 1, 0, 0,
+ 0, 86, 21, 16, 0, 1, 0, 0, 0, 134, 141, 32, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, 0, 0,
+ 0, 0, 0, 6, 8, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9,
+ 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, 0, 0, 0,
+ 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0,
+ 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70,
+ 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0,
+ 0, 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 8, 242, 0, 16,
+ 0, 2, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 86, 133,
+ 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 50, 0, 0, 10, 242,
+ 0, 16, 0, 0, 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, 0,
+ 6, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 70, 14, 16,
+ 0, 2, 0, 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 1, 0,
+ 0, 0, 6, 16, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242,
+ 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 10,
+ 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0,
+ 96, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0,
+ 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 6, 0, 0,
+ 0, 70, 14, 16, 0, 2, 0, 0, 0, 70, 14, 16, 0, 0, 0,
+ 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 246,
+ 143, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 70, 14, 16, 0,
+ 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 54, 0, 0,
+ 5, 82, 0, 16, 0, 1, 0, 0, 0, 6, 16, 16, 0, 1, 0,
+ 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 2, 0, 0, 0, 86,
+ 21, 16, 0, 1, 0, 0, 0, 134, 141, 32, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, 0, 1, 0, 0,
+ 0, 6, 8, 16, 0, 2, 0, 0, 0, 69, 0, 0, 9, 242, 0,
+ 16, 0, 3, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 70,
+ 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0,
+ 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 10, 16,
+ 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96,
+ 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0,
+ 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0,
+ 70, 14, 16, 0, 3, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0,
+ 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 86, 133,
+ 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, 0, 1,
+ 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5,
+ 82, 0, 16, 0, 2, 0, 0, 0, 6, 16, 16, 0, 1, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0,
+ 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0,
+ 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0,
+ 2, 0, 0, 0, 230, 10, 16, 0, 2, 0, 0, 0, 70, 126, 16,
+ 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 50, 0,
+ 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, 0, 1, 0, 0, 0,
+ 70, 14, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16,
+ 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 7, 0,
+ 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 70, 14, 16, 0, 0,
+ 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 1, 0, 0, 0,
+ 26, 16, 16, 0, 1, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0,
+ 0, 5, 0, 0, 0, 54, 0, 0, 5, 18, 0, 16, 0, 1, 0,
+ 0, 0, 10, 16, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242,
+ 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 50, 0, 0, 10, 242, 32, 16, 0, 0, 0, 0, 0, 6, 128,
+ 32, 0, 0, 0, 0, 0, 8, 0, 0, 0, 70, 14, 16, 0, 1,
+ 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1,
+ 83, 84, 65, 84, 116, 0, 0, 0, 29, 0, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 12, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 82, 68, 69, 70, 184, 1, 0, 0, 1, 0, 0,
+ 0, 148, 0, 0, 0, 3, 0, 0, 0, 28, 0, 0, 0, 0, 4,
+ 255, 255, 0, 1, 0, 0, 132, 1, 0, 0, 124, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 139, 0, 0,
+ 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255,
+ 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 143,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 115, 83, 104, 97, 100, 111, 119, 83, 97, 109, 112, 108, 101, 114,
+ 0, 116, 101, 120, 0, 99, 98, 49, 0, 171, 143, 0, 0, 0, 4,
+ 0, 0, 0, 172, 0, 0, 0, 160, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 0, 0, 48, 0, 0,
+ 0, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 0, 0, 44, 1,
+ 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 60,
+ 1, 0, 0, 0, 0, 0, 0, 76, 1, 0, 0, 96, 0, 0, 0,
+ 48, 0, 0, 0, 2, 0, 0, 0, 88, 1, 0, 0, 0, 0, 0,
+ 0, 104, 1, 0, 0, 144, 0, 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 116, 1, 0, 0, 0, 0, 0, 0, 66, 108, 117, 114, 79,
+ 102, 102, 115, 101, 116, 115, 72, 0, 171, 171, 171, 1, 0, 3, 0,
+ 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 66, 108, 117,
+ 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, 171, 171, 171, 1, 0,
+ 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 66,
+ 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, 0, 1, 0, 3, 0,
+ 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 83, 104, 97,
+ 100, 111, 119, 67, 111, 108, 111, 114, 0, 1, 0, 3, 0, 1, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, 111,
+ 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83,
+ 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32,
+ 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0,
+ 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8,
+ 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0,
+ 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0,
+ 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88,
+ 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0,
+ 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171,
+ 171, 184, 21, 1, 0, 0, 0, 0, 0, 80, 50, 0, 4, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0,
+ 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0,
+ 0, 128, 63, 1, 0, 0, 0, 3, 0, 0, 0, 255, 255, 255, 255,
+ 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56, 155,
+ 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68, 4,
+ 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0, 244,
+ 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0, 0,
+ 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2, 254,
+ 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0, 0,
+ 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0, 0,
+ 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 0,
+ 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0,
+ 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0,
+ 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160,
+ 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0, 180,
+ 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0,
+ 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0,
+ 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0, 2,
+ 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72, 68,
+ 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89, 0,
+ 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 95,
+ 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, 4,
+ 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, 0,
+ 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, 32,
+ 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 0,
+ 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0,
+ 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16, 0,
+ 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0, 1,
+ 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0, 1,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0,
+ 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0, 4,
+ 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0,
+ 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212, 0,
+ 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196,
+ 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0, 0,
+ 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0,
+ 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68,
+ 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114,
+ 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100,
+ 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, 99,
+ 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76,
+ 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101,
+ 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56,
+ 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8,
+ 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83,
+ 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0,
+ 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116,
+ 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171,
+ 171, 163, 31, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 164, 10, 0, 0, 68, 88, 66, 67, 70,
+ 166, 174, 156, 153, 145, 163, 116, 127, 37, 205, 162, 136, 116, 62, 222,
+ 1, 0, 0, 0, 164, 10, 0, 0, 6, 0, 0, 0, 56, 0, 0,
+ 0, 24, 3, 0, 0, 112, 7, 0, 0, 236, 7, 0, 0, 0, 10,
+ 0, 0, 112, 10, 0, 0, 65, 111, 110, 57, 216, 2, 0, 0, 216,
+ 2, 0, 0, 0, 2, 255, 255, 160, 2, 0, 0, 56, 0, 0, 0,
+ 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36,
+ 0, 0, 0, 56, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 3, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 31,
+ 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2,
+ 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0,
+ 144, 1, 8, 15, 160, 2, 0, 0, 3, 0, 0, 2, 128, 0, 0,
+ 85, 176, 0, 0, 85, 160, 1, 0, 0, 2, 0, 0, 1, 128, 0,
+ 0, 0, 176, 2, 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176,
+ 0, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0,
+ 176, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 1, 8,
+ 228, 160, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 1,
+ 8, 228, 160, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128,
+ 3, 0, 85, 160, 4, 0, 0, 4, 0, 0, 15, 128, 3, 0, 0,
+ 160, 1, 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, 0,
+ 2, 128, 0, 0, 85, 176, 0, 0, 170, 160, 1, 0, 0, 2, 1,
+ 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, 2, 0, 2, 128,
+ 0, 0, 85, 176, 0, 0, 255, 160, 1, 0, 0, 2, 2, 0, 1,
+ 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0,
+ 228, 128, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 2,
+ 0, 228, 128, 1, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128,
+ 3, 0, 170, 160, 1, 0, 228, 128, 0, 0, 228, 128, 4, 0, 0,
+ 4, 0, 0, 15, 128, 3, 0, 255, 160, 2, 0, 228, 128, 0, 0,
+ 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, 1,
+ 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, 176,
+ 2, 0, 0, 3, 2, 0, 2, 128, 0, 0, 85, 176, 1, 0, 85,
+ 160, 1, 0, 0, 2, 2, 0, 1, 128, 0, 0, 0, 176, 66, 0,
+ 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 1, 8, 228, 160, 66,
+ 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 1, 8, 228, 160,
+ 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, 0, 160, 1, 0, 228,
+ 128, 0, 0, 228, 128, 4, 0, 0, 4, 0, 0, 15, 128, 4, 0,
+ 85, 160, 2, 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1,
+ 0, 2, 128, 0, 0, 85, 176, 1, 0, 170, 160, 1, 0, 0, 2,
+ 1, 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, 2, 0, 2,
+ 128, 0, 0, 85, 176, 1, 0, 255, 160, 1, 0, 0, 2, 2, 0,
+ 1, 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1,
+ 0, 228, 128, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128,
+ 2, 0, 228, 128, 1, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15,
+ 128, 4, 0, 170, 160, 1, 0, 228, 128, 0, 0, 228, 128, 4, 0,
+ 0, 4, 0, 0, 15, 128, 4, 0, 255, 160, 2, 0, 228, 128, 0,
+ 0, 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176,
+ 2, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0,
+ 176, 1, 0, 0, 2, 2, 0, 3, 128, 0, 0, 235, 176, 66, 0,
+ 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 1, 8, 228, 160, 66,
+ 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160,
+ 4, 0, 0, 4, 0, 0, 15, 128, 5, 0, 0, 160, 1, 0, 228,
+ 128, 0, 0, 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 2, 0,
+ 255, 128, 0, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 0,
+ 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 80, 4, 0, 0,
+ 64, 0, 0, 0, 20, 1, 0, 0, 89, 0, 0, 4, 70, 142, 32,
+ 0, 0, 0, 0, 0, 9, 0, 0, 0, 90, 0, 0, 3, 0, 96,
+ 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1,
+ 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0,
+ 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0,
+ 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0,
+ 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, 1, 0, 0, 0, 101,
+ 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2,
+ 4, 0, 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 0, 0, 0,
+ 0, 6, 16, 16, 0, 1, 0, 0, 0, 0, 0, 0, 8, 242, 0,
+ 16, 0, 1, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 134,
+ 141, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 54, 0, 0, 5,
+ 162, 0, 16, 0, 0, 0, 0, 0, 6, 8, 16, 0, 1, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 230, 10,
+ 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0,
+ 96, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0,
+ 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 126, 16,
+ 0, 0, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0,
+ 0, 8, 242, 0, 16, 0, 2, 0, 0, 0, 70, 14, 16, 0, 2,
+ 0, 0, 0, 86, 133, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0,
+ 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 6, 128, 32,
+ 0, 0, 0, 0, 0, 6, 0, 0, 0, 70, 14, 16, 0, 0, 0,
+ 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, 82,
+ 0, 16, 0, 1, 0, 0, 0, 6, 16, 16, 0, 1, 0, 0, 0,
+ 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 0, 16,
+ 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96,
+ 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1,
+ 0, 0, 0, 230, 10, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0,
+ 0, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 50, 0, 0,
+ 10, 242, 0, 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0,
+ 0, 0, 6, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 70,
+ 14, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0,
+ 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 6, 0, 0,
+ 0, 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0,
+ 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 1, 0, 0, 0, 6,
+ 16, 16, 0, 1, 0, 0, 0, 0, 0, 0, 8, 242, 0, 16, 0,
+ 2, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 134, 141, 32,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 54, 0, 0, 5, 162, 0,
+ 16, 0, 1, 0, 0, 0, 6, 8, 16, 0, 2, 0, 0, 0, 69,
+ 0, 0, 9, 242, 0, 16, 0, 3, 0, 0, 0, 70, 0, 16, 0,
+ 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16,
+ 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0,
+ 0, 0, 230, 10, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0,
+ 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 50, 0, 0, 10,
+ 242, 0, 16, 0, 0, 0, 0, 0, 6, 128, 32, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 70, 14, 16, 0, 3, 0, 0, 0, 70, 14,
+ 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0,
+ 0, 0, 0, 86, 133, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0,
+ 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0,
+ 0, 54, 0, 0, 5, 82, 0, 16, 0, 2, 0, 0, 0, 6, 16,
+ 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1,
+ 0, 0, 0, 70, 0, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0,
+ 0, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 69, 0, 0,
+ 9, 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, 2, 0,
+ 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 1,
+ 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0,
+ 166, 138, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 70, 14, 16,
+ 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 50, 0,
+ 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 246, 143, 32, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0,
+ 70, 14, 16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16,
+ 0, 1, 0, 0, 0, 26, 16, 16, 0, 1, 0, 0, 0, 10, 128,
+ 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 54, 0, 0, 5, 18,
+ 0, 16, 0, 1, 0, 0, 0, 10, 16, 16, 0, 1, 0, 0, 0,
+ 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16,
+ 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96,
+ 16, 0, 1, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0,
+ 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, 0, 8, 0, 0, 0,
+ 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 26,
+ 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0,
+ 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0,
+ 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, 16,
+ 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 31, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 12, 2, 0, 0, 1, 0, 0, 0, 232, 0, 0, 0, 5,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0,
+ 216, 1, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 2, 0, 0,
+ 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0,
+ 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, 0, 0, 2,
+ 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255,
+ 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 225, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115,
+ 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, 115, 83, 104,
+ 97, 100, 111, 119, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120,
+ 0, 109, 97, 115, 107, 0, 99, 98, 49, 0, 171, 171, 171, 225, 0,
+ 0, 0, 4, 0, 0, 0, 0, 1, 0, 0, 160, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 96, 1, 0, 0, 0, 0, 0, 0,
+ 48, 0, 0, 0, 0, 0, 0, 0, 112, 1, 0, 0, 0, 0, 0,
+ 0, 128, 1, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 2, 0,
+ 0, 0, 144, 1, 0, 0, 0, 0, 0, 0, 160, 1, 0, 0, 96,
+ 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 172, 1, 0, 0,
+ 0, 0, 0, 0, 188, 1, 0, 0, 144, 0, 0, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 200, 1, 0, 0, 0, 0, 0, 0, 66, 108,
+ 117, 114, 79, 102, 102, 115, 101, 116, 115, 72, 0, 171, 171, 171, 1,
+ 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, 171, 171,
+ 171, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 66, 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, 0, 1,
+ 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 83, 104, 97, 100, 111, 119, 67, 111, 108, 111, 114, 0, 1, 0, 3,
+ 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105,
+ 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108,
+ 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51,
+ 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0,
+ 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
+ 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12,
+ 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0,
+ 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71,
+ 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101,
+ 116, 0, 171, 171, 255, 35, 1, 0, 0, 0, 0, 0, 83, 97, 109,
+ 112, 108, 101, 84, 101, 120, 116, 84, 101, 120, 116, 117, 114, 101, 0,
+ 85, 110, 109, 97, 115, 107, 101, 100, 0, 4, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 3, 0, 0, 0, 255, 255, 255, 255, 68, 4, 0,
+ 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56, 155, 78, 125, 96,
+ 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68, 4, 0, 0, 6,
+ 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0, 244, 1, 0, 0,
+ 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0, 0, 65, 111, 110,
+ 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2, 254, 255, 132, 0,
+ 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0, 0, 48, 0, 0,
+ 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0, 0, 0, 0, 0,
+ 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254,
+ 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, 0, 0, 0,
+ 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5,
+ 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224,
+ 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0,
+ 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0, 180, 160, 3, 0,
+ 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1,
+ 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 3, 192,
+ 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12,
+ 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72, 68, 82, 244, 0,
+ 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89, 0, 0, 4, 70,
+ 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 95, 0, 0, 3,
+ 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32,
+ 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1,
+ 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 0, 0, 0, 0,
+ 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2,
+ 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0,
+ 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0, 1, 0, 0, 0,
+ 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 5,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 40,
+ 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0, 1, 0, 0, 0,
+ 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 246, 0, 0,
+ 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0, 4, 0, 0, 0,
+ 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0,
+ 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212, 0, 0, 0, 16,
+ 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0,
+ 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0, 0, 16, 0, 0,
+ 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 236, 0,
+ 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 196,
+ 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99,
+ 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0,
+ 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84,
+ 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, 114, 111, 115,
+ 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104,
+ 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54,
+ 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 73,
+ 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73,
+ 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3,
+ 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0,
+ 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0,
+ 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110,
+ 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 250, 46,
+ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 152, 4, 0, 0, 68, 88, 66, 67, 227, 84, 48, 176,
+ 142, 231, 109, 63, 97, 30, 1, 57, 105, 137, 178, 120, 1, 0, 0,
+ 0, 152, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 4, 1,
+ 0, 0, 224, 1, 0, 0, 92, 2, 0, 0, 220, 3, 0, 0, 76,
+ 4, 0, 0, 65, 111, 110, 57, 196, 0, 0, 0, 196, 0, 0, 0,
+ 0, 2, 255, 255, 144, 0, 0, 0, 52, 0, 0, 0, 1, 0, 40,
+ 0, 0, 0, 52, 0, 0, 0, 52, 0, 1, 0, 36, 0, 0, 0,
+ 52, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 1, 0, 15, 160,
+ 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0,
+ 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 1, 0, 0, 2, 0,
+ 0, 7, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128,
+ 0, 0, 36, 128, 1, 0, 64, 160, 1, 0, 21, 160, 1, 0, 0,
+ 2, 0, 8, 15, 128, 0, 0, 228, 128, 66, 0, 0, 3, 0, 0,
+ 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 5, 0, 0, 3, 0,
+ 0, 15, 128, 0, 0, 70, 128, 0, 0, 255, 160, 1, 0, 0, 2,
+ 1, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68,
+ 82, 212, 0, 0, 0, 64, 0, 0, 0, 53, 0, 0, 0, 89, 0,
+ 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 90,
+ 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, 24, 0, 4,
+ 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0,
+ 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32,
+ 16, 0, 0, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 1,
+ 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 54, 0, 0, 6,
+ 114, 32, 16, 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 69, 0, 0, 9, 242,
+ 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0,
+ 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0,
+ 0, 56, 0, 0, 8, 242, 32, 16, 0, 1, 0, 0, 0, 102, 4,
+ 16, 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0,
+ 5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70,
+ 120, 1, 0, 0, 1, 0, 0, 0, 144, 0, 0, 0, 3, 0, 0,
+ 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 70, 1,
+ 0, 0, 124, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 133, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0,
+ 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0,
+ 0, 0, 12, 0, 0, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, 109, 112, 108, 101,
+ 114, 0, 116, 101, 120, 0, 99, 98, 48, 0, 171, 171, 171, 137, 0,
+ 0, 0, 4, 0, 0, 0, 168, 0, 0, 0, 64, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 0,
+ 0, 36, 1, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 20, 1, 0, 0, 0, 0, 0, 0, 46, 1, 0, 0, 32,
+ 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 20, 1, 0, 0,
+ 0, 0, 0, 0, 60, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0,
+ 0, 2, 0, 0, 0, 20, 1, 0, 0, 0, 0, 0, 0, 81, 117,
+ 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1,
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67,
+ 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111,
+ 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0,
+ 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72,
+ 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112,
+ 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49,
+ 54, 51, 56, 52, 0, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0,
+ 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
+ 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12,
+ 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0,
+ 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71,
+ 78, 68, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 0, 56, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 15, 0, 0, 0, 56, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0,
+ 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 86, 51,
+ 1, 0, 0, 0, 0, 0, 77, 97, 115, 107, 101, 100, 0, 4, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 255, 255, 255,
+ 255, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56,
+ 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68,
+ 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0,
+ 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0,
+ 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2,
+ 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0,
+ 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0,
+ 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0,
+ 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31,
+ 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4,
+ 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228,
+ 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0,
+ 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0,
+ 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3,
+ 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0,
+ 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72,
+ 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89,
+ 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0,
+ 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0,
+ 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194,
+ 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0,
+ 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0,
+ 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16,
+ 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138,
+ 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0,
+ 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116,
+ 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82,
+ 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0,
+ 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0,
+ 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0,
+ 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0,
+ 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212,
+ 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0,
+ 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0,
+ 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0,
+ 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0,
+ 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100,
+ 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111,
+ 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114,
+ 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105,
+ 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108,
+ 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51,
+ 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0,
+ 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79,
+ 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104,
+ 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105,
+ 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171,
+ 171, 171, 49, 56, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 140, 5, 0, 0, 68, 88, 66, 67,
+ 233, 167, 4, 110, 60, 182, 197, 16, 114, 252, 67, 184, 217, 172, 169,
+ 241, 1, 0, 0, 0, 140, 5, 0, 0, 6, 0, 0, 0, 56, 0,
+ 0, 0, 64, 1, 0, 0, 132, 2, 0, 0, 0, 3, 0, 0, 208,
+ 4, 0, 0, 64, 5, 0, 0, 65, 111, 110, 57, 0, 1, 0, 0,
+ 0, 1, 0, 0, 0, 2, 255, 255, 200, 0, 0, 0, 56, 0, 0,
+ 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0,
+ 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255,
+ 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 0, 0,
+ 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0,
+ 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160,
+ 1, 0, 0, 2, 0, 0, 7, 128, 0, 0, 228, 160, 4, 0, 0,
+ 4, 0, 0, 15, 128, 0, 0, 36, 128, 1, 0, 64, 160, 1, 0,
+ 21, 160, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 1,
+ 0, 0, 2, 0, 0, 3, 128, 0, 0, 235, 176, 66, 0, 0, 3,
+ 1, 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 66, 0, 0,
+ 3, 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, 228, 160, 5, 0,
+ 0, 3, 1, 0, 15, 128, 1, 0, 70, 128, 0, 0, 255, 160, 5,
+ 0, 0, 3, 0, 0, 15, 128, 0, 0, 255, 128, 1, 0, 228, 128,
+ 1, 0, 0, 2, 1, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0,
+ 0, 83, 72, 68, 82, 60, 1, 0, 0, 64, 0, 0, 0, 79, 0,
+ 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0,
+ 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0,
+ 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24,
+ 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98,
+ 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3,
+ 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16,
+ 0, 0, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 1, 0,
+ 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 6, 114,
+ 32, 16, 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, 0, 0, 0, 0,
+ 0, 1, 64, 0, 0, 0, 0, 128, 63, 69, 0, 0, 9, 242, 0,
+ 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70,
+ 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0,
+ 56, 0, 0, 8, 242, 0, 16, 0, 0, 0, 0, 0, 102, 4, 16,
+ 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230,
+ 26, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0,
+ 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16,
+ 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15,
+ 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116,
+ 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82,
+ 68, 69, 70, 200, 1, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0,
+ 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0,
+ 0, 150, 1, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0,
+ 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0,
+ 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0,
+ 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255,
+ 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 115, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83,
+ 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107,
+ 0, 99, 98, 48, 0, 171, 219, 0, 0, 0, 4, 0, 0, 0, 248,
+ 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 88, 1, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 100, 1, 0, 0, 0, 0, 0, 0, 116, 1, 0, 0, 16, 0,
+ 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 100, 1, 0, 0, 0,
+ 0, 0, 0, 126, 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0,
+ 0, 0, 0, 0, 100, 1, 0, 0, 0, 0, 0, 0, 140, 1, 0,
+ 0, 48, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 100, 1,
+ 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0,
+ 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77,
+ 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101,
+ 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, 114, 111, 115, 111,
+ 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97,
+ 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46,
+ 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 73, 83,
+ 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3,
+ 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80,
+ 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82,
+ 68, 0, 171, 171, 171, 79, 83, 71, 78, 68, 0, 0, 0, 2, 0,
+ 0, 0, 8, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
+ 56, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
+ 0, 1, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114,
+ 103, 101, 116, 0, 171, 171, 141, 60, 1, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 255, 255, 255, 255, 0, 0, 0, 0, 46, 0, 0, 0, 18, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 64, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0,
+ 93, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0,
+ 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0,
+ 65, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 65, 0,
+ 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 136, 0, 0, 0, 160, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0,
+ 0, 168, 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181,
+ 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0,
+ 0, 140, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 0, 0, 0, 65,
+ 0, 0, 0, 0, 0, 0, 0, 144, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 218, 0, 0, 0, 112, 0, 0,
+ 0, 0, 0, 0, 0, 7, 0, 0, 0, 255, 255, 255, 255, 0, 0,
+ 0, 0, 3, 1, 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 61, 1, 0, 0, 33, 1, 0, 0, 0, 0, 0, 0, 48, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 1,
+ 0, 0, 79, 1, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 1, 0, 0,
+ 33, 1, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 1, 0, 0, 126, 1,
+ 0, 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 156, 1, 0, 0, 126, 1, 0, 0,
+ 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 164, 1, 0, 0, 126, 1, 0, 0, 0, 0,
+ 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 175, 1, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0,
+ 6, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 179, 1, 0,
+ 0, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 1, 0, 0, 33,
+ 1, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 221, 1, 0, 0, 33, 1, 0,
+ 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 228, 1, 0, 0, 126, 1, 0, 0, 0,
+ 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 234, 1, 0, 0, 126, 1, 0, 0, 0, 0, 0,
+ 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 247, 1, 0, 0, 126, 1, 0, 0, 0, 0, 0, 0, 72,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 40, 2, 0, 0, 12, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255,
+ 255, 0, 0, 0, 0, 44, 2, 0, 0, 12, 2, 0, 0, 0, 0,
+ 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 51, 2, 0, 0, 12,
+ 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0,
+ 97, 2, 0, 0, 69, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255,
+ 255, 4, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 106, 2, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 40, 2, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 118, 2, 0, 0, 47, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 130, 2, 0, 0, 0, 0, 0, 0, 142, 2,
+ 0, 0, 69, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4,
+ 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 154, 2, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 44, 2, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 166, 2, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 178, 2, 0, 0, 0, 0, 0, 0, 190, 2, 0, 0,
+ 69, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4, 0, 0,
+ 0, 45, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 203, 2,
+ 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 40,
+ 2, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 215, 2, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 227, 2, 0, 0, 0, 0, 0, 0, 239, 2, 0, 0, 69, 2,
+ 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4, 0, 0, 0, 45,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 254, 2, 0, 0,
+ 55, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 40, 2, 0,
+ 0, 46, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 10, 3,
+ 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 22,
+ 3, 0, 0, 0, 0, 0, 0, 34, 3, 0, 0, 69, 2, 0, 0,
+ 0, 0, 0, 0, 255, 255, 255, 255, 4, 0, 0, 0, 45, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 47, 3, 0, 0, 55, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 51, 2, 0, 0, 46,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 59, 3, 0, 0,
+ 47, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 71, 3, 0,
+ 0, 0, 0, 0, 0, 83, 3, 0, 0, 69, 2, 0, 0, 0, 0,
+ 0, 0, 255, 255, 255, 255, 5, 0, 0, 0, 45, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 98, 3, 0, 0, 55, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 40, 2, 0, 0, 46, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 110, 3, 0, 0, 47, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 122, 3, 0, 0, 52,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 134, 3, 0, 0,
+ 0, 0, 0, 0, 214, 3, 0, 0, 186, 3, 0, 0, 0, 0, 0,
+ 0, 255, 255, 255, 255, 2, 0, 0, 0, 19, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 226, 3, 0, 0, 13, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 238, 3, 0, 0, 0, 0, 0, 0,
+ 33, 4, 0, 0, 5, 4, 0, 0, 0, 0, 0, 0, 255, 255, 255,
+ 255, 2, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 46, 4, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 58, 4, 0, 0, 0, 0, 0, 0, 70, 4, 0, 0,
+ 5, 4, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 8, 0, 0,
+ 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 83, 4,
+ 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 95,
+ 4, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 107, 4, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 119, 4, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 131, 4, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 143, 4, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 155, 4, 0, 0, 44, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 167, 4, 0, 0, 0, 0, 0, 0, 179, 4,
+ 0, 0, 5, 4, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 9,
+ 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 190, 4, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 202, 4, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 214, 4, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 226, 4, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 238, 4, 0, 0, 41, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 250, 4, 0, 0, 42, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 6, 5, 0, 0, 43, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 18, 5, 0, 0, 44, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 30, 5, 0, 0, 0, 0, 0,
+ 0, 42, 5, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 56, 5,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 131, 9, 0, 0, 8, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 139, 9, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 111, 12, 0, 0, 119,
+ 12, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0, 227, 16, 0, 0, 8, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 235, 16, 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 67, 30, 0, 0, 75, 30, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 183, 34, 0, 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 191, 34, 0, 0, 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0, 39, 52, 0, 0, 47, 52, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0,
+ 0, 0, 156, 56, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 164, 56, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 140, 94, 0, 0, 148, 94, 0, 0, 6, 0, 0,
+ 0, 0, 0, 0, 0, 169, 94, 0, 0, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 214,
+ 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,
+ 222, 101, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 230, 101, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0,
+ 0, 0, 214, 111, 0, 0, 222, 111, 0, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0,
+ 0, 17, 119, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 25, 119, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7,
+ 0, 0, 0, 233, 126, 0, 0, 241, 126, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0,
+ 0, 0, 42, 134, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 50, 134, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 38, 144, 0, 0, 46, 144, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7,
+ 0, 0, 0, 101, 151, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 109, 151, 0, 0, 7, 0, 0, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 65, 159, 0, 0, 73, 159, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 132, 166, 0, 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 140, 166, 0, 0, 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0, 132, 176, 0, 0, 140, 176, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 197, 183, 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 205, 183, 0, 0, 7, 0, 0, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 165, 191, 0, 0, 173, 191, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 169, 94, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7,
+ 0, 0, 0, 209, 198, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 217, 198, 0, 0, 7, 0, 0, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 53, 211, 0, 0, 241, 126, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 77, 218, 0, 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 85, 218, 0, 0, 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0, 181, 230, 0, 0, 73, 159, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 205, 237, 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 213, 237, 0, 0, 7, 0, 0, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 57, 250, 0, 0, 65, 250, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7,
+ 0, 0, 0, 157, 254, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 165, 254, 0, 0, 7, 0, 0, 0, 0, 0, 0,
+ 0, 7, 0, 0, 0, 137, 2, 1, 0, 145, 2, 1, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 7, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 214, 3, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 169, 2, 1, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 205, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 33, 4, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 33, 7, 1, 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 41, 7, 1, 0, 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0, 33, 17, 1, 0, 41, 17, 1, 0, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 214, 3, 0, 0, 10, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 44, 17, 1, 0, 11, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 80, 17, 1, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 70, 4, 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 164, 21, 1, 0, 8, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 172, 21, 1, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 104, 31, 1, 0, 112,
+ 31, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0, 214, 3, 0, 0, 10, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 115, 31, 1, 0, 11, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 151, 31, 1, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 70, 4, 0, 0,
+ 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 235, 35, 1,
+ 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 243, 35,
+ 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 167,
+ 46, 1, 0, 175, 46, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 193, 46, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 214, 3, 0, 0, 10, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 202, 46, 1, 0, 11,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 238, 46, 1, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 179, 4, 0,
+ 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 66, 51,
+ 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 74,
+ 51, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,
+ 242, 55, 1, 0, 250, 55, 1, 0, 7, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 214, 3,
+ 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
+ 56, 1, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 37, 56, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 179, 4, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0,
+ 0, 0, 121, 60, 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 129, 60, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 29, 66, 1, 0};
diff --git a/gfx/2d/ShadersD2D1.h b/gfx/2d/ShadersD2D1.h
new file mode 100644
index 0000000000..9df2858da5
--- /dev/null
+++ b/gfx/2d/ShadersD2D1.h
@@ -0,0 +1,1186 @@
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+//
+//
+// Buffer Definitions:
+//
+// cbuffer radialGradientConstants
+// {
+//
+// float3 diff; // Offset: 0 Size: 12
+// float2 center1; // Offset: 16 Size: 8
+// float A; // Offset: 24 Size: 4
+// float radius1; // Offset: 28 Size: 4
+// float sq_radius1; // Offset: 32 Size: 4
+// float repeat_correct; // Offset: 36 Size: 4
+// float allow_odd; // Offset: 40 Size: 4
+// float3x2 transform; // Offset: 48 Size: 28
+//
+// }
+//
+//
+// Resource Bindings:
+//
+// Name Type Format Dim Slot Elements
+// ------------------------------ ---------- ------- ----------- ---- --------
+// InputSampler sampler NA NA 0 1
+// GradientSampler sampler NA NA 1 1
+// InputTexture texture float4 2d 0 1
+// GradientTexture texture float4 2d 1 1
+// radialGradientConstants cbuffer NA NA 0 1
+//
+//
+//
+// Input signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_POSITION 0 xyzw 0 POS float
+// SCENE_POSITION 0 xyzw 1 NONE float xy
+// TEXCOORD 0 xyzw 2 NONE float xy
+//
+//
+// Output signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_Target 0 xyzw 0 TARGET float xyzw
+//
+//
+// Constant buffer to DX9 shader constant mappings:
+//
+// Target Reg Buffer Start Reg # of Regs Data Conversion
+// ---------- ------- --------- --------- ----------------------
+// c0 cb0 0 5 ( FLT, FLT, FLT, FLT)
+//
+//
+// Sampler/Resource to DX9 shader sampler mappings:
+//
+// Target Sampler Source Sampler Source Resource
+// -------------- --------------- ----------------
+// s0 s0 t0
+// s1 s1 t1
+//
+//
+// Level9 shader bytecode:
+//
+ ps_2_x
+ def c5, 0.5, 1, 0, 0
+ def c6, 1, -1, 0, -0
+ dcl t0
+ dcl t1
+ dcl_2d s0
+ dcl_2d s1
+ dp2add r0.x, t0, c3, c3.z
+ dp2add r0.y, t0, c4, c4.z
+ add r0.xy, r0, -c1
+ dp2add r0.w, r0, r0, -c2.x
+ mul r0.w, r0.w, c1.z
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ mad r0.y, r0.x, r0.x, -r0.w
+ abs r0.z, r0.y
+ cmp r0.y, r0.y, c5.y, c5.z
+ rsq r0.z, r0.z
+ rcp r1.x, r0.z
+ mov r1.yz, -r1.x
+ add r0.xzw, r0.x, r1.xyyz
+ rcp r1.x, c1.z
+ mul r0.xzw, r0, r1.x
+ mov r1.w, c1.w
+ mad r1.xyz, r0.xzww, c0.z, r1.w
+ cmp r1.w, r1.x, r0.x, r0.w
+ cmp r0.xzw, r1.xyyz, c6.xyxy, c6.zyzw
+ frc r1.x, r1.w
+ add r1.x, -r1.x, r1.w
+ mul r1.y, r1.x, c5.x
+ abs r1.y, r1.y
+ frc r1.y, r1.y
+ cmp r1.y, r1.x, r1.y, -r1.y
+ add r1.x, -r1.x, r1.w
+ add r1.y, r1.y, r1.y
+ abs r1.y, r1.y
+ mul r1.y, r1.y, c2.z
+ frc r1.z, -r1.w
+ lrp r2.w, r1.y, r1.z, r1.x
+ lrp r3.x, c2.y, r2.w, r1.w
+ mov r3.y, c5.x
+ texld r1, t1, s0
+ texld r2, r3, s1
+ mul r2.xyz, r2.w, r2
+ mul r1, r1, r2
+ add r0.w, r0.w, r0.x
+ cmp r0.x, r0.w, r0.x, r0.z
+ mul r1, r0.x, r1
+ mul r0, r0.y, r1
+ mov oC0, r0
+
+// approximately 46 instruction slots used (2 texture, 44 arithmetic)
+ps_4_0
+dcl_constantbuffer cb0[5], immediateIndexed
+dcl_sampler s0, mode_default
+dcl_sampler s1, mode_default
+dcl_resource_texture2d (float,float,float,float) t0
+dcl_resource_texture2d (float,float,float,float) t1
+dcl_input_ps linear v1.xy
+dcl_input_ps linear v2.xy
+dcl_output o0.xyzw
+dcl_temps 3
+dp2 r0.x, v1.xyxx, cb0[3].xyxx
+add r0.x, r0.x, cb0[3].z
+dp2 r0.z, v1.xyxx, cb0[4].xyxx
+add r0.y, r0.z, cb0[4].z
+add r0.xy, r0.xyxx, -cb0[1].xyxx
+dp2 r0.w, r0.xyxx, r0.xyxx
+add r0.w, r0.w, -cb0[2].x
+mul r0.w, r0.w, cb0[1].z
+mov r0.z, cb0[1].w
+dp3 r0.x, r0.xyzx, cb0[0].xyzx
+mad r0.y, r0.x, r0.x, -r0.w
+sqrt r1.x, |r0.y|
+ge r0.y, r0.y, l(0.000000)
+and r0.y, r0.y, l(0x3f800000)
+mov r1.y, -r1.x
+add r0.xz, r0.xxxx, r1.xxyx
+div r0.xz, r0.xxzx, cb0[1].zzzz
+add r0.w, -r0.z, r0.x
+mul r1.xy, r0.xzxx, cb0[0].zzzz
+ge r1.xy, r1.xyxx, -cb0[1].wwww
+and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0)
+mad r0.x, r1.x, r0.w, r0.z
+max r0.z, r1.y, r1.x
+ge r0.z, l(0.000000), r0.z
+movc r0.z, r0.z, l(-0.000000), l(1.000000)
+round_pi r0.w, r0.x
+add r0.w, -r0.x, r0.w
+round_ni r1.x, r0.x
+mul r1.y, r1.x, l(0.500000)
+add r1.x, r0.x, -r1.x
+ge r1.z, r1.y, -r1.y
+frc r1.y, |r1.y|
+movc r1.y, r1.z, r1.y, -r1.y
+add r1.y, r1.y, r1.y
+mul r1.z, |r1.y|, cb0[2].z
+mad r1.y, -|r1.y|, cb0[2].z, l(1.000000)
+mul r0.w, r0.w, r1.z
+mad r0.w, r1.x, r1.y, r0.w
+mul r0.w, r0.w, cb0[2].y
+add r1.x, l(1.000000), -cb0[2].y
+mad r1.x, r0.x, r1.x, r0.w
+mov r1.y, l(0.500000)
+sample r1.xyzw, r1.xyxx, t1.xyzw, s1
+mul r1.xyz, r1.wwww, r1.xyzx
+sample r2.xyzw, v2.xyxx, t0.xyzw, s0
+mul r1.xyzw, r1.xyzw, r2.xyzw
+mul r1.xyzw, r0.zzzz, r1.xyzw
+mul o0.xyzw, r0.yyyy, r1.xyzw
+ret
+// Approximately 49 instruction slots used
+#endif
+
+const BYTE SampleRadialGradientPS[] = {
+ 68, 88, 66, 67, 221, 203, 207, 240, 164, 242, 31, 220, 34, 19, 29,
+ 61, 18, 184, 230, 185, 1, 0, 0, 0, 196, 13, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0, 136, 3, 0, 0, 232, 9, 0, 0, 100,
+ 10, 0, 0, 20, 13, 0, 0, 144, 13, 0, 0, 65, 111, 110, 57,
+ 72, 3, 0, 0, 72, 3, 0, 0, 0, 2, 255, 255, 16, 3, 0,
+ 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0,
+ 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1,
+ 1, 1, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 255, 255, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, 0,
+ 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0,
+ 0, 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 128, 191, 0,
+ 0, 0, 0, 0, 0, 0, 128, 31, 0, 0, 2, 0, 0, 0, 128,
+ 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 128, 1, 0, 15,
+ 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 31, 0,
+ 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 90, 0, 0, 4, 0,
+ 0, 1, 128, 0, 0, 228, 176, 3, 0, 228, 160, 3, 0, 170, 160,
+ 90, 0, 0, 4, 0, 0, 2, 128, 0, 0, 228, 176, 4, 0, 228,
+ 160, 4, 0, 170, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0,
+ 228, 128, 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0,
+ 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 161, 5, 0, 0, 3,
+ 0, 0, 8, 128, 0, 0, 255, 128, 1, 0, 170, 160, 1, 0, 0,
+ 2, 0, 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0,
+ 1, 128, 0, 0, 228, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0,
+ 0, 2, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 129,
+ 35, 0, 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 88, 0, 0,
+ 4, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 85, 160, 5, 0,
+ 170, 160, 7, 0, 0, 2, 0, 0, 4, 128, 0, 0, 170, 128, 6,
+ 0, 0, 2, 1, 0, 1, 128, 0, 0, 170, 128, 1, 0, 0, 2,
+ 1, 0, 6, 128, 1, 0, 0, 129, 2, 0, 0, 3, 0, 0, 13,
+ 128, 0, 0, 0, 128, 1, 0, 148, 128, 6, 0, 0, 2, 1, 0,
+ 1, 128, 1, 0, 170, 160, 5, 0, 0, 3, 0, 0, 13, 128, 0,
+ 0, 228, 128, 1, 0, 0, 128, 1, 0, 0, 2, 1, 0, 8, 128,
+ 1, 0, 255, 160, 4, 0, 0, 4, 1, 0, 7, 128, 0, 0, 248,
+ 128, 0, 0, 170, 160, 1, 0, 255, 128, 88, 0, 0, 4, 1, 0,
+ 8, 128, 1, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 128, 88,
+ 0, 0, 4, 0, 0, 13, 128, 1, 0, 148, 128, 6, 0, 68, 160,
+ 6, 0, 230, 160, 19, 0, 0, 2, 1, 0, 1, 128, 1, 0, 255,
+ 128, 2, 0, 0, 3, 1, 0, 1, 128, 1, 0, 0, 129, 1, 0,
+ 255, 128, 5, 0, 0, 3, 1, 0, 2, 128, 1, 0, 0, 128, 5,
+ 0, 0, 160, 35, 0, 0, 2, 1, 0, 2, 128, 1, 0, 85, 128,
+ 19, 0, 0, 2, 1, 0, 2, 128, 1, 0, 85, 128, 88, 0, 0,
+ 4, 1, 0, 2, 128, 1, 0, 0, 128, 1, 0, 85, 128, 1, 0,
+ 85, 129, 2, 0, 0, 3, 1, 0, 1, 128, 1, 0, 0, 129, 1,
+ 0, 255, 128, 2, 0, 0, 3, 1, 0, 2, 128, 1, 0, 85, 128,
+ 1, 0, 85, 128, 35, 0, 0, 2, 1, 0, 2, 128, 1, 0, 85,
+ 128, 5, 0, 0, 3, 1, 0, 2, 128, 1, 0, 85, 128, 2, 0,
+ 170, 160, 19, 0, 0, 2, 1, 0, 4, 128, 1, 0, 255, 129, 18,
+ 0, 0, 4, 2, 0, 8, 128, 1, 0, 85, 128, 1, 0, 170, 128,
+ 1, 0, 0, 128, 18, 0, 0, 4, 3, 0, 1, 128, 2, 0, 85,
+ 160, 2, 0, 255, 128, 1, 0, 255, 128, 1, 0, 0, 2, 3, 0,
+ 2, 128, 5, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 1,
+ 0, 228, 176, 0, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128,
+ 3, 0, 228, 128, 1, 8, 228, 160, 5, 0, 0, 3, 2, 0, 7,
+ 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0,
+ 15, 128, 1, 0, 228, 128, 2, 0, 228, 128, 2, 0, 0, 3, 0,
+ 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, 128, 88, 0, 0, 4,
+ 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, 0, 0, 170,
+ 128, 5, 0, 0, 3, 1, 0, 15, 128, 0, 0, 0, 128, 1, 0,
+ 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 85, 128, 1,
+ 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128,
+ 255, 255, 0, 0, 83, 72, 68, 82, 88, 6, 0, 0, 64, 0, 0,
+ 0, 150, 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0,
+ 0, 0, 5, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0,
+ 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0,
+ 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0,
+ 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85,
+ 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98,
+ 16, 0, 3, 50, 16, 16, 0, 2, 0, 0, 0, 101, 0, 0, 3,
+ 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0,
+ 0, 15, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 16,
+ 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 15, 0, 0, 8, 66, 0, 16, 0, 0, 0,
+ 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0,
+ 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 9, 50, 0,
+ 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 15, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 56, 0, 0, 8, 130, 0, 16, 0, 0, 0, 0,
+ 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0,
+ 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 16, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16,
+ 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 50, 0, 0, 10, 34, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0,
+ 58, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0,
+ 6, 18, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0,
+ 0, 0, 0, 0, 0, 0, 29, 0, 0, 7, 34, 0, 16, 0, 0,
+ 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 7, 34, 0, 16, 0, 0, 0, 0,
+ 0, 26, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0,
+ 128, 63, 54, 0, 0, 6, 34, 0, 16, 0, 1, 0, 0, 0, 10,
+ 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 7,
+ 82, 0, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, 0, 0, 0,
+ 0, 6, 1, 16, 0, 1, 0, 0, 0, 14, 0, 0, 8, 82, 0,
+ 16, 0, 0, 0, 0, 0, 6, 2, 16, 0, 0, 0, 0, 0, 166,
+ 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 8,
+ 130, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 56, 0,
+ 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, 134, 0, 16, 0, 0,
+ 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16,
+ 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 1, 0, 0, 10, 50, 0, 16, 0, 1,
+ 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0,
+ 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0,
+ 0, 50, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42,
+ 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, 66, 0, 16, 0,
+ 0, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16,
+ 0, 1, 0, 0, 0, 29, 0, 0, 7, 66, 0, 16, 0, 0, 0,
+ 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0,
+ 0, 0, 0, 55, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 128, 1, 64, 0, 0, 0, 0, 128, 63, 66, 0, 0, 5, 130, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 8, 130, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 65, 0, 0, 5, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 34, 0, 16, 0, 1,
+ 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 0, 63, 0, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0,
+ 0, 0, 1, 0, 0, 0, 29, 0, 0, 8, 66, 0, 16, 0, 1,
+ 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128,
+ 65, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 6, 34, 0, 16,
+ 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, 1, 0,
+ 0, 0, 55, 0, 0, 10, 34, 0, 16, 0, 1, 0, 0, 0, 42,
+ 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0,
+ 26, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 7, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0,
+ 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 56, 0, 0, 9, 66,
+ 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0, 0, 0,
+ 1, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 50, 0, 0, 11, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0,
+ 16, 128, 193, 0, 0, 0, 1, 0, 0, 0, 42, 128, 32, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63,
+ 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16,
+ 0, 0, 0, 0, 0, 42, 0, 16, 0, 1, 0, 0, 0, 50, 0,
+ 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1,
+ 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0,
+ 0, 0, 0, 0, 56, 0, 0, 8, 130, 0, 16, 0, 0, 0, 0,
+ 0, 58, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 1,
+ 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 26, 128, 32, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 50, 0, 0,
+ 9, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 1, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16,
+ 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126,
+ 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56,
+ 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0,
+ 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 69, 0, 0,
+ 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 16, 16, 0, 2, 0,
+ 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0,
+ 0, 0, 0, 56, 0, 0, 7, 242, 0, 16, 0, 1, 0, 0, 0,
+ 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0,
+ 0, 56, 0, 0, 7, 242, 0, 16, 0, 1, 0, 0, 0, 166, 10,
+ 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 56,
+ 0, 0, 7, 242, 32, 16, 0, 0, 0, 0, 0, 86, 5, 16, 0,
+ 0, 0, 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 62, 0, 0,
+ 1, 83, 84, 65, 84, 116, 0, 0, 0, 49, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 40, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 82, 68, 69, 70, 168, 2, 0, 0, 1, 0,
+ 0, 0, 16, 1, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0,
+ 4, 255, 255, 0, 1, 0, 0, 116, 2, 0, 0, 188, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 201, 0,
+ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+ 217, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0,
+ 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 13, 0,
+ 0, 0, 230, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4,
+ 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0,
+ 13, 0, 0, 0, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 1, 0, 0, 0, 73, 110, 112, 117, 116, 83, 97, 109, 112,
+ 108, 101, 114, 0, 71, 114, 97, 100, 105, 101, 110, 116, 83, 97, 109,
+ 112, 108, 101, 114, 0, 73, 110, 112, 117, 116, 84, 101, 120, 116, 117,
+ 114, 101, 0, 71, 114, 97, 100, 105, 101, 110, 116, 84, 101, 120, 116,
+ 117, 114, 101, 0, 114, 97, 100, 105, 97, 108, 71, 114, 97, 100, 105,
+ 101, 110, 116, 67, 111, 110, 115, 116, 97, 110, 116, 115, 0, 171, 171,
+ 246, 0, 0, 0, 8, 0, 0, 0, 40, 1, 0, 0, 80, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, 0, 0,
+ 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0,
+ 2, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 24, 2, 0,
+ 0, 24, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 28, 2,
+ 0, 0, 0, 0, 0, 0, 44, 2, 0, 0, 28, 0, 0, 0, 4,
+ 0, 0, 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, 0,
+ 52, 2, 0, 0, 32, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0,
+ 0, 28, 2, 0, 0, 0, 0, 0, 0, 63, 2, 0, 0, 36, 0,
+ 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 28, 2, 0, 0, 0,
+ 0, 0, 0, 78, 2, 0, 0, 40, 0, 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, 88, 2, 0,
+ 0, 48, 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 100, 2,
+ 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1,
+ 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 99, 101, 110, 116, 101, 114, 49, 0, 1, 0, 3, 0, 1, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 171, 171, 0, 0,
+ 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114,
+ 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117,
+ 115, 49, 0, 114, 101, 112, 101, 97, 116, 95, 99, 111, 114, 114, 101,
+ 99, 116, 0, 97, 108, 108, 111, 119, 95, 111, 100, 100, 0, 116, 114,
+ 97, 110, 115, 102, 111, 114, 109, 0, 171, 171, 3, 0, 3, 0, 3,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114,
+ 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32,
+ 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114,
+ 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52,
+ 0, 171, 171, 73, 83, 71, 78, 116, 0, 0, 0, 3, 0, 0, 0,
+ 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1,
+ 0, 0, 0, 15, 3, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 15, 3, 0,
+ 0, 83, 86, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 83, 67,
+ 69, 78, 69, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 84, 69,
+ 88, 67, 79, 79, 82, 68, 0, 79, 83, 71, 78, 44, 0, 0, 0,
+ 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0,
+ 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171};
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+//
+//
+// Buffer Definitions:
+//
+// cbuffer radialGradientConstants
+// {
+//
+// float3 diff; // Offset: 0 Size: 12
+// float2 center1; // Offset: 16 Size: 8
+// float A; // Offset: 24 Size: 4 [unused]
+// float radius1; // Offset: 28 Size: 4
+// float sq_radius1; // Offset: 32 Size: 4 [unused]
+// float repeat_correct; // Offset: 36 Size: 4
+// float allow_odd; // Offset: 40 Size: 4
+// float3x2 transform; // Offset: 48 Size: 28
+//
+// }
+//
+//
+// Resource Bindings:
+//
+// Name Type Format Dim Slot Elements
+// ------------------------------ ---------- ------- ----------- ---- --------
+// InputSampler sampler NA NA 0 1
+// GradientSampler sampler NA NA 1 1
+// InputTexture texture float4 2d 0 1
+// GradientTexture texture float4 2d 1 1
+// radialGradientConstants cbuffer NA NA 0 1
+//
+//
+//
+// Input signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_POSITION 0 xyzw 0 POS float
+// SCENE_POSITION 0 xyzw 1 NONE float xy
+// TEXCOORD 0 xyzw 2 NONE float xy
+//
+//
+// Output signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_Target 0 xyzw 0 TARGET float xyzw
+//
+//
+// Constant buffer to DX9 shader constant mappings:
+//
+// Target Reg Buffer Start Reg # of Regs Data Conversion
+// ---------- ------- --------- --------- ----------------------
+// c0 cb0 0 5 ( FLT, FLT, FLT, FLT)
+//
+//
+// Sampler/Resource to DX9 shader sampler mappings:
+//
+// Target Sampler Source Sampler Source Resource
+// -------------- --------------- ----------------
+// s0 s0 t0
+// s1 s1 t1
+//
+//
+// Level9 shader bytecode:
+//
+ ps_2_x
+ def c5, 0.5, -0, 1, 0
+ dcl t0
+ dcl t1
+ dcl_2d s0
+ dcl_2d s1
+ dp2add r0.x, t0, c3, c3.z
+ dp2add r0.y, t0, c4, c4.z
+ add r0.xy, r0, -c1
+ mul r0.w, c1.w, c1.w
+ dp2add r0.w, r0, r0, -r0.w
+ mul r0.w, r0.w, c5.x
+ mov r0.z, c1.w
+ dp3 r0.x, r0, c0
+ rcp r0.x, r0.x
+ mul r0.y, r0.x, r0.w
+ frc r0.z, r0.y
+ add r0.z, -r0.z, r0.y
+ mul r1.w, r0.z, c5.x
+ abs r1.x, r1.w
+ frc r1.x, r1.x
+ cmp r1.x, r0.z, r1.x, -r1.x
+ mad r0.x, r0.w, r0.x, -r0.z
+ add r0.z, r1.x, r1.x
+ abs r0.z, r0.z
+ mul r0.z, r0.z, c2.z
+ frc r0.w, -r0.y
+ lrp r1.x, r0.z, r0.w, r0.x
+ lrp r2.x, c2.y, r1.x, r0.y
+ mov r0.w, c1.w
+ mad r0.x, r0.y, -c0.z, -r0.w
+ cmp r0.x, r0.x, c5.y, c5.z
+ mov r2.y, c5.x
+ texld r1, t1, s0
+ texld r2, r2, s1
+ mul r2.xyz, r2.w, r2
+ mul r1, r1, r2
+ mul r0, r0.x, r1
+ mov oC0, r0
+
+// approximately 36 instruction slots used (2 texture, 34 arithmetic)
+ps_4_0
+dcl_constantbuffer cb0[5], immediateIndexed
+dcl_sampler s0, mode_default
+dcl_sampler s1, mode_default
+dcl_resource_texture2d (float,float,float,float) t0
+dcl_resource_texture2d (float,float,float,float) t1
+dcl_input_ps linear v1.xy
+dcl_input_ps linear v2.xy
+dcl_output o0.xyzw
+dcl_temps 3
+dp2 r0.x, v1.xyxx, cb0[3].xyxx
+add r0.x, r0.x, cb0[3].z
+dp2 r0.z, v1.xyxx, cb0[4].xyxx
+add r0.y, r0.z, cb0[4].z
+add r0.xy, r0.xyxx, -cb0[1].xyxx
+dp2 r0.w, r0.xyxx, r0.xyxx
+mad r0.w, -cb0[1].w, cb0[1].w, r0.w
+mul r0.w, r0.w, l(0.500000)
+mov r0.z, cb0[1].w
+dp3 r0.x, r0.xyzx, cb0[0].xyzx
+div r0.x, r0.w, r0.x
+round_pi r0.y, r0.x
+round_ni r0.z, r0.x
+mul r0.w, r0.z, l(0.500000)
+add r0.yz, -r0.xxzx, r0.yyxy
+ge r1.x, r0.w, -r0.w
+frc r0.w, |r0.w|
+movc r0.w, r1.x, r0.w, -r0.w
+add r0.w, r0.w, r0.w
+mul r1.x, |r0.w|, cb0[2].z
+mad r0.w, -|r0.w|, cb0[2].z, l(1.000000)
+mul r0.y, r0.y, r1.x
+mad r0.y, r0.z, r0.w, r0.y
+mul r0.y, r0.y, cb0[2].y
+add r0.z, l(1.000000), -cb0[2].y
+mad r1.x, r0.x, r0.z, r0.y
+mul r0.x, r0.x, cb0[0].z
+ge r0.x, -cb0[1].w, r0.x
+movc r0.x, r0.x, l(-0.000000), l(1.000000)
+mov r1.y, l(0.500000)
+sample r1.xyzw, r1.xyxx, t1.xyzw, s1
+mul r1.xyz, r1.wwww, r1.xyzx
+sample r2.xyzw, v2.xyxx, t0.xyzw, s0
+mul r1.xyzw, r1.xyzw, r2.xyzw
+mul o0.xyzw, r0.xxxx, r1.xyzw
+ret
+// Approximately 36 instruction slots used
+#endif
+
+const BYTE SampleRadialGradientA0PS[] = {
+ 68, 88, 66, 67, 251, 98, 227, 203, 98, 180, 0, 199, 88, 100, 39,
+ 81, 223, 130, 11, 15, 1, 0, 0, 0, 136, 11, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0, 212, 2, 0, 0, 172, 7, 0, 0, 40,
+ 8, 0, 0, 216, 10, 0, 0, 84, 11, 0, 0, 65, 111, 110, 57,
+ 148, 2, 0, 0, 148, 2, 0, 0, 0, 2, 255, 255, 92, 2, 0,
+ 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0,
+ 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1,
+ 1, 1, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 255, 255, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, 0,
+ 63, 0, 0, 0, 128, 0, 0, 128, 63, 0, 0, 0, 0, 31, 0,
+ 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0,
+ 0, 0, 128, 1, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144,
+ 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15,
+ 160, 90, 0, 0, 4, 0, 0, 1, 128, 0, 0, 228, 176, 3, 0,
+ 228, 160, 3, 0, 170, 160, 90, 0, 0, 4, 0, 0, 2, 128, 0,
+ 0, 228, 176, 4, 0, 228, 160, 4, 0, 170, 160, 2, 0, 0, 3,
+ 0, 0, 3, 128, 0, 0, 228, 128, 1, 0, 228, 161, 5, 0, 0,
+ 3, 0, 0, 8, 128, 1, 0, 255, 160, 1, 0, 255, 160, 90, 0,
+ 0, 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0, 228, 128, 0,
+ 0, 255, 129, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128,
+ 5, 0, 0, 160, 1, 0, 0, 2, 0, 0, 4, 128, 1, 0, 255,
+ 160, 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228, 128, 0, 0,
+ 228, 160, 6, 0, 0, 2, 0, 0, 1, 128, 0, 0, 0, 128, 5,
+ 0, 0, 3, 0, 0, 2, 128, 0, 0, 0, 128, 0, 0, 255, 128,
+ 19, 0, 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 2, 0, 0,
+ 3, 0, 0, 4, 128, 0, 0, 170, 129, 0, 0, 85, 128, 5, 0,
+ 0, 3, 1, 0, 8, 128, 0, 0, 170, 128, 5, 0, 0, 160, 35,
+ 0, 0, 2, 1, 0, 1, 128, 1, 0, 255, 128, 19, 0, 0, 2,
+ 1, 0, 1, 128, 1, 0, 0, 128, 88, 0, 0, 4, 1, 0, 1,
+ 128, 0, 0, 170, 128, 1, 0, 0, 128, 1, 0, 0, 129, 4, 0,
+ 0, 4, 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, 0,
+ 0, 170, 129, 2, 0, 0, 3, 0, 0, 4, 128, 1, 0, 0, 128,
+ 1, 0, 0, 128, 35, 0, 0, 2, 0, 0, 4, 128, 0, 0, 170,
+ 128, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 2, 0,
+ 170, 160, 19, 0, 0, 2, 0, 0, 8, 128, 0, 0, 85, 129, 18,
+ 0, 0, 4, 1, 0, 1, 128, 0, 0, 170, 128, 0, 0, 255, 128,
+ 0, 0, 0, 128, 18, 0, 0, 4, 2, 0, 1, 128, 2, 0, 85,
+ 160, 1, 0, 0, 128, 0, 0, 85, 128, 1, 0, 0, 2, 0, 0,
+ 8, 128, 1, 0, 255, 160, 4, 0, 0, 4, 0, 0, 1, 128, 0,
+ 0, 85, 128, 0, 0, 170, 161, 0, 0, 255, 129, 88, 0, 0, 4,
+ 0, 0, 1, 128, 0, 0, 0, 128, 5, 0, 85, 160, 5, 0, 170,
+ 160, 1, 0, 0, 2, 2, 0, 2, 128, 5, 0, 0, 160, 66, 0,
+ 0, 3, 1, 0, 15, 128, 1, 0, 228, 176, 0, 8, 228, 160, 66,
+ 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 1, 8, 228, 160,
+ 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228,
+ 128, 5, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 2, 0,
+ 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 0, 128, 1,
+ 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128,
+ 255, 255, 0, 0, 83, 72, 68, 82, 208, 4, 0, 0, 64, 0, 0,
+ 0, 52, 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0,
+ 0, 0, 5, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0,
+ 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0,
+ 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0,
+ 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85,
+ 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98,
+ 16, 0, 3, 50, 16, 16, 0, 2, 0, 0, 0, 101, 0, 0, 3,
+ 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0,
+ 0, 15, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 16,
+ 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 15, 0, 0, 8, 66, 0, 16, 0, 0, 0,
+ 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0,
+ 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32,
+ 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 9, 50, 0,
+ 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 15, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16,
+ 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 50, 0,
+ 0, 12, 130, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 58, 128, 32, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0,
+ 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 54,
+ 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 8, 18, 0, 16,
+ 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 130,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 7, 18,
+ 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 66, 0, 0, 5, 34, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 65, 0,
+ 0, 5, 66, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 63, 0, 0, 0, 8, 98, 0, 16, 0, 0, 0, 0, 0, 6, 2,
+ 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 86, 4, 16, 0, 0,
+ 0, 0, 0, 29, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0, 0,
+ 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 26, 0, 0, 6, 130, 0, 16, 0, 0, 0,
+ 0, 0, 58, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 55,
+ 0, 0, 10, 130, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0,
+ 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16,
+ 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 130, 0,
+ 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 58,
+ 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 9, 18, 0, 16, 0,
+ 1, 0, 0, 0, 58, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0,
+ 0, 42, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 50, 0,
+ 0, 11, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 128, 193,
+ 0, 0, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, 0,
+ 7, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 50, 0, 0, 9, 34,
+ 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0,
+ 58, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0,
+ 0, 56, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0,
+ 16, 0, 0, 0, 0, 0, 26, 128, 32, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0, 128, 63, 26, 128, 32, 128, 65, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 50, 0, 0, 9, 18, 0,
+ 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42,
+ 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0,
+ 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 29, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 58,
+ 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 55, 0, 0, 9, 18, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 0, 128, 1, 64, 0, 0, 0, 0, 128, 63, 54,
+ 0, 0, 5, 34, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0,
+ 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0,
+ 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0,
+ 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114,
+ 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0,
+ 70, 2, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16,
+ 0, 2, 0, 0, 0, 70, 16, 16, 0, 2, 0, 0, 0, 70, 126,
+ 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 56,
+ 0, 0, 7, 242, 0, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0,
+ 1, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 56, 0, 0,
+ 7, 242, 32, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, 0, 0,
+ 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83,
+ 84, 65, 84, 116, 0, 0, 0, 36, 0, 0, 0, 3, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 82, 68, 69, 70, 168, 2, 0, 0, 1, 0, 0, 0,
+ 16, 1, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255,
+ 255, 0, 1, 0, 0, 116, 2, 0, 0, 188, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 201, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 217, 0,
+ 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255,
+ 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0,
+ 230, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0,
+ 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 13, 0,
+ 0, 0, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 73, 110, 112, 117, 116, 83, 97, 109, 112, 108, 101,
+ 114, 0, 71, 114, 97, 100, 105, 101, 110, 116, 83, 97, 109, 112, 108,
+ 101, 114, 0, 73, 110, 112, 117, 116, 84, 101, 120, 116, 117, 114, 101,
+ 0, 71, 114, 97, 100, 105, 101, 110, 116, 84, 101, 120, 116, 117, 114,
+ 101, 0, 114, 97, 100, 105, 97, 108, 71, 114, 97, 100, 105, 101, 110,
+ 116, 67, 111, 110, 115, 116, 97, 110, 116, 115, 0, 171, 171, 246, 0,
+ 0, 0, 8, 0, 0, 0, 40, 1, 0, 0, 80, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, 0, 0, 0, 0,
+ 12, 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, 2, 0,
+ 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 24, 2, 0, 0, 24,
+ 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 28, 2, 0, 0,
+ 0, 0, 0, 0, 44, 2, 0, 0, 28, 0, 0, 0, 4, 0, 0,
+ 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, 52, 2,
+ 0, 0, 32, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 28,
+ 2, 0, 0, 0, 0, 0, 0, 63, 2, 0, 0, 36, 0, 0, 0,
+ 4, 0, 0, 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0,
+ 0, 78, 2, 0, 0, 40, 0, 0, 0, 4, 0, 0, 0, 2, 0,
+ 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, 88, 2, 0, 0, 48,
+ 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 100, 2, 0, 0,
+ 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0, 3,
+ 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 101,
+ 110, 116, 101, 114, 49, 0, 1, 0, 3, 0, 1, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 65, 0, 171, 171, 0, 0, 3, 0,
+ 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100,
+ 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49,
+ 0, 114, 101, 112, 101, 97, 116, 95, 99, 111, 114, 114, 101, 99, 116,
+ 0, 97, 108, 108, 111, 119, 95, 111, 100, 100, 0, 116, 114, 97, 110,
+ 115, 102, 111, 114, 109, 0, 171, 171, 3, 0, 3, 0, 3, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, 111, 115,
+ 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104,
+ 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54,
+ 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171,
+ 171, 73, 83, 71, 78, 116, 0, 0, 0, 3, 0, 0, 0, 8, 0,
+ 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0,
+ 0, 15, 3, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 15, 3, 0, 0, 83,
+ 86, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 83, 67, 69, 78,
+ 69, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 84, 69, 88, 67,
+ 79, 79, 82, 68, 0, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0,
+ 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
+ 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171};
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+//
+//
+// Buffer Definitions:
+//
+// cbuffer conicGradientConstants
+// {
+//
+// float2 center; // Offset: 0 Size: 8
+// float angle; // Offset: 8 Size: 4
+// float start_offset; // Offset: 12 Size: 4
+// float end_offset; // Offset: 16 Size: 4
+// float repeat_correct_conic; // Offset: 20 Size: 4 [unused]
+// float allow_odd_conic; // Offset: 24 Size: 4 [unused]
+// float3x2 transform_conic; // Offset: 32 Size: 28
+//
+// }
+//
+//
+// Resource Bindings:
+//
+// Name Type Format Dim Slot Elements
+// ------------------------------ ---------- ------- ----------- ---- --------
+// InputSampler sampler NA NA 0 1
+// GradientSampler sampler NA NA 1 1
+// InputTexture texture float4 2d 0 1
+// GradientTexture texture float4 2d 1 1
+// conicGradientConstants cbuffer NA NA 0 1
+//
+//
+//
+// Input signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_POSITION 0 xyzw 0 POS float
+// SCENE_POSITION 0 xyzw 1 NONE float xy
+// TEXCOORD 0 xyzw 2 NONE float xy
+//
+//
+// Output signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_Target 0 xyzw 0 TARGET float xyzw
+//
+//
+// Constant buffer to DX9 shader constant mappings:
+//
+// Target Reg Buffer Start Reg # of Regs Data Conversion
+// ---------- ------- --------- --------- ----------------------
+// c0 cb0 0 4 ( FLT, FLT, FLT, FLT)
+//
+//
+// Sampler/Resource to DX9 shader sampler mappings:
+//
+// Target Sampler Source Sampler Source Resource
+// -------------- --------------- ----------------
+// s0 s0 t0
+// s1 s1 t1
+//
+//
+// Level9 shader bytecode:
+//
+ ps_2_x
+ def c4, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497
+ def c5, 0.999866009, 0, 1, 3.14159274
+ def c6, -2, 1.57079637, 0.159154937, 0.5
+ dcl t0
+ dcl t1
+ dcl_2d s0
+ dcl_2d s1
+ dp2add r0.w, t0, c2, c2.z
+ add r0.x, -r0.w, c0.x
+ dp2add r0.z, t0, c3, c3.z
+ add r0.z, -r0.z, c0.y
+ abs r0.yw, r0.xxzz
+ max r1.w, r0.y, r0.w
+ rcp r1.x, r1.w
+ min r1.y, r0.w, r0.y
+ add r0.y, -r0.y, r0.w
+ cmp r0.y, r0.y, c5.y, c5.z
+ mul r0.w, r1.x, r1.y
+ mul r1.x, r0.w, r0.w
+ mad r1.y, r1.x, c4.x, c4.y
+ mad r1.y, r1.x, r1.y, c4.z
+ mad r1.y, r1.x, r1.y, c4.w
+ mad r1.x, r1.x, r1.y, c5.x
+ mul r0.w, r0.w, r1.x
+ mad r1.x, r0.w, c6.x, c6.y
+ mad r0.y, r1.x, r0.y, r0.w
+ cmp r0.w, -r0.z, -c5.y, -c5.w
+ add r0.y, r0.w, r0.y
+ add r0.w, r0.y, r0.y
+ max r1.x, r0.x, -r0.z
+ min r1.y, -r0.z, r0.x
+ cmp r0.x, r1.x, c5.z, c5.y
+ cmp r0.x, r1.y, c5.y, r0.x
+ mad r0.x, r0.x, -r0.w, r0.y
+ add r0.x, r0.x, -c0.z
+ mov r0.w, c0.w
+ add r0.y, -r0.w, c1.x
+ rcp r0.y, r0.y
+ mul r0.x, r0.x, r0.y
+ mov r0.z, c6.z
+ mad r0.x, r0.x, r0.z, c1.x
+ add r0.x, r0.x, -c0.w
+ add r0.x, r0.x, c6.w
+ abs r0.y, r0.x
+ frc r0.y, r0.y
+ cmp r0.x, r0.x, r0.y, -r0.y
+ mov r0.y, c6.w
+ texld r1, t1, s0
+ texld r0, r0, s1
+ mul r0.xyz, r0.w, r0
+ mul r0, r1, r0
+ mov oC0, r0
+
+// approximately 47 instruction slots used (2 texture, 45 arithmetic)
+ps_4_0
+dcl_constantbuffer cb0[4], immediateIndexed
+dcl_sampler s0, mode_default
+dcl_sampler s1, mode_default
+dcl_resource_texture2d (float,float,float,float) t0
+dcl_resource_texture2d (float,float,float,float) t1
+dcl_input_ps linear v1.xy
+dcl_input_ps linear v2.xy
+dcl_output o0.xyzw
+dcl_temps 2
+dp2 r0.x, v1.xyxx, cb0[2].xyxx
+add r0.x, r0.x, cb0[2].z
+dp2 r0.y, v1.xyxx, cb0[3].xyxx
+add r0.y, r0.y, cb0[3].z
+add r0.xy, -r0.xyxx, cb0[0].xyxx
+max r0.z, |r0.y|, |r0.x|
+div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z
+min r0.w, |r0.y|, |r0.x|
+mul r0.z, r0.z, r0.w
+mul r0.w, r0.z, r0.z
+mad r1.x, r0.w, l(0.020835), l(-0.085133)
+mad r1.x, r0.w, r1.x, l(0.180141)
+mad r1.x, r0.w, r1.x, l(-0.330299)
+mad r0.w, r0.w, r1.x, l(0.999866)
+mul r1.x, r0.w, r0.z
+mad r1.x, r1.x, l(-2.000000), l(1.570796)
+lt r1.y, |r0.y|, |r0.x|
+and r1.x, r1.y, r1.x
+mad r0.z, r0.z, r0.w, r1.x
+lt r0.w, -r0.y, r0.y
+and r0.w, r0.w, l(0xc0490fdb)
+add r0.z, r0.w, r0.z
+min r0.w, -r0.y, r0.x
+max r0.x, -r0.y, r0.x
+ge r0.x, r0.x, -r0.x
+lt r0.y, r0.w, -r0.w
+and r0.x, r0.x, r0.y
+movc r0.x, r0.x, -r0.z, r0.z
+add r0.x, r0.x, -cb0[0].z
+add r0.y, -cb0[0].w, cb0[1].x
+div r0.y, l(1.000000, 1.000000, 1.000000, 1.000000), r0.y
+mul r0.x, r0.x, r0.y
+mad r0.x, r0.x, l(0.159155), cb0[1].x
+add r0.x, r0.x, -cb0[0].w
+add r0.x, r0.x, l(0.500000)
+ge r0.y, r0.x, -r0.x
+frc r0.x, |r0.x|
+movc r0.x, r0.y, r0.x, -r0.x
+mov r0.y, l(0.500000)
+sample r0.xyzw, r0.xyxx, t1.xyzw, s1
+mul r0.xyz, r0.wwww, r0.xyzx
+sample r1.xyzw, v2.xyxx, t0.xyzw, s0
+mul o0.xyzw, r0.xyzw, r1.xyzw
+ret
+// Approximately 44 instruction slots used
+#endif
+
+const BYTE SampleConicGradientPS[] = {
+ 68, 88, 66, 67, 111, 210, 133, 71, 96, 114, 123, 208, 6, 154, 50,
+ 242, 194, 61, 177, 240, 1, 0, 0, 0, 184, 13, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0, 224, 3, 0, 0, 240, 9, 0, 0, 108,
+ 10, 0, 0, 8, 13, 0, 0, 132, 13, 0, 0, 65, 111, 110, 57,
+ 160, 3, 0, 0, 160, 3, 0, 0, 0, 2, 255, 255, 104, 3, 0,
+ 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0,
+ 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1,
+ 1, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 255, 255, 81, 0, 0, 5, 4, 0, 15, 160, 95, 174, 170,
+ 60, 54, 90, 174, 189, 226, 118, 56, 62, 4, 29, 169, 190, 81, 0,
+ 0, 5, 5, 0, 15, 160, 56, 247, 127, 63, 0, 0, 0, 0, 0,
+ 0, 128, 63, 219, 15, 73, 64, 81, 0, 0, 5, 6, 0, 15, 160,
+ 0, 0, 0, 192, 219, 15, 201, 63, 131, 249, 34, 62, 0, 0, 0,
+ 63, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0,
+ 0, 2, 0, 0, 0, 128, 1, 0, 15, 176, 31, 0, 0, 2, 0,
+ 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144,
+ 1, 8, 15, 160, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228,
+ 176, 2, 0, 228, 160, 2, 0, 170, 160, 2, 0, 0, 3, 0, 0,
+ 1, 128, 0, 0, 255, 129, 0, 0, 0, 160, 90, 0, 0, 4, 0,
+ 0, 4, 128, 0, 0, 228, 176, 3, 0, 228, 160, 3, 0, 170, 160,
+ 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 129, 0, 0, 85,
+ 160, 35, 0, 0, 2, 0, 0, 10, 128, 0, 0, 160, 128, 11, 0,
+ 0, 3, 1, 0, 8, 128, 0, 0, 85, 128, 0, 0, 255, 128, 6,
+ 0, 0, 2, 1, 0, 1, 128, 1, 0, 255, 128, 10, 0, 0, 3,
+ 1, 0, 2, 128, 0, 0, 255, 128, 0, 0, 85, 128, 2, 0, 0,
+ 3, 0, 0, 2, 128, 0, 0, 85, 129, 0, 0, 255, 128, 88, 0,
+ 0, 4, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 85, 160, 5,
+ 0, 170, 160, 5, 0, 0, 3, 0, 0, 8, 128, 1, 0, 0, 128,
+ 1, 0, 85, 128, 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 255,
+ 128, 0, 0, 255, 128, 4, 0, 0, 4, 1, 0, 2, 128, 1, 0,
+ 0, 128, 4, 0, 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, 1,
+ 0, 2, 128, 1, 0, 0, 128, 1, 0, 85, 128, 4, 0, 170, 160,
+ 4, 0, 0, 4, 1, 0, 2, 128, 1, 0, 0, 128, 1, 0, 85,
+ 128, 4, 0, 255, 160, 4, 0, 0, 4, 1, 0, 1, 128, 1, 0,
+ 0, 128, 1, 0, 85, 128, 5, 0, 0, 160, 5, 0, 0, 3, 0,
+ 0, 8, 128, 0, 0, 255, 128, 1, 0, 0, 128, 4, 0, 0, 4,
+ 1, 0, 1, 128, 0, 0, 255, 128, 6, 0, 0, 160, 6, 0, 85,
+ 160, 4, 0, 0, 4, 0, 0, 2, 128, 1, 0, 0, 128, 0, 0,
+ 85, 128, 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, 8, 128, 0,
+ 0, 170, 129, 5, 0, 85, 161, 5, 0, 255, 161, 2, 0, 0, 3,
+ 0, 0, 2, 128, 0, 0, 255, 128, 0, 0, 85, 128, 2, 0, 0,
+ 3, 0, 0, 8, 128, 0, 0, 85, 128, 0, 0, 85, 128, 11, 0,
+ 0, 3, 1, 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, 129, 10,
+ 0, 0, 3, 1, 0, 2, 128, 0, 0, 170, 129, 0, 0, 0, 128,
+ 88, 0, 0, 4, 0, 0, 1, 128, 1, 0, 0, 128, 5, 0, 170,
+ 160, 5, 0, 85, 160, 88, 0, 0, 4, 0, 0, 1, 128, 1, 0,
+ 85, 128, 5, 0, 85, 160, 0, 0, 0, 128, 4, 0, 0, 4, 0,
+ 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 129, 0, 0, 85, 128,
+ 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 170,
+ 161, 1, 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 160, 2, 0,
+ 0, 3, 0, 0, 2, 128, 0, 0, 255, 129, 1, 0, 0, 160, 6,
+ 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 0, 3,
+ 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 85, 128, 1, 0, 0,
+ 2, 0, 0, 4, 128, 6, 0, 170, 160, 4, 0, 0, 4, 0, 0,
+ 1, 128, 0, 0, 0, 128, 0, 0, 170, 128, 1, 0, 0, 160, 2,
+ 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 161,
+ 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 6, 0, 255,
+ 160, 35, 0, 0, 2, 0, 0, 2, 128, 0, 0, 0, 128, 19, 0,
+ 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 88, 0, 0, 4, 0,
+ 0, 1, 128, 0, 0, 0, 128, 0, 0, 85, 128, 0, 0, 85, 129,
+ 1, 0, 0, 2, 0, 0, 2, 128, 6, 0, 255, 160, 66, 0, 0,
+ 3, 1, 0, 15, 128, 1, 0, 228, 176, 0, 8, 228, 160, 66, 0,
+ 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, 228, 160, 5,
+ 0, 0, 3, 0, 0, 7, 128, 0, 0, 255, 128, 0, 0, 228, 128,
+ 5, 0, 0, 3, 0, 0, 15, 128, 1, 0, 228, 128, 0, 0, 228,
+ 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255,
+ 0, 0, 83, 72, 68, 82, 8, 6, 0, 0, 64, 0, 0, 0, 130,
+ 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0,
+ 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24,
+ 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88,
+ 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0,
+ 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0,
+ 3, 50, 16, 16, 0, 2, 0, 0, 0, 101, 0, 0, 3, 242, 32,
+ 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 15,
+ 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0,
+ 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 15, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0,
+ 70, 16, 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0,
+ 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 9, 50, 0, 16, 0,
+ 0, 0, 0, 0, 70, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0,
+ 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0,
+ 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 129,
+ 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0,
+ 0, 0, 0, 0, 14, 0, 0, 10, 66, 0, 16, 0, 0, 0, 0,
+ 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
+ 128, 63, 0, 0, 128, 63, 42, 0, 16, 0, 0, 0, 0, 0, 51,
+ 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128,
+ 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0,
+ 0, 0, 0, 0, 0, 56, 0, 0, 7, 66, 0, 16, 0, 0, 0,
+ 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0,
+ 0, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0,
+ 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 95, 174, 170, 60, 1,
+ 64, 0, 0, 54, 90, 174, 189, 50, 0, 0, 9, 18, 0, 16, 0,
+ 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 1, 0, 0, 0, 1, 64, 0, 0, 226, 118, 56, 62, 50, 0,
+ 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0,
+ 4, 29, 169, 190, 50, 0, 0, 9, 130, 0, 16, 0, 0, 0, 0,
+ 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0,
+ 0, 0, 1, 64, 0, 0, 56, 247, 127, 63, 56, 0, 0, 7, 18,
+ 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16,
+ 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64,
+ 0, 0, 0, 0, 0, 192, 1, 64, 0, 0, 219, 15, 201, 63, 49,
+ 0, 0, 9, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128,
+ 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, 0, 1, 0,
+ 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1,
+ 0, 0, 0, 50, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 1, 0, 0, 0, 49, 0, 0, 8, 130, 0,
+ 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0,
+ 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 1, 0, 0, 7,
+ 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0,
+ 0, 1, 64, 0, 0, 219, 15, 73, 192, 0, 0, 0, 7, 66, 0,
+ 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42,
+ 0, 16, 0, 0, 0, 0, 0, 51, 0, 0, 8, 130, 0, 16, 0,
+ 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 8, 18, 0,
+ 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0,
+ 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 29, 0, 0, 8,
+ 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 49, 0,
+ 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0,
+ 0, 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16,
+ 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 55, 0,
+ 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0,
+ 0, 0, 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0,
+ 42, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16,
+ 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128,
+ 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 10, 34, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128,
+ 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 128, 32,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 14, 0, 0, 10, 34, 0,
+ 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0,
+ 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 26, 0, 16, 0,
+ 0, 0, 0, 0, 56, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0,
+ 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0,
+ 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 10,
+ 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 131, 249, 34, 62,
+ 10, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0,
+ 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0,
+ 63, 29, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0,
+ 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0,
+ 0, 0, 0, 26, 0, 0, 6, 18, 0, 16, 0, 0, 0, 0, 0,
+ 10, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0,
+ 10, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0,
+ 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65,
+ 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0,
+ 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0,
+ 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0,
+ 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1,
+ 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 0, 0, 0, 0,
+ 246, 15, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0,
+ 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16,
+ 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0,
+ 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0,
+ 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 70, 14, 16,
+ 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0,
+ 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68,
+ 69, 70, 148, 2, 0, 0, 1, 0, 0, 0, 16, 1, 0, 0, 5,
+ 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0,
+ 96, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 1, 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 1, 0, 0, 0, 217, 0, 0, 0, 2, 0, 0,
+ 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0,
+ 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 230, 0, 0, 0, 2,
+ 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255,
+ 1, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 246, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 73,
+ 110, 112, 117, 116, 83, 97, 109, 112, 108, 101, 114, 0, 71, 114, 97,
+ 100, 105, 101, 110, 116, 83, 97, 109, 112, 108, 101, 114, 0, 73, 110,
+ 112, 117, 116, 84, 101, 120, 116, 117, 114, 101, 0, 71, 114, 97, 100,
+ 105, 101, 110, 116, 84, 101, 120, 116, 117, 114, 101, 0, 99, 111, 110,
+ 105, 99, 71, 114, 97, 100, 105, 101, 110, 116, 67, 111, 110, 115, 116,
+ 97, 110, 116, 115, 0, 171, 171, 171, 246, 0, 0, 0, 7, 0, 0,
+ 0, 40, 1, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 208, 1, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 2,
+ 0, 0, 0, 216, 1, 0, 0, 0, 0, 0, 0, 232, 1, 0, 0,
+ 8, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 240, 1, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 12, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 13,
+ 2, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0,
+ 240, 1, 0, 0, 0, 0, 0, 0, 24, 2, 0, 0, 20, 0, 0,
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 240, 1, 0, 0, 0, 0,
+ 0, 0, 45, 2, 0, 0, 24, 0, 0, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 61, 2, 0, 0,
+ 32, 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 80, 2, 0,
+ 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 171, 1, 0,
+ 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97,
+ 110, 103, 108, 101, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111,
+ 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101,
+ 116, 0, 114, 101, 112, 101, 97, 116, 95, 99, 111, 114, 114, 101, 99,
+ 116, 95, 99, 111, 110, 105, 99, 0, 97, 108, 108, 111, 119, 95, 111,
+ 100, 100, 95, 99, 111, 110, 105, 99, 0, 116, 114, 97, 110, 115, 102,
+ 111, 114, 109, 95, 99, 111, 110, 105, 99, 0, 171, 171, 171, 3, 0,
+ 3, 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77,
+ 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76,
+ 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105,
+ 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54,
+ 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 116, 0, 0, 0, 3,
+ 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0,
+ 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 1, 0, 0, 0, 15, 3, 0, 0, 107, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0,
+ 15, 3, 0, 0, 83, 86, 95, 80, 79, 83, 73, 84, 73, 79, 78,
+ 0, 83, 67, 69, 78, 69, 95, 80, 79, 83, 73, 84, 73, 79, 78,
+ 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 79, 83, 71, 78, 44,
+ 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
+ 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0,
+ 171, 171};
diff --git a/gfx/2d/ShadersD2D1.hlsl b/gfx/2d/ShadersD2D1.hlsl
new file mode 100644
index 0000000000..163b6b388f
--- /dev/null
+++ b/gfx/2d/ShadersD2D1.hlsl
@@ -0,0 +1,162 @@
+/* -*- 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/. */
+
+Texture2D InputTexture : register(t0);
+SamplerState InputSampler : register(s0);
+Texture2D GradientTexture : register(t1);
+SamplerState GradientSampler : register(s1);
+
+cbuffer radialGradientConstants : register(b0)
+{
+ // Precalculate as much as we can!
+ float3 diff : packoffset(c0.x);
+ float2 center1 : packoffset(c1.x);
+ float A : packoffset(c1.z);
+ float radius1 : packoffset(c1.w);
+ float sq_radius1 : packoffset(c2.x);
+
+ // The next two values are used for a hack to compensate for an apparent
+ // bug in D2D where the GradientSampler SamplerState doesn't get the
+ // correct addressing modes.
+ float repeat_correct : packoffset(c2.y);
+ float allow_odd : packoffset(c2.z);
+
+ float3x2 transform : packoffset(c3.x);
+}
+
+cbuffer conicGradientConstants : register(b0)
+{
+ float2 center : packoffset(c0.x);
+ float angle : packoffset(c0.z);
+ float start_offset : packoffset(c0.w);
+ float end_offset : packoffset(c1.x);
+
+ // The next two values are used for a hack to compensate for an apparent
+ // bug in D2D where the GradientSampler SamplerState doesn't get the
+ // correct addressing modes.
+ float repeat_correct_conic : packoffset(c1.y);
+ float allow_odd_conic : packoffset(c1.z);
+
+ float3x2 transform_conic : packoffset(c2.x);
+}
+
+
+static const float M_PI = 3.14159265f;
+
+float4 SampleConicGradientPS(
+ float4 clipSpaceOutput : SV_POSITION,
+ float4 sceneSpaceOutput : SCENE_POSITION,
+ float4 texelSpaceInput0 : TEXCOORD0
+ ) : SV_Target
+{
+ float2 p = float2(sceneSpaceOutput.x * transform_conic._11 + sceneSpaceOutput.y * transform_conic._21 + transform_conic._31,
+ sceneSpaceOutput.x * transform_conic._12 + sceneSpaceOutput.y * transform_conic._22 + transform_conic._32);
+ float2 dir = float2(
+ -(center.y - p.y),
+ (center.x - p.x));
+ float vstart = start_offset;
+ float vend = end_offset;
+ float n = 1/(vend-vstart);
+ float current_angle = atan2(dir.y, dir.x)-angle;
+ float lambda = fmod(n*current_angle/M_PI/2+vend-vstart+.5,1);
+ float offset = lambda;
+ float4 output = GradientTexture.Sample(GradientSampler, float2(offset, 0.5));
+ // Premultiply
+ output.rgb *= output.a;
+ // Multiply the output color by the input mask for the operation.
+ output *= InputTexture.Sample(InputSampler, texelSpaceInput0.xy);
+
+ return output;
+};
+
+float4 SampleRadialGradientPS(
+ float4 clipSpaceOutput : SV_POSITION,
+ float4 sceneSpaceOutput : SCENE_POSITION,
+ float4 texelSpaceInput0 : TEXCOORD0
+ ) : SV_Target
+{
+ // Radial gradient painting is defined as the set of circles whose centers
+ // are described by C(t) = (C2 - C1) * t + C1; with radii
+ // R(t) = (R2 - R1) * t + R1; for R(t) > 0. This shader solves the
+ // quadratic equation that arises when calculating t for pixel (x, y).
+ //
+ // A more extensive derrivation can be found in the pixman radial gradient
+ // code.
+
+ float2 p = float2(sceneSpaceOutput.x * transform._11 + sceneSpaceOutput.y * transform._21 + transform._31,
+ sceneSpaceOutput.x * transform._12 + sceneSpaceOutput.y * transform._22 + transform._32);
+ float3 dp = float3(p - center1, radius1);
+
+ // dpx * dcx + dpy * dcy + r * dr
+ float B = dot(dp, diff);
+
+ float C = pow(dp.x, 2) + pow(dp.y, 2) - sq_radius1;
+
+ float det = pow(B, 2) - A * C;
+
+ float sqrt_det = sqrt(abs(det));
+
+ float2 t = (B + float2(sqrt_det, -sqrt_det)) / A;
+
+ float2 isValid = step(float2(-radius1, -radius1), t * diff.z);
+
+ float upper_t = lerp(t.y, t.x, isValid.x);
+
+ // Addressing mode bug work-around.. first let's see if we should consider odd repetitions separately.
+ float oddeven = abs(fmod(floor(upper_t), 2)) * allow_odd;
+
+ // Now let's calculate even or odd addressing in a branchless manner.
+ float upper_t_repeated = ((upper_t - floor(upper_t)) * (1.0f - oddeven)) + ((ceil(upper_t) - upper_t) * oddeven);
+
+ float4 output = GradientTexture.Sample(GradientSampler, float2(upper_t * (1.0f - repeat_correct) + upper_t_repeated * repeat_correct, 0.5));
+ // Premultiply
+ output.rgb *= output.a;
+ // Multiply the output color by the input mask for the operation.
+ output *= InputTexture.Sample(InputSampler, texelSpaceInput0.xy);
+
+ // In order to compile for PS_4_0_level_9_3 we need to be branchless.
+ // This is essentially returning nothing, i.e. bailing early if:
+ // det < 0 || max(isValid.x, isValid.y) <= 0
+ return output * abs(step(max(isValid.x, isValid.y), 0) - 1.0f) * step(0, det);
+};
+
+float4 SampleRadialGradientA0PS(
+ float4 clipSpaceOutput : SV_POSITION,
+ float4 sceneSpaceOutput : SCENE_POSITION,
+ float4 texelSpaceInput0 : TEXCOORD0
+ ) : SV_Target
+{
+ // This simpler shader is used for the degenerate case where A is 0,
+ // i.e. we're actually solving a linear equation.
+
+ float2 p = float2(sceneSpaceOutput.x * transform._11 + sceneSpaceOutput.y * transform._21 + transform._31,
+ sceneSpaceOutput.x * transform._12 + sceneSpaceOutput.y * transform._22 + transform._32);
+ float3 dp = float3(p - center1, radius1);
+
+ // dpx * dcx + dpy * dcy + r * dr
+ float B = dot(dp, diff);
+
+ float C = pow(dp.x, 2) + pow(dp.y, 2) - pow(radius1, 2);
+
+ float t = 0.5 * C / B;
+
+ // Addressing mode bug work-around.. first let's see if we should consider odd repetitions separately.
+ float oddeven = abs(fmod(floor(t), 2)) * allow_odd;
+
+ // Now let's calculate even or odd addressing in a branchless manner.
+ float t_repeated = ((t - floor(t)) * (1.0f - oddeven)) + ((ceil(t) - t) * oddeven);
+
+ float4 output = GradientTexture.Sample(GradientSampler, float2(t * (1.0f - repeat_correct) + t_repeated * repeat_correct, 0.5));
+ // Premultiply
+ output.rgb *= output.a;
+ // Multiply the output color by the input mask for the operation.
+ output *= InputTexture.Sample(InputSampler, texelSpaceInput0.xy);
+
+ // In order to compile for PS_4_0_level_9_3 we need to be branchless.
+ // This is essentially returning nothing, i.e. bailing early if:
+ // -radius1 >= t * diff.z
+ return output * abs(step(t * diff.z, -radius1) - 1.0f);
+};
+
diff --git a/gfx/2d/SkConvolver.cpp b/gfx/2d/SkConvolver.cpp
new file mode 100644
index 0000000000..befe8da30b
--- /dev/null
+++ b/gfx/2d/SkConvolver.cpp
@@ -0,0 +1,559 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+// Copyright (c) 2011-2016 Google Inc.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the gfx/skia/LICENSE file.
+
+#include "SkConvolver.h"
+#include "mozilla/Vector.h"
+
+#ifdef USE_SSE2
+# include "mozilla/SSE.h"
+#endif
+
+#ifdef USE_NEON
+# include "mozilla/arm.h"
+#endif
+
+namespace skia {
+
+// Converts the argument to an 8-bit unsigned value by clamping to the range
+// 0-255.
+static inline unsigned char ClampTo8(int a) {
+ if (static_cast<unsigned>(a) < 256) {
+ return a; // Avoid the extra check in the common case.
+ }
+ if (a < 0) {
+ return 0;
+ }
+ return 255;
+}
+
+// Convolves horizontally along a single row. The row data is given in
+// |srcData| and continues for the numValues() of the filter.
+template <bool hasAlpha>
+void ConvolveHorizontally(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow) {
+ // Loop over each pixel on this row in the output image.
+ int numValues = filter.numValues();
+ for (int outX = 0; outX < numValues; outX++) {
+ // Get the filter that determines the current output pixel.
+ int filterOffset, filterLength;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filter.FilterForValue(outX, &filterOffset, &filterLength);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filterLength| pixels (4 bytes each) after this.
+ const unsigned char* rowToFilter = &srcData[filterOffset * 4];
+
+ // Apply the filter to the row to get the destination pixel in |accum|.
+ int accum[4] = {0};
+ for (int filterX = 0; filterX < filterLength; filterX++) {
+ SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX];
+ accum[0] += curFilter * rowToFilter[filterX * 4 + 0];
+ accum[1] += curFilter * rowToFilter[filterX * 4 + 1];
+ accum[2] += curFilter * rowToFilter[filterX * 4 + 2];
+ if (hasAlpha) {
+ accum[3] += curFilter * rowToFilter[filterX * 4 + 3];
+ }
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of fractional part.
+ accum[0] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[1] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[2] >>= SkConvolutionFilter1D::kShiftBits;
+
+ if (hasAlpha) {
+ accum[3] >>= SkConvolutionFilter1D::kShiftBits;
+ }
+
+ // Store the new pixel.
+ outRow[outX * 4 + 0] = ClampTo8(accum[0]);
+ outRow[outX * 4 + 1] = ClampTo8(accum[1]);
+ outRow[outX * 4 + 2] = ClampTo8(accum[2]);
+ if (hasAlpha) {
+ outRow[outX * 4 + 3] = ClampTo8(accum[3]);
+ }
+ }
+}
+
+// Does vertical convolution to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |sourceDataRows| array, with each row
+// being |pixelWidth| wide.
+//
+// The output must have room for |pixelWidth * 4| bytes.
+template <bool hasAlpha>
+void ConvolveVertically(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow) {
+ // We go through each column in the output and do a vertical convolution,
+ // generating one output pixel each time.
+ for (int outX = 0; outX < pixelWidth; outX++) {
+ // Compute the number of bytes over in each row that the current column
+ // we're convolving starts at. The pixel will cover the next 4 bytes.
+ int byteOffset = outX * 4;
+
+ // Apply the filter to one column of pixels.
+ int accum[4] = {0};
+ for (int filterY = 0; filterY < filterLength; filterY++) {
+ SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY];
+ accum[0] += curFilter * sourceDataRows[filterY][byteOffset + 0];
+ accum[1] += curFilter * sourceDataRows[filterY][byteOffset + 1];
+ accum[2] += curFilter * sourceDataRows[filterY][byteOffset + 2];
+ if (hasAlpha) {
+ accum[3] += curFilter * sourceDataRows[filterY][byteOffset + 3];
+ }
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of precision.
+ accum[0] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[1] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[2] >>= SkConvolutionFilter1D::kShiftBits;
+ if (hasAlpha) {
+ accum[3] >>= SkConvolutionFilter1D::kShiftBits;
+ }
+
+ // Store the new pixel.
+ outRow[byteOffset + 0] = ClampTo8(accum[0]);
+ outRow[byteOffset + 1] = ClampTo8(accum[1]);
+ outRow[byteOffset + 2] = ClampTo8(accum[2]);
+
+ if (hasAlpha) {
+ unsigned char alpha = ClampTo8(accum[3]);
+
+ // Make sure the alpha channel doesn't come out smaller than any of the
+ // color channels. We use premultipled alpha channels, so this should
+ // never happen, but rounding errors will cause this from time to time.
+ // These "impossible" colors will cause overflows (and hence random pixel
+ // values) when the resulting bitmap is drawn to the screen.
+ //
+ // We only need to do this when generating the final output row (here).
+ int maxColorChannel =
+ std::max(outRow[byteOffset + 0],
+ std::max(outRow[byteOffset + 1], outRow[byteOffset + 2]));
+ if (alpha < maxColorChannel) {
+ outRow[byteOffset + 3] = maxColorChannel;
+ } else {
+ outRow[byteOffset + 3] = alpha;
+ }
+ } else {
+ // No alpha channel, the image is opaque.
+ outRow[byteOffset + 3] = 0xff;
+ }
+ }
+}
+
+#ifdef USE_SSE2
+void convolve_vertically_avx2(const int16_t* filter, int filterLen,
+ uint8_t* const* srcRows, int width, uint8_t* out,
+ bool hasAlpha);
+void convolve_horizontally_sse2(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow, bool hasAlpha);
+void convolve_vertically_sse2(const int16_t* filter, int filterLen,
+ uint8_t* const* srcRows, int width, uint8_t* out,
+ bool hasAlpha);
+#elif defined(USE_NEON)
+void convolve_horizontally_neon(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow, bool hasAlpha);
+void convolve_vertically_neon(const int16_t* filter, int filterLen,
+ uint8_t* const* srcRows, int width, uint8_t* out,
+ bool hasAlpha);
+#endif
+
+void convolve_horizontally(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow, bool hasAlpha) {
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+ convolve_horizontally_sse2(srcData, filter, outRow, hasAlpha);
+ return;
+ }
+#elif defined(USE_NEON)
+ if (mozilla::supports_neon()) {
+ convolve_horizontally_neon(srcData, filter, outRow, hasAlpha);
+ return;
+ }
+#endif
+ if (hasAlpha) {
+ ConvolveHorizontally<true>(srcData, filter, outRow);
+ } else {
+ ConvolveHorizontally<false>(srcData, filter, outRow);
+ }
+}
+
+void convolve_vertically(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow, bool hasAlpha) {
+#ifdef USE_SSE2
+ if (mozilla::supports_avx2()) {
+ convolve_vertically_avx2(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow, hasAlpha);
+ return;
+ }
+ if (mozilla::supports_sse2()) {
+ convolve_vertically_sse2(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow, hasAlpha);
+ return;
+ }
+#elif defined(USE_NEON)
+ if (mozilla::supports_neon()) {
+ convolve_vertically_neon(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow, hasAlpha);
+ return;
+ }
+#endif
+ if (hasAlpha) {
+ ConvolveVertically<true>(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow);
+ } else {
+ ConvolveVertically<false>(filterValues, filterLength, sourceDataRows,
+ pixelWidth, outRow);
+ }
+}
+
+// Stores a list of rows in a circular buffer. The usage is you write into it
+// by calling AdvanceRow. It will keep track of which row in the buffer it
+// should use next, and the total number of rows added.
+class CircularRowBuffer {
+ public:
+ // The number of pixels in each row is given in |sourceRowPixelWidth|.
+ // The maximum number of rows needed in the buffer is |maxYFilterSize|
+ // (we only need to store enough rows for the biggest filter).
+ //
+ // We use the |firstInputRow| to compute the coordinates of all of the
+ // following rows returned by Advance().
+ CircularRowBuffer(int destRowPixelWidth, int maxYFilterSize,
+ int firstInputRow)
+ : fRowByteWidth(destRowPixelWidth * 4),
+ fNumRows(maxYFilterSize),
+ fNextRow(0),
+ fNextRowCoordinate(firstInputRow) {
+ fBuffer.resize(fRowByteWidth * maxYFilterSize);
+ fRowAddresses.resize(fNumRows);
+ }
+
+ // Moves to the next row in the buffer, returning a pointer to the beginning
+ // of it.
+ unsigned char* advanceRow() {
+ unsigned char* row = &fBuffer[fNextRow * fRowByteWidth];
+ fNextRowCoordinate++;
+
+ // Set the pointer to the next row to use, wrapping around if necessary.
+ fNextRow++;
+ if (fNextRow == fNumRows) {
+ fNextRow = 0;
+ }
+ return row;
+ }
+
+ // Returns a pointer to an "unrolled" array of rows. These rows will start
+ // at the y coordinate placed into |*firstRowIndex| and will continue in
+ // order for the maximum number of rows in this circular buffer.
+ //
+ // The |firstRowIndex_| may be negative. This means the circular buffer
+ // starts before the top of the image (it hasn't been filled yet).
+ unsigned char* const* GetRowAddresses(int* firstRowIndex) {
+ // Example for a 4-element circular buffer holding coords 6-9.
+ // Row 0 Coord 8
+ // Row 1 Coord 9
+ // Row 2 Coord 6 <- fNextRow = 2, fNextRowCoordinate = 10.
+ // Row 3 Coord 7
+ //
+ // The "next" row is also the first (lowest) coordinate. This computation
+ // may yield a negative value, but that's OK, the math will work out
+ // since the user of this buffer will compute the offset relative
+ // to the firstRowIndex and the negative rows will never be used.
+ *firstRowIndex = fNextRowCoordinate - fNumRows;
+
+ int curRow = fNextRow;
+ for (int i = 0; i < fNumRows; i++) {
+ fRowAddresses[i] = &fBuffer[curRow * fRowByteWidth];
+
+ // Advance to the next row, wrapping if necessary.
+ curRow++;
+ if (curRow == fNumRows) {
+ curRow = 0;
+ }
+ }
+ return &fRowAddresses[0];
+ }
+
+ private:
+ // The buffer storing the rows. They are packed, each one fRowByteWidth.
+ std::vector<unsigned char> fBuffer;
+
+ // Number of bytes per row in the |buffer|.
+ int fRowByteWidth;
+
+ // The number of rows available in the buffer.
+ int fNumRows;
+
+ // The next row index we should write into. This wraps around as the
+ // circular buffer is used.
+ int fNextRow;
+
+ // The y coordinate of the |fNextRow|. This is incremented each time a
+ // new row is appended and does not wrap.
+ int fNextRowCoordinate;
+
+ // Buffer used by GetRowAddresses().
+ std::vector<unsigned char*> fRowAddresses;
+};
+
+SkConvolutionFilter1D::SkConvolutionFilter1D() : fMaxFilter(0) {}
+
+SkConvolutionFilter1D::~SkConvolutionFilter1D() = default;
+
+void SkConvolutionFilter1D::AddFilter(int filterOffset,
+ const ConvolutionFixed* filterValues,
+ int filterLength) {
+ // It is common for leading/trailing filter values to be zeros. In such
+ // cases it is beneficial to only store the central factors.
+ // For a scaling to 1/4th in each dimension using a Lanczos-2 filter on
+ // a 1080p image this optimization gives a ~10% speed improvement.
+ int filterSize = filterLength;
+ int firstNonZero = 0;
+ while (firstNonZero < filterLength && filterValues[firstNonZero] == 0) {
+ firstNonZero++;
+ }
+
+ if (firstNonZero < filterLength) {
+ // Here we have at least one non-zero factor.
+ int lastNonZero = filterLength - 1;
+ while (lastNonZero >= 0 && filterValues[lastNonZero] == 0) {
+ lastNonZero--;
+ }
+
+ filterOffset += firstNonZero;
+ filterLength = lastNonZero + 1 - firstNonZero;
+ MOZ_ASSERT(filterLength > 0);
+
+ fFilterValues.insert(fFilterValues.end(), &filterValues[firstNonZero],
+ &filterValues[lastNonZero + 1]);
+ } else {
+ // Here all the factors were zeroes.
+ filterLength = 0;
+ }
+
+ FilterInstance instance = {
+ // We pushed filterLength elements onto fFilterValues
+ int(fFilterValues.size()) - filterLength, filterOffset, filterLength,
+ filterSize};
+ fFilters.push_back(instance);
+
+ fMaxFilter = std::max(fMaxFilter, filterLength);
+}
+
+bool SkConvolutionFilter1D::ComputeFilterValues(
+ const SkBitmapFilter& aBitmapFilter, int32_t aSrcSize, int32_t aDstSize) {
+ // When we're doing a magnification, the scale will be larger than one. This
+ // means the destination pixels are much smaller than the source pixels, and
+ // that the range covered by the filter won't necessarily cover any source
+ // pixel boundaries. Therefore, we use these clamped values (max of 1) for
+ // some computations.
+ float scale = float(aDstSize) / float(aSrcSize);
+ float clampedScale = std::min(1.0f, scale);
+ // This is how many source pixels from the center we need to count
+ // to support the filtering function.
+ float srcSupport = aBitmapFilter.width() / clampedScale;
+ float invScale = 1.0f / scale;
+
+ mozilla::Vector<float, 64> filterValues;
+ mozilla::Vector<ConvolutionFixed, 64> fixedFilterValues;
+
+ // Loop over all pixels in the output range. We will generate one set of
+ // filter values for each one. Those values will tell us how to blend the
+ // source pixels to compute the destination pixel.
+
+ // This value is computed based on how SkTDArray::resizeStorageToAtLeast works
+ // in order to ensure that it does not overflow or assert. That functions
+ // computes
+ // n+4 + (n+4)/4
+ // and we want to to fit in a 32 bit signed int. Equating that to 2^31-1 and
+ // solving n gives n = (2^31-6)*4/5 = 1717986913.6
+ const int32_t maxToPassToReserveAdditional = 1717986913;
+
+ int32_t filterValueCount = int32_t(ceilf(aDstSize * srcSupport * 2));
+ if (aDstSize > maxToPassToReserveAdditional || filterValueCount < 0 ||
+ filterValueCount > maxToPassToReserveAdditional) {
+ return false;
+ }
+ reserveAdditional(aDstSize, filterValueCount);
+ for (int32_t destI = 0; destI < aDstSize; destI++) {
+ // This is the pixel in the source directly under the pixel in the dest.
+ // Note that we base computations on the "center" of the pixels. To see
+ // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x
+ // downscale should "cover" the pixels around the pixel with *its center*
+ // at coordinates (2.5, 2.5) in the source, not those around (0, 0).
+ // Hence we need to scale coordinates (0.5, 0.5), not (0, 0).
+ float srcPixel = (static_cast<float>(destI) + 0.5f) * invScale;
+
+ // Compute the (inclusive) range of source pixels the filter covers.
+ float srcBegin = std::max(0.0f, floorf(srcPixel - srcSupport));
+ float srcEnd = std::min(aSrcSize - 1.0f, ceilf(srcPixel + srcSupport));
+
+ // Compute the unnormalized filter value at each location of the source
+ // it covers.
+
+ // Sum of the filter values for normalizing.
+ // Distance from the center of the filter, this is the filter coordinate
+ // in source space. We also need to consider the center of the pixel
+ // when comparing distance against 'srcPixel'. In the 5x downscale
+ // example used above the distance from the center of the filter to
+ // the pixel with coordinates (2, 2) should be 0, because its center
+ // is at (2.5, 2.5).
+ int32_t filterCount = int32_t(srcEnd - srcBegin) + 1;
+ if (filterCount <= 0 || !filterValues.resize(filterCount) ||
+ !fixedFilterValues.resize(filterCount)) {
+ return false;
+ }
+
+ float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale;
+ float filterSum = 0.0f;
+ for (int32_t index = 0; index < filterCount; index++) {
+ float filterValue = aBitmapFilter.evaluate(destFilterDist);
+ filterValues[index] = filterValue;
+ filterSum += filterValue;
+ destFilterDist += clampedScale;
+ }
+
+ // The filter must be normalized so that we don't affect the brightness of
+ // the image. Convert to normalized fixed point.
+ ConvolutionFixed fixedSum = 0;
+ float invFilterSum = 1.0f / filterSum;
+ for (int32_t fixedI = 0; fixedI < filterCount; fixedI++) {
+ ConvolutionFixed curFixed = ToFixed(filterValues[fixedI] * invFilterSum);
+ fixedSum += curFixed;
+ fixedFilterValues[fixedI] = curFixed;
+ }
+
+ // The conversion to fixed point will leave some rounding errors, which
+ // we add back in to avoid affecting the brightness of the image. We
+ // arbitrarily add this to the center of the filter array (this won't always
+ // be the center of the filter function since it could get clipped on the
+ // edges, but it doesn't matter enough to worry about that case).
+ ConvolutionFixed leftovers = ToFixed(1) - fixedSum;
+ fixedFilterValues[filterCount / 2] += leftovers;
+
+ AddFilter(int32_t(srcBegin), fixedFilterValues.begin(), filterCount);
+ }
+
+ return maxFilter() > 0 && numValues() == aDstSize;
+}
+
+// Does a two-dimensional convolution on the given source image.
+//
+// It is assumed the source pixel offsets referenced in the input filters
+// reference only valid pixels, so the source image size is not required. Each
+// row of the source image starts |sourceByteRowStride| after the previous
+// one (this allows you to have rows with some padding at the end).
+//
+// The result will be put into the given output buffer. The destination image
+// size will be xfilter.numValues() * yfilter.numValues() pixels. It will be
+// in rows of exactly xfilter.numValues() * 4 bytes.
+//
+// |sourceHasAlpha| is a hint that allows us to avoid doing computations on
+// the alpha channel if the image is opaque. If you don't know, set this to
+// true and it will work properly, but setting this to false will be a few
+// percent faster if you know the image is opaque.
+//
+// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order
+// (this is ARGB when loaded into 32-bit words on a little-endian machine).
+/**
+ * Returns false if it was unable to perform the convolution/rescale. in which
+ * case the output buffer is assumed to be undefined.
+ */
+bool BGRAConvolve2D(const unsigned char* sourceData, int sourceByteRowStride,
+ bool sourceHasAlpha, const SkConvolutionFilter1D& filterX,
+ const SkConvolutionFilter1D& filterY,
+ int outputByteRowStride, unsigned char* output) {
+ int maxYFilterSize = filterY.maxFilter();
+
+ // The next row in the input that we will generate a horizontally
+ // convolved row for. If the filter doesn't start at the beginning of the
+ // image (this is the case when we are only resizing a subset), then we
+ // don't want to generate any output rows before that. Compute the starting
+ // row for convolution as the first pixel for the first vertical filter.
+ int filterOffset = 0, filterLength = 0;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filterY.FilterForValue(0, &filterOffset, &filterLength);
+ int nextXRow = filterOffset;
+
+ // We loop over each row in the input doing a horizontal convolution. This
+ // will result in a horizontally convolved image. We write the results into
+ // a circular buffer of convolved rows and do vertical convolution as rows
+ // are available. This prevents us from having to store the entire
+ // intermediate image and helps cache coherency.
+ // We will need four extra rows to allow horizontal convolution could be done
+ // simultaneously. We also pad each row in row buffer to be aligned-up to
+ // 32 bytes.
+ // TODO(jiesun): We do not use aligned load from row buffer in vertical
+ // convolution pass yet. Somehow Windows does not like it.
+ int rowBufferWidth = (filterX.numValues() + 31) & ~0x1F;
+ int rowBufferHeight = maxYFilterSize;
+
+ // check for too-big allocation requests : crbug.com/528628
+ {
+ int64_t size = int64_t(rowBufferWidth) * int64_t(rowBufferHeight);
+ // need some limit, to avoid over-committing success from malloc, but then
+ // crashing when we try to actually use the memory.
+ // 100meg seems big enough to allow "normal" zoom factors and image sizes
+ // through while avoiding the crash seen by the bug (crbug.com/528628)
+ if (size > 100 * 1024 * 1024) {
+ // printf_stderr("BGRAConvolve2D: tmp allocation [%lld] too
+ // big\n", size);
+ return false;
+ }
+ }
+
+ CircularRowBuffer rowBuffer(rowBufferWidth, rowBufferHeight, filterOffset);
+
+ // Loop over every possible output row, processing just enough horizontal
+ // convolutions to run each subsequent vertical convolution.
+ MOZ_ASSERT(outputByteRowStride >= filterX.numValues() * 4);
+ int numOutputRows = filterY.numValues();
+
+ // We need to check which is the last line to convolve before we advance 4
+ // lines in one iteration.
+ int lastFilterOffset, lastFilterLength;
+ filterY.FilterForValue(numOutputRows - 1, &lastFilterOffset,
+ &lastFilterLength);
+
+ for (int outY = 0; outY < numOutputRows; outY++) {
+ filterValues = filterY.FilterForValue(outY, &filterOffset, &filterLength);
+
+ // Generate output rows until we have enough to run the current filter.
+ while (nextXRow < filterOffset + filterLength) {
+ convolve_horizontally(
+ &sourceData[(uint64_t)nextXRow * sourceByteRowStride], filterX,
+ rowBuffer.advanceRow(), sourceHasAlpha);
+ nextXRow++;
+ }
+
+ // Compute where in the output image this row of final data will go.
+ unsigned char* curOutputRow = &output[(uint64_t)outY * outputByteRowStride];
+
+ // Get the list of rows that the circular buffer has, in order.
+ int firstRowInCircularBuffer;
+ unsigned char* const* rowsToConvolve =
+ rowBuffer.GetRowAddresses(&firstRowInCircularBuffer);
+
+ // Now compute the start of the subset of those rows that the filter needs.
+ unsigned char* const* firstRowForFilter =
+ &rowsToConvolve[filterOffset - firstRowInCircularBuffer];
+
+ convolve_vertically(filterValues, filterLength, firstRowForFilter,
+ filterX.numValues(), curOutputRow, sourceHasAlpha);
+ }
+ return true;
+}
+
+} // namespace skia
diff --git a/gfx/2d/SkConvolver.h b/gfx/2d/SkConvolver.h
new file mode 100644
index 0000000000..5ea8ab9b5d
--- /dev/null
+++ b/gfx/2d/SkConvolver.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+// Copyright (c) 2011-2016 Google Inc.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the gfx/skia/LICENSE file.
+
+#ifndef MOZILLA_GFX_SKCONVOLVER_H_
+#define MOZILLA_GFX_SKCONVOLVER_H_
+
+#include "mozilla/Assertions.h"
+#include <cfloat>
+#include <cmath>
+#include <vector>
+
+namespace skia {
+
+class SkBitmapFilter {
+ public:
+ explicit SkBitmapFilter(float width) : fWidth(width) {}
+ virtual ~SkBitmapFilter() = default;
+
+ float width() const { return fWidth; }
+ virtual float evaluate(float x) const = 0;
+
+ protected:
+ float fWidth;
+};
+
+class SkBoxFilter final : public SkBitmapFilter {
+ public:
+ explicit SkBoxFilter(float width = 0.5f) : SkBitmapFilter(width) {}
+
+ float evaluate(float x) const override {
+ return (x >= -fWidth && x < fWidth) ? 1.0f : 0.0f;
+ }
+};
+
+class SkLanczosFilter final : public SkBitmapFilter {
+ public:
+ explicit SkLanczosFilter(float width = 3.0f) : SkBitmapFilter(width) {}
+
+ float evaluate(float x) const override {
+ if (x <= -fWidth || x >= fWidth) {
+ return 0.0f; // Outside of the window.
+ }
+ if (x > -FLT_EPSILON && x < FLT_EPSILON) {
+ return 1.0f; // Special case the discontinuity at the origin.
+ }
+ float xpi = x * float(M_PI);
+ return (sinf(xpi) / xpi) * // sinc(x)
+ sinf(xpi / fWidth) / (xpi / fWidth); // sinc(x/fWidth)
+ }
+};
+
+// Represents a filter in one dimension. Each output pixel has one entry in this
+// object for the filter values contributing to it. You build up the filter
+// list by calling AddFilter for each output pixel (in order).
+//
+// We do 2-dimensional convolution by first convolving each row by one
+// SkConvolutionFilter1D, then convolving each column by another one.
+//
+// Entries are stored in ConvolutionFixed point, shifted left by kShiftBits.
+class SkConvolutionFilter1D {
+ public:
+ using ConvolutionFixed = short;
+
+ // The number of bits that ConvolutionFixed point values are shifted by.
+ enum { kShiftBits = 14 };
+
+ SkConvolutionFilter1D();
+ ~SkConvolutionFilter1D();
+
+ // Convert between floating point and our ConvolutionFixed point
+ // representation.
+ static ConvolutionFixed ToFixed(float f) {
+ return static_cast<ConvolutionFixed>(f * (1 << kShiftBits));
+ }
+
+ // Returns the maximum pixel span of a filter.
+ int maxFilter() const { return fMaxFilter; }
+
+ // Returns the number of filters in this filter. This is the dimension of the
+ // output image.
+ int numValues() const { return static_cast<int>(fFilters.size()); }
+
+ void reserveAdditional(int filterCount, int filterValueCount) {
+ fFilters.reserve(fFilters.size() + filterCount);
+ fFilterValues.reserve(fFilterValues.size() + filterValueCount);
+ }
+
+ // Appends the given list of scaling values for generating a given output
+ // pixel. |filterOffset| is the distance from the edge of the image to where
+ // the scaling factors start. The scaling factors apply to the source pixels
+ // starting from this position, and going for the next |filterLength| pixels.
+ //
+ // You will probably want to make sure your input is normalized (that is,
+ // all entries in |filterValuesg| sub to one) to prevent affecting the overall
+ // brighness of the image.
+ //
+ // The filterLength must be > 0.
+ void AddFilter(int filterOffset, const ConvolutionFixed* filterValues,
+ int filterLength);
+
+ // Retrieves a filter for the given |valueOffset|, a position in the output
+ // image in the direction we're convolving. The offset and length of the
+ // filter values are put into the corresponding out arguments (see AddFilter
+ // above for what these mean), and a pointer to the first scaling factor is
+ // returned. There will be |filterLength| values in this array.
+ inline const ConvolutionFixed* FilterForValue(int valueOffset,
+ int* filterOffset,
+ int* filterLength) const {
+ const FilterInstance& filter = fFilters[valueOffset];
+ *filterOffset = filter.fOffset;
+ *filterLength = filter.fTrimmedLength;
+ if (filter.fTrimmedLength == 0) {
+ return nullptr;
+ }
+ return &fFilterValues[filter.fDataLocation];
+ }
+
+ bool ComputeFilterValues(const SkBitmapFilter& aBitmapFilter,
+ int32_t aSrcSize, int32_t aDstSize);
+
+ private:
+ struct FilterInstance {
+ // Offset within filterValues for this instance of the filter.
+ int fDataLocation;
+
+ // Distance from the left of the filter to the center. IN PIXELS
+ int fOffset;
+
+ // Number of values in this filter instance.
+ int fTrimmedLength;
+
+ // Filter length as specified. Note that this may be different from
+ // 'trimmed_length' if leading/trailing zeros of the original floating
+ // point form were clipped differently on each tail.
+ int fLength;
+ };
+
+ // Stores the information for each filter added to this class.
+ std::vector<FilterInstance> fFilters;
+
+ // We store all the filter values in this flat list, indexed by
+ // |FilterInstance.data_location| to avoid the mallocs required for storing
+ // each one separately.
+ std::vector<ConvolutionFixed> fFilterValues;
+
+ // The maximum size of any filter we've added.
+ int fMaxFilter;
+};
+
+void convolve_horizontally(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow, bool hasAlpha);
+
+void convolve_vertically(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
+ unsigned char* outRow, bool hasAlpha);
+
+bool BGRAConvolve2D(const unsigned char* sourceData, int sourceByteRowStride,
+ bool sourceHasAlpha, const SkConvolutionFilter1D& filterX,
+ const SkConvolutionFilter1D& filterY,
+ int outputByteRowStride, unsigned char* output);
+
+} // namespace skia
+
+#endif /* MOZILLA_GFX_SKCONVOLVER_H_ */
diff --git a/gfx/2d/SourceSurfaceCairo.cpp b/gfx/2d/SourceSurfaceCairo.cpp
new file mode 100644
index 0000000000..3bf380c35f
--- /dev/null
+++ b/gfx/2d/SourceSurfaceCairo.cpp
@@ -0,0 +1,129 @@
+/* -*- 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 "SourceSurfaceCairo.h"
+#include "DrawTargetCairo.h"
+#include "HelpersCairo.h"
+#include "DataSourceSurfaceWrapper.h"
+
+#include "cairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+static SurfaceFormat CairoFormatToSurfaceFormat(cairo_format_t format) {
+ switch (format) {
+ case CAIRO_FORMAT_ARGB32:
+ return SurfaceFormat::B8G8R8A8;
+ case CAIRO_FORMAT_RGB24:
+ return SurfaceFormat::B8G8R8X8;
+ case CAIRO_FORMAT_RGB16_565:
+ return SurfaceFormat::R5G6B5_UINT16;
+ case CAIRO_FORMAT_A8:
+ return SurfaceFormat::A8;
+ default:
+ return SurfaceFormat::B8G8R8A8;
+ }
+}
+
+SourceSurfaceCairo::SourceSurfaceCairo(
+ cairo_surface_t* aSurface, const IntSize& aSize,
+ const SurfaceFormat& aFormat, DrawTargetCairo* aDrawTarget /* = nullptr */)
+ : mSize(aSize),
+ mFormat(aFormat),
+ mSurface(aSurface),
+ mDrawTarget(aDrawTarget) {
+ cairo_surface_reference(mSurface);
+}
+
+SourceSurfaceCairo::~SourceSurfaceCairo() { cairo_surface_destroy(mSurface); }
+
+IntSize SourceSurfaceCairo::GetSize() const { return mSize; }
+
+SurfaceFormat SourceSurfaceCairo::GetFormat() const { return mFormat; }
+
+already_AddRefed<DataSourceSurface> SourceSurfaceCairo::GetDataSurface() {
+ RefPtr<DataSourceSurface> dataSurf;
+
+ if (cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
+ dataSurf = new DataSourceSurfaceCairo(mSurface);
+ } else {
+ cairo_surface_t* imageSurf = cairo_image_surface_create(
+ GfxFormatToCairoFormat(mFormat), mSize.width, mSize.height);
+
+ // Fill the new image surface with the contents of our surface.
+ cairo_t* ctx = cairo_create(imageSurf);
+ cairo_set_source_surface(ctx, mSurface, 0, 0);
+ cairo_paint(ctx);
+ cairo_destroy(ctx);
+
+ dataSurf = new DataSourceSurfaceCairo(imageSurf);
+ cairo_surface_destroy(imageSurf);
+ }
+
+ // We also need to make sure that the returned surface has
+ // surface->GetType() == SurfaceType::DATA.
+ return MakeAndAddRef<DataSourceSurfaceWrapper>(dataSurf);
+}
+
+cairo_surface_t* SourceSurfaceCairo::GetSurface() const { return mSurface; }
+
+void SourceSurfaceCairo::DrawTargetWillChange() {
+ if (mDrawTarget) {
+ mDrawTarget = nullptr;
+
+ // We're about to lose our version of the surface, so make a copy of it.
+ cairo_surface_t* surface = cairo_surface_create_similar(
+ mSurface, GfxFormatToCairoContent(mFormat), mSize.width, mSize.height);
+ cairo_t* ctx = cairo_create(surface);
+ cairo_pattern_t* pat = cairo_pattern_create_for_surface(mSurface);
+ cairo_set_source(ctx, pat);
+ cairo_paint(ctx);
+ cairo_destroy(ctx);
+ cairo_pattern_destroy(pat);
+
+ // Swap in this new surface.
+ cairo_surface_destroy(mSurface);
+ mSurface = surface;
+ }
+}
+
+DataSourceSurfaceCairo::DataSourceSurfaceCairo(cairo_surface_t* imageSurf)
+ : mImageSurface(imageSurf) {
+ cairo_surface_reference(mImageSurface);
+}
+
+DataSourceSurfaceCairo::~DataSourceSurfaceCairo() {
+ cairo_surface_destroy(mImageSurface);
+}
+
+unsigned char* DataSourceSurfaceCairo::GetData() {
+ return cairo_image_surface_get_data(mImageSurface);
+}
+
+int32_t DataSourceSurfaceCairo::Stride() {
+ return cairo_image_surface_get_stride(mImageSurface);
+}
+
+IntSize DataSourceSurfaceCairo::GetSize() const {
+ IntSize size;
+ size.width = cairo_image_surface_get_width(mImageSurface);
+ size.height = cairo_image_surface_get_height(mImageSurface);
+
+ return size;
+}
+
+SurfaceFormat DataSourceSurfaceCairo::GetFormat() const {
+ return CairoFormatToSurfaceFormat(
+ cairo_image_surface_get_format(mImageSurface));
+}
+
+cairo_surface_t* DataSourceSurfaceCairo::GetSurface() const {
+ return mImageSurface;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/SourceSurfaceCairo.h b/gfx/2d/SourceSurfaceCairo.h
new file mode 100644
index 0000000000..4fcdfa2de8
--- /dev/null
+++ b/gfx/2d/SourceSurfaceCairo.h
@@ -0,0 +1,71 @@
+/* -*- 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_OP_SOURCESURFACE_CAIRO_H
+#define _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H
+
+#include "2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class DrawTargetCairo;
+
+class SourceSurfaceCairo : public SourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCairo, override)
+
+ // Create a SourceSurfaceCairo. The surface will not be copied, but simply
+ // referenced.
+ // If aDrawTarget is non-nullptr, it is assumed that this is a snapshot source
+ // surface, and we'll call DrawTargetCairo::RemoveSnapshot(this) on it when
+ // we're destroyed.
+ SourceSurfaceCairo(cairo_surface_t* aSurface, const IntSize& aSize,
+ const SurfaceFormat& aFormat,
+ DrawTargetCairo* aDrawTarget = nullptr);
+ virtual ~SourceSurfaceCairo();
+
+ SurfaceType GetType() const override { return SurfaceType::CAIRO; }
+ IntSize GetSize() const override;
+ SurfaceFormat GetFormat() const override;
+ already_AddRefed<DataSourceSurface> GetDataSurface() override;
+
+ cairo_surface_t* GetSurface() const;
+
+ private: // methods
+ friend class DrawTargetCairo;
+ void DrawTargetWillChange();
+
+ private: // data
+ IntSize mSize;
+ SurfaceFormat mFormat;
+ cairo_surface_t* mSurface;
+ DrawTargetCairo* mDrawTarget;
+};
+
+class DataSourceSurfaceCairo : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceCairo, override)
+
+ explicit DataSourceSurfaceCairo(cairo_surface_t* imageSurf);
+ virtual ~DataSourceSurfaceCairo();
+ unsigned char* GetData() override;
+ int32_t Stride() override;
+
+ SurfaceType GetType() const override { return SurfaceType::CAIRO_IMAGE; }
+ IntSize GetSize() const override;
+ SurfaceFormat GetFormat() const override;
+
+ cairo_surface_t* GetSurface() const;
+
+ private:
+ cairo_surface_t* mImageSurface;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H
diff --git a/gfx/2d/SourceSurfaceD2D1.cpp b/gfx/2d/SourceSurfaceD2D1.cpp
new file mode 100644
index 0000000000..9aef7ab54e
--- /dev/null
+++ b/gfx/2d/SourceSurfaceD2D1.cpp
@@ -0,0 +1,245 @@
+/* -*- 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 "SourceSurfaceD2D1.h"
+#include "DrawTargetD2D1.h"
+
+namespace mozilla {
+namespace gfx {
+
+SourceSurfaceD2D1::SourceSurfaceD2D1(ID2D1Image* aImage,
+ ID2D1DeviceContext* aDC,
+ SurfaceFormat aFormat,
+ const IntSize& aSize, DrawTargetD2D1* aDT)
+ : mImage(aImage),
+ mDC(aDC),
+ mDevice(Factory::GetD2D1Device()),
+ mFormat(aFormat),
+ mSize(aSize),
+ mDrawTarget(aDT),
+ mOwnsCopy(false) {
+ aImage->QueryInterface((ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap));
+ if (aDT) {
+ mSnapshotLock = aDT->mSnapshotLock;
+ }
+}
+
+SourceSurfaceD2D1::~SourceSurfaceD2D1() {
+ if (mOwnsCopy) {
+ DrawTargetD2D1::mVRAMUsageSS -=
+ mSize.width * mSize.height * BytesPerPixel(mFormat);
+ }
+}
+
+bool SourceSurfaceD2D1::IsValid() const {
+ return mDevice == Factory::GetD2D1Device();
+}
+
+already_AddRefed<DataSourceSurface> SourceSurfaceD2D1::GetDataSurface() {
+ Maybe<MutexAutoLock> lock;
+ if (mSnapshotLock) {
+ lock.emplace(*mSnapshotLock);
+ }
+
+ if (!EnsureRealizedBitmap()) {
+ gfxCriticalError() << "Failed to realize a bitmap, device "
+ << hexa(mDevice);
+ return nullptr;
+ }
+
+ HRESULT hr;
+
+ RefPtr<ID2D1Bitmap1> softwareBitmap;
+ D2D1_BITMAP_PROPERTIES1 props;
+ props.dpiX = 96;
+ props.dpiY = 96;
+ props.pixelFormat = D2DPixelFormat(mFormat);
+ props.colorContext = nullptr;
+ props.bitmapOptions =
+ D2D1_BITMAP_OPTIONS_CANNOT_DRAW | D2D1_BITMAP_OPTIONS_CPU_READ;
+ hr = mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props,
+ (ID2D1Bitmap1**)getter_AddRefs(softwareBitmap));
+
+ if (FAILED(hr)) {
+ gfxCriticalError() << "Failed to create software bitmap: " << mSize
+ << " Code: " << hexa(hr);
+ return nullptr;
+ }
+
+ D2D1_POINT_2U point = D2D1::Point2U(0, 0);
+ D2D1_RECT_U rect = D2D1::RectU(0, 0, mSize.width, mSize.height);
+
+ hr = softwareBitmap->CopyFromBitmap(&point, mRealizedBitmap, &rect);
+
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to readback into software bitmap. Code: "
+ << hexa(hr);
+ return nullptr;
+ }
+
+ return MakeAndAddRef<DataSourceSurfaceD2D1>(softwareBitmap, mFormat);
+}
+
+bool SourceSurfaceD2D1::EnsureRealizedBitmap() {
+ if (mRealizedBitmap) {
+ return true;
+ }
+
+ // Why aren't we using mDevice here or anywhere else?
+ RefPtr<ID2D1Device> device = Factory::GetD2D1Device();
+ if (!device) {
+ return false;
+ }
+
+ RefPtr<ID2D1DeviceContext> dc;
+ device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
+ getter_AddRefs(dc));
+
+ D2D1_BITMAP_PROPERTIES1 props;
+ props.dpiX = 96;
+ props.dpiY = 96;
+ props.pixelFormat = D2DPixelFormat(mFormat);
+ props.colorContext = nullptr;
+ props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
+ dc->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props,
+ (ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap));
+
+ dc->SetTarget(mRealizedBitmap);
+
+ dc->BeginDraw();
+ dc->DrawImage(mImage);
+ dc->EndDraw();
+
+ return true;
+}
+
+void SourceSurfaceD2D1::DrawTargetWillChange() {
+ MOZ_ASSERT(mSnapshotLock);
+ mSnapshotLock->AssertCurrentThreadOwns();
+
+ // At this point in time this should always be true here.
+ MOZ_ASSERT(mRealizedBitmap);
+
+ RefPtr<ID2D1Bitmap1> oldBitmap = mRealizedBitmap;
+
+ D2D1_BITMAP_PROPERTIES1 props;
+ props.dpiX = 96;
+ props.dpiY = 96;
+ props.pixelFormat = D2DPixelFormat(mFormat);
+ props.colorContext = nullptr;
+ props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
+ HRESULT hr =
+ mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props,
+ (ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap));
+
+ if (FAILED(hr)) {
+ gfxCriticalError()
+ << "Failed to create bitmap to make DrawTarget copy. Size: " << mSize
+ << " Code: " << hexa(hr);
+ MarkIndependent();
+ return;
+ }
+
+ D2D1_POINT_2U point = D2D1::Point2U(0, 0);
+ D2D1_RECT_U rect = D2D1::RectU(0, 0, mSize.width, mSize.height);
+ mRealizedBitmap->CopyFromBitmap(&point, oldBitmap, &rect);
+ mImage = mRealizedBitmap;
+
+ DrawTargetD2D1::mVRAMUsageSS +=
+ mSize.width * mSize.height * BytesPerPixel(mFormat);
+ mOwnsCopy = true;
+
+ // Ensure the object stays alive for the duration of MarkIndependent.
+ RefPtr<SourceSurfaceD2D1> deathGrip = this;
+ // We now no longer depend on the source surface content remaining the same.
+ MarkIndependent();
+}
+
+void SourceSurfaceD2D1::MarkIndependent() {
+ if (mDrawTarget) {
+ MOZ_ASSERT(mDrawTarget->mSnapshot == this);
+ mDrawTarget->mSnapshot = nullptr;
+ mDrawTarget = nullptr;
+ }
+}
+
+DataSourceSurfaceD2D1::DataSourceSurfaceD2D1(ID2D1Bitmap1* aMappableBitmap,
+ SurfaceFormat aFormat)
+ : mBitmap(aMappableBitmap),
+ mFormat(aFormat),
+ mIsMapped(false),
+ mImplicitMapped(false) {}
+
+DataSourceSurfaceD2D1::~DataSourceSurfaceD2D1() {
+ if (mImplicitMapped) {
+ mBitmap->Unmap();
+ }
+}
+
+IntSize DataSourceSurfaceD2D1::GetSize() const {
+ D2D1_SIZE_F size = mBitmap->GetSize();
+
+ return IntSize(int32_t(size.width), int32_t(size.height));
+}
+
+uint8_t* DataSourceSurfaceD2D1::GetData() {
+ EnsureMapped();
+
+ return mMap.bits;
+}
+
+bool DataSourceSurfaceD2D1::Map(MapType aMapType,
+ MappedSurface* aMappedSurface) {
+ // DataSourceSurfaces used with the new Map API should not be used with
+ // GetData!!
+ MOZ_ASSERT(!mImplicitMapped);
+ MOZ_ASSERT(!mIsMapped);
+
+ if (aMapType != MapType::READ) {
+ gfxWarning() << "Attempt to map D2D1 DrawTarget for writing.";
+ return false;
+ }
+
+ D2D1_MAPPED_RECT map;
+ if (FAILED(mBitmap->Map(D2D1_MAP_OPTIONS_READ, &map))) {
+ gfxCriticalError() << "Failed to map bitmap (M).";
+ return false;
+ }
+ aMappedSurface->mData = map.bits;
+ aMappedSurface->mStride = map.pitch;
+
+ mIsMapped = !!aMappedSurface->mData;
+ return mIsMapped;
+}
+
+void DataSourceSurfaceD2D1::Unmap() {
+ MOZ_ASSERT(mIsMapped);
+
+ mIsMapped = false;
+ mBitmap->Unmap();
+}
+
+int32_t DataSourceSurfaceD2D1::Stride() {
+ EnsureMapped();
+
+ return mMap.pitch;
+}
+
+void DataSourceSurfaceD2D1::EnsureMapped() {
+ // Do not use GetData() after having used Map!
+ MOZ_ASSERT(!mIsMapped);
+ if (mImplicitMapped) {
+ return;
+ }
+ if (FAILED(mBitmap->Map(D2D1_MAP_OPTIONS_READ, &mMap))) {
+ gfxCriticalError() << "Failed to map bitmap (EM).";
+ return;
+ }
+ mImplicitMapped = true;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/SourceSurfaceD2D1.h b/gfx/2d/SourceSurfaceD2D1.h
new file mode 100644
index 0000000000..cfc6dd6b86
--- /dev/null
+++ b/gfx/2d/SourceSurfaceD2D1.h
@@ -0,0 +1,102 @@
+/* -*- 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_SOURCESURFACED2D1_H_
+#define MOZILLA_GFX_SOURCESURFACED2D1_H_
+
+#include "2D.h"
+#include "HelpersD2D.h"
+#include <vector>
+#include <d3d11.h>
+#include <d2d1_1.h>
+
+namespace mozilla {
+namespace gfx {
+
+class DrawTargetD2D1;
+
+class SourceSurfaceD2D1 : public SourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceD2D1, override)
+
+ SourceSurfaceD2D1(ID2D1Image* aImage, ID2D1DeviceContext* aDC,
+ SurfaceFormat aFormat, const IntSize& aSize,
+ DrawTargetD2D1* aDT = nullptr);
+ ~SourceSurfaceD2D1();
+
+ SurfaceType GetType() const override { return SurfaceType::D2D1_1_IMAGE; }
+ IntSize GetSize() const override { return mSize; }
+ SurfaceFormat GetFormat() const override { return mFormat; }
+ bool IsValid() const override;
+ already_AddRefed<DataSourceSurface> GetDataSurface() override;
+
+ ID2D1Image* GetImage() { return mImage; }
+
+ void EnsureIndependent() {
+ if (!mDrawTarget) return;
+ DrawTargetWillChange();
+ }
+
+ private:
+ friend class DrawTargetD2D1;
+
+ bool EnsureRealizedBitmap();
+
+ // This function is called by the draw target this texture belongs to when
+ // it is about to be changed. The texture will be required to make a copy
+ // of itself when this happens.
+ void DrawTargetWillChange();
+
+ // This will mark the surface as no longer depending on its drawtarget,
+ // this may happen on destruction or copying.
+ void MarkIndependent();
+
+ RefPtr<ID2D1Image> mImage;
+ // This may be null if we were created for a non-bitmap image and have not
+ // had a reason yet to realize ourselves.
+ RefPtr<ID2D1Bitmap1> mRealizedBitmap;
+ RefPtr<ID2D1DeviceContext> mDC;
+ // Keep this around to verify whether out image is still valid in the future.
+ RefPtr<ID2D1Device> mDevice;
+
+ const SurfaceFormat mFormat;
+ const IntSize mSize;
+ DrawTargetD2D1* mDrawTarget;
+ std::shared_ptr<Mutex> mSnapshotLock;
+ bool mOwnsCopy;
+};
+
+class DataSourceSurfaceD2D1 : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceD2D1, override)
+
+ DataSourceSurfaceD2D1(ID2D1Bitmap1* aMappableBitmap, SurfaceFormat aFormat);
+ ~DataSourceSurfaceD2D1();
+
+ SurfaceType GetType() const override { return SurfaceType::DATA; }
+ IntSize GetSize() const override;
+ SurfaceFormat GetFormat() const override { return mFormat; }
+ bool IsValid() const override { return !!mBitmap; }
+ uint8_t* GetData() override;
+ int32_t Stride() override;
+ bool Map(MapType, MappedSurface* aMappedSurface) override;
+ void Unmap() override;
+
+ private:
+ friend class SourceSurfaceD2DTarget;
+ void EnsureMapped();
+
+ mutable RefPtr<ID2D1Bitmap1> mBitmap;
+ SurfaceFormat mFormat;
+ D2D1_MAPPED_RECT mMap;
+ bool mIsMapped;
+ bool mImplicitMapped;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SOURCESURFACED2D2TARGET_H_ */
diff --git a/gfx/2d/SourceSurfaceRawData.cpp b/gfx/2d/SourceSurfaceRawData.cpp
new file mode 100644
index 0000000000..d5590e329c
--- /dev/null
+++ b/gfx/2d/SourceSurfaceRawData.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "SourceSurfaceRawData.h"
+
+#include "DataSurfaceHelpers.h"
+#include "Logging.h"
+#include "mozilla/Types.h" // for decltype
+
+namespace mozilla {
+namespace gfx {
+
+void SourceSurfaceRawData::InitWrappingData(
+ uint8_t* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat, Factory::SourceSurfaceDeallocator aDeallocator,
+ void* aClosure) {
+ mRawData = aData;
+ mSize = aSize;
+ mStride = aStride;
+ mFormat = aFormat;
+ mDeallocator = aDeallocator;
+ mClosure = aClosure;
+}
+
+void SourceSurfaceRawData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ SizeOfInfo& aInfo) const {
+ aInfo.AddType(SurfaceType::DATA);
+ if (mDeallocator) {
+ aInfo.mUnknownBytes = mStride * mSize.height;
+ }
+}
+
+bool SourceSurfaceAlignedRawData::Init(const IntSize& aSize,
+ SurfaceFormat aFormat, bool aClearMem,
+ uint8_t aClearValue, int32_t aStride) {
+ mFormat = aFormat;
+ mStride = aStride ? aStride
+ : GetAlignedStride<16>(aSize.width, BytesPerPixel(aFormat));
+
+ size_t bufLen = BufferSizeFromStrideAndHeight(mStride, aSize.height);
+ if (bufLen > 0) {
+ bool zeroMem = aClearMem && !aClearValue;
+ static_assert(sizeof(decltype(mArray[0])) == 1,
+ "mArray.Realloc() takes an object count, so its objects must "
+ "be 1-byte sized if we use bufLen");
+
+ // AlignedArray uses cmalloc to zero mem for a fast path.
+ mArray.Realloc(/* actually an object count */ bufLen, zeroMem);
+ mSize = aSize;
+
+ if (mArray && aClearMem && aClearValue) {
+ memset(mArray, aClearValue, mStride * aSize.height);
+ }
+ } else {
+ mArray.Dealloc();
+ mSize.SizeTo(0, 0);
+ }
+
+ return mArray != nullptr;
+}
+
+void SourceSurfaceAlignedRawData::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf, SizeOfInfo& aInfo) const {
+ aInfo.AddType(SurfaceType::DATA_ALIGNED);
+ aInfo.mHeapBytes = mArray.HeapSizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/SourceSurfaceRawData.h b/gfx/2d/SourceSurfaceRawData.h
new file mode 100644
index 0000000000..904f0fa1c8
--- /dev/null
+++ b/gfx/2d/SourceSurfaceRawData.h
@@ -0,0 +1,129 @@
+/* -*- 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_SOURCESURFACERAWDATA_H_
+#define MOZILLA_GFX_SOURCESURFACERAWDATA_H_
+
+#include "2D.h"
+#include "Tools.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla {
+namespace gfx {
+
+class SourceSurfaceMappedData final : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceMappedData, final)
+
+ SourceSurfaceMappedData(ScopedMap&& aMap, const IntSize& aSize,
+ SurfaceFormat aFormat)
+ : mMap(std::move(aMap)), mSize(aSize), mFormat(aFormat) {}
+
+ ~SourceSurfaceMappedData() final = default;
+
+ uint8_t* GetData() final { return mMap.GetData(); }
+ int32_t Stride() final { return mMap.GetStride(); }
+
+ SurfaceType GetType() const final { return SurfaceType::DATA_MAPPED; }
+ IntSize GetSize() const final { return mSize; }
+ SurfaceFormat GetFormat() const final { return mFormat; }
+
+ void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ SizeOfInfo& aInfo) const override {
+ aInfo.AddType(SurfaceType::DATA_MAPPED);
+ mMap.GetSurface()->SizeOfExcludingThis(aMallocSizeOf, aInfo);
+ }
+
+ const DataSourceSurface* GetScopedSurface() const {
+ return mMap.GetSurface();
+ }
+
+ private:
+ ScopedMap mMap;
+ IntSize mSize;
+ SurfaceFormat mFormat;
+};
+
+class SourceSurfaceRawData : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceRawData, override)
+
+ SourceSurfaceRawData()
+ : mRawData(0),
+ mStride(0),
+ mFormat(SurfaceFormat::UNKNOWN),
+ mDeallocator(nullptr),
+ mClosure(nullptr) {}
+
+ virtual ~SourceSurfaceRawData() {
+ if (mDeallocator) {
+ mDeallocator(mClosure);
+ }
+ }
+
+ virtual uint8_t* GetData() override { return mRawData; }
+ virtual int32_t Stride() override { return mStride; }
+
+ virtual SurfaceType GetType() const override { return SurfaceType::DATA; }
+ virtual IntSize GetSize() const override { return mSize; }
+ virtual SurfaceFormat GetFormat() const override { return mFormat; }
+
+ void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ SizeOfInfo& aInfo) const override;
+
+ private:
+ friend class Factory;
+
+ // If we have a custom deallocator, the |aData| will be released using the
+ // custom deallocator and |aClosure| in dtor. The assumption is that the
+ // caller will check for valid size and stride before making this call.
+ void InitWrappingData(unsigned char* aData, const IntSize& aSize,
+ int32_t aStride, SurfaceFormat aFormat,
+ Factory::SourceSurfaceDeallocator aDeallocator,
+ void* aClosure);
+
+ uint8_t* mRawData;
+ int32_t mStride;
+ SurfaceFormat mFormat;
+ IntSize mSize;
+
+ Factory::SourceSurfaceDeallocator mDeallocator;
+ void* mClosure;
+};
+
+class SourceSurfaceAlignedRawData : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceAlignedRawData,
+ override)
+ SourceSurfaceAlignedRawData() : mStride(0), mFormat(SurfaceFormat::UNKNOWN) {}
+ ~SourceSurfaceAlignedRawData() override = default;
+
+ bool Init(const IntSize& aSize, SurfaceFormat aFormat, bool aClearMem,
+ uint8_t aClearValue, int32_t aStride = 0);
+
+ uint8_t* GetData() override { return mArray; }
+ int32_t Stride() override { return mStride; }
+
+ SurfaceType GetType() const override { return SurfaceType::DATA_ALIGNED; }
+ IntSize GetSize() const override { return mSize; }
+ SurfaceFormat GetFormat() const override { return mFormat; }
+
+ void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ SizeOfInfo& aInfo) const override;
+
+ private:
+ friend class Factory;
+
+ AlignedArray<uint8_t> mArray;
+ int32_t mStride;
+ SurfaceFormat mFormat;
+ IntSize mSize;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SOURCESURFACERAWDATA_H_ */
diff --git a/gfx/2d/SourceSurfaceSkia.cpp b/gfx/2d/SourceSurfaceSkia.cpp
new file mode 100644
index 0000000000..21f15f62e6
--- /dev/null
+++ b/gfx/2d/SourceSurfaceSkia.cpp
@@ -0,0 +1,228 @@
+/* -*- 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 "Logging.h"
+#include "SourceSurfaceSkia.h"
+#include "HelpersSkia.h"
+#include "DrawTargetSkia.h"
+#include "skia/include/core/SkData.h"
+#include "skia/include/core/SkImage.h"
+#include "skia/include/core/SkSurface.h"
+#include "skia/include/private/base/SkMalloc.h"
+#include "mozilla/CheckedInt.h"
+
+namespace mozilla::gfx {
+
+SourceSurfaceSkia::SourceSurfaceSkia()
+ : mFormat(SurfaceFormat::UNKNOWN),
+ mStride(0),
+ mDrawTarget(nullptr),
+ mChangeMutex("SourceSurfaceSkia::mChangeMutex"),
+ mIsMapped(false) {}
+
+SourceSurfaceSkia::~SourceSurfaceSkia() {
+ // if mIsMapped is true then mChangeMutex will be locked
+ // which will cause problems during destruction.
+ MOZ_RELEASE_ASSERT(!mIsMapped);
+}
+
+IntSize SourceSurfaceSkia::GetSize() const { return mSize; }
+
+SurfaceFormat SourceSurfaceSkia::GetFormat() const { return mFormat; }
+
+// This is only ever called by the DT destructor, which can only ever happen
+// from one place at a time. Therefore it doesn't need to hold the ChangeMutex
+// as mSurface is never read to directly and is just there to keep the object
+// alive, which itself is refcounted in a thread-safe manner.
+void SourceSurfaceSkia::GiveSurface(SkSurface* aSurface) {
+ mSurface.reset(aSurface);
+ mDrawTarget = nullptr;
+}
+
+sk_sp<SkImage> SourceSurfaceSkia::GetImage(Maybe<MutexAutoLock>* aLock) {
+ // If we were provided a lock object, we can let the caller access
+ // a shared SkImage and we know it won't go away while the lock is held.
+ // Otherwise we need to call DrawTargetWillChange to ensure we have our
+ // own SkImage.
+ if (aLock) {
+ MOZ_ASSERT(aLock->isNothing());
+ aLock->emplace(mChangeMutex);
+
+ // Now that we are locked, we can check mDrawTarget. If it's null, then
+ // we're not shared and we can unlock eagerly.
+ if (!mDrawTarget) {
+ aLock->reset();
+ }
+ } else {
+ DrawTargetWillChange();
+ }
+ sk_sp<SkImage> image = mImage;
+ return image;
+}
+
+static sk_sp<SkData> MakeSkData(void* aData, int32_t aHeight, size_t aStride) {
+ CheckedInt<size_t> size = aStride;
+ size *= aHeight;
+ if (size.isValid()) {
+ void* mem = sk_malloc_flags(size.value(), 0);
+ if (mem) {
+ if (aData) {
+ memcpy(mem, aData, size.value());
+ }
+ return SkData::MakeFromMalloc(mem, size.value());
+ }
+ }
+ return nullptr;
+}
+
+static sk_sp<SkImage> ReadSkImage(const sk_sp<SkImage>& aImage,
+ const SkImageInfo& aInfo, size_t aStride,
+ int aX = 0, int aY = 0) {
+ if (sk_sp<SkData> data = MakeSkData(nullptr, aInfo.height(), aStride)) {
+ if (aImage->readPixels(aInfo, data->writable_data(), aStride, aX, aY,
+ SkImage::kDisallow_CachingHint)) {
+ return SkImage::MakeRasterData(aInfo, data, aStride);
+ }
+ }
+ return nullptr;
+}
+
+bool SourceSurfaceSkia::InitFromData(unsigned char* aData, const IntSize& aSize,
+ int32_t aStride, SurfaceFormat aFormat) {
+ sk_sp<SkData> data = MakeSkData(aData, aSize.height, aStride);
+ if (!data) {
+ return false;
+ }
+
+ SkImageInfo info = MakeSkiaImageInfo(aSize, aFormat);
+ mImage = SkImage::MakeRasterData(info, data, aStride);
+ if (!mImage) {
+ return false;
+ }
+
+ mSize = aSize;
+ mFormat = aFormat;
+ mStride = aStride;
+ return true;
+}
+
+bool SourceSurfaceSkia::InitFromImage(const sk_sp<SkImage>& aImage,
+ SurfaceFormat aFormat,
+ DrawTargetSkia* aOwner) {
+ if (!aImage) {
+ return false;
+ }
+
+ mSize = IntSize(aImage->width(), aImage->height());
+
+ // For the raster image case, we want to use the format and stride
+ // information that the underlying raster image is using, which is
+ // reliable.
+ // For the GPU case (for which peekPixels is false), we can't easily
+ // figure this information out. It is better to report the originally
+ // intended format and stride that we will convert to if this GPU
+ // image is ever read back into a raster image.
+ SkPixmap pixmap;
+ if (aImage->peekPixels(&pixmap)) {
+ mFormat =
+ aFormat != SurfaceFormat::UNKNOWN
+ ? aFormat
+ : SkiaColorTypeToGfxFormat(pixmap.colorType(), pixmap.alphaType());
+ mStride = pixmap.rowBytes();
+ } else if (aFormat != SurfaceFormat::UNKNOWN) {
+ mFormat = aFormat;
+ SkImageInfo info = MakeSkiaImageInfo(mSize, mFormat);
+ mStride = GetAlignedStride<4>(info.width(), info.bytesPerPixel());
+ if (!mStride) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ mImage = aImage;
+
+ if (aOwner) {
+ mDrawTarget = aOwner;
+ }
+
+ return true;
+}
+
+already_AddRefed<SourceSurface> SourceSurfaceSkia::ExtractSubrect(
+ const IntRect& aRect) {
+ if (!mImage || aRect.IsEmpty() || !GetRect().Contains(aRect)) {
+ return nullptr;
+ }
+ SkImageInfo info = MakeSkiaImageInfo(aRect.Size(), mFormat);
+ size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel());
+ if (!stride) {
+ return nullptr;
+ }
+ sk_sp<SkImage> subImage = ReadSkImage(mImage, info, stride, aRect.x, aRect.y);
+ if (!subImage) {
+ return nullptr;
+ }
+ RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia;
+ if (!surface->InitFromImage(subImage)) {
+ return nullptr;
+ }
+ return surface.forget().downcast<SourceSurface>();
+}
+
+uint8_t* SourceSurfaceSkia::GetData() {
+ if (!mImage) {
+ return nullptr;
+ }
+ SkPixmap pixmap;
+ if (!mImage->peekPixels(&pixmap)) {
+ gfxCriticalError() << "Failed accessing pixels for Skia raster image";
+ }
+ return reinterpret_cast<uint8_t*>(pixmap.writable_addr());
+}
+
+bool SourceSurfaceSkia::Map(MapType, MappedSurface* aMappedSurface)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mChangeMutex.Lock();
+ aMappedSurface->mData = GetData();
+ aMappedSurface->mStride = Stride();
+ mIsMapped = !!aMappedSurface->mData;
+ bool isMapped = mIsMapped;
+ if (!mIsMapped) {
+ mChangeMutex.Unlock();
+ }
+ // Static analysis will warn due to a conditional Unlock
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ return isMapped;
+ MOZ_POP_THREAD_SAFETY
+}
+
+void SourceSurfaceSkia::Unmap() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mChangeMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mIsMapped);
+ mIsMapped = false;
+ mChangeMutex.Unlock();
+}
+
+void SourceSurfaceSkia::DrawTargetWillChange() {
+ MutexAutoLock lock(mChangeMutex);
+ if (mDrawTarget.exchange(nullptr)) {
+ // Raster snapshots do not use Skia's internal copy-on-write mechanism,
+ // so we need to do an explicit copy here.
+ // GPU snapshots, for which peekPixels is false, will already be dealt
+ // with automatically via the internal copy-on-write mechanism, so we
+ // don't need to do anything for them here.
+ SkPixmap pixmap;
+ if (mImage->peekPixels(&pixmap)) {
+ mImage = ReadSkImage(mImage, pixmap.info(), pixmap.rowBytes());
+ if (!mImage) {
+ gfxCriticalError() << "Failed copying Skia raster snapshot";
+ }
+ }
+ }
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/SourceSurfaceSkia.h b/gfx/2d/SourceSurfaceSkia.h
new file mode 100644
index 0000000000..e0f085d2d4
--- /dev/null
+++ b/gfx/2d/SourceSurfaceSkia.h
@@ -0,0 +1,78 @@
+/* -*- 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_SOURCESURFACESKIA_H_
+#define MOZILLA_GFX_SOURCESURFACESKIA_H_
+
+#include "2D.h"
+#include "mozilla/Mutex.h"
+#include "skia/include/core/SkRefCnt.h"
+
+class SkImage;
+class SkSurface;
+
+namespace mozilla {
+
+namespace gfx {
+
+class DrawTargetSkia;
+class SnapshotLock;
+
+class SourceSurfaceSkia : public DataSourceSurface {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceSkia, override)
+
+ SourceSurfaceSkia();
+ virtual ~SourceSurfaceSkia();
+
+ SurfaceType GetType() const override { return SurfaceType::SKIA; }
+ IntSize GetSize() const override;
+ SurfaceFormat GetFormat() const override;
+
+ void GiveSurface(SkSurface* aSurface);
+
+ sk_sp<SkImage> GetImage(Maybe<MutexAutoLock>* aLock);
+
+ bool InitFromData(unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat);
+
+ bool InitFromImage(const sk_sp<SkImage>& aImage,
+ SurfaceFormat aFormat = SurfaceFormat::UNKNOWN,
+ DrawTargetSkia* aOwner = nullptr);
+
+ already_AddRefed<SourceSurface> ExtractSubrect(const IntRect& aRect) override;
+
+ uint8_t* GetData() override;
+
+ /**
+ * The caller is responsible for ensuring aMappedSurface is not null.
+ */
+ bool Map(MapType, MappedSurface* aMappedSurface) override;
+
+ void Unmap() override;
+
+ int32_t Stride() override { return mStride; }
+
+ private:
+ friend class DrawTargetSkia;
+
+ void DrawTargetWillChange();
+
+ sk_sp<SkImage> mImage;
+ // This keeps a surface alive if needed because its DrawTarget has gone away.
+ sk_sp<SkSurface> mSurface;
+ SurfaceFormat mFormat;
+ IntSize mSize;
+ int32_t mStride;
+ Atomic<DrawTargetSkia*> mDrawTarget;
+ Mutex mChangeMutex MOZ_UNANNOTATED;
+ bool mIsMapped;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SOURCESURFACESKIA_H_ */
diff --git a/gfx/2d/StackArray.h b/gfx/2d/StackArray.h
new file mode 100644
index 0000000000..165d45cb12
--- /dev/null
+++ b/gfx/2d/StackArray.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+/* A handy class that will allocate data for size*T objects on the stack and
+ * otherwise allocate them on the heap. It is similar in purpose to AutoTArray
+ */
+
+template <class T, size_t size>
+class StackArray final {
+ public:
+ explicit StackArray(size_t count) {
+ if (count > size) {
+ mData = new T[count];
+ } else {
+ mData = mStackData;
+ }
+ }
+ ~StackArray() {
+ if (mData != mStackData) {
+ delete[] mData;
+ }
+ }
+ T& operator[](size_t n) { return mData[n]; }
+ const T& operator[](size_t n) const { return mData[n]; }
+ T* data() { return mData; };
+
+ private:
+ T mStackData[size];
+ T* mData;
+};
diff --git a/gfx/2d/Swizzle.cpp b/gfx/2d/Swizzle.cpp
new file mode 100644
index 0000000000..03647348f3
--- /dev/null
+++ b/gfx/2d/Swizzle.cpp
@@ -0,0 +1,1574 @@
+/* -*- 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 "Swizzle.h"
+#include "Logging.h"
+#include "Orientation.h"
+#include "Tools.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/UniquePtr.h"
+
+#ifdef USE_SSE2
+# include "mozilla/SSE.h"
+#endif
+
+#ifdef USE_NEON
+# include "mozilla/arm.h"
+#endif
+
+#include <new>
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Convenience macros for dispatching to various format combinations.
+ */
+
+// Hash the formats to a relatively dense value to optimize jump table
+// generation. The first 6 formats in SurfaceFormat are the 32-bit BGRA variants
+// and are the most common formats dispatched here. Room is reserved in the
+// lowish bits for up to these 6 destination formats. If a destination format is
+// >= 6, the 6th bit is set to avoid collisions.
+#define FORMAT_KEY(aSrcFormat, aDstFormat) \
+ (int(aSrcFormat) * 6 + int(aDstFormat) + (int(int(aDstFormat) >= 6) << 6))
+
+#define FORMAT_CASE_EXPR(aSrcFormat, aDstFormat, ...) \
+ case FORMAT_KEY(aSrcFormat, aDstFormat): \
+ __VA_ARGS__; \
+ return true;
+
+#define FORMAT_CASE(aSrcFormat, aDstFormat, ...) \
+ FORMAT_CASE_EXPR(aSrcFormat, aDstFormat, FORMAT_CASE_CALL(__VA_ARGS__))
+
+#define FORMAT_CASE_ROW(aSrcFormat, aDstFormat, ...) \
+ case FORMAT_KEY(aSrcFormat, aDstFormat): \
+ return &__VA_ARGS__;
+
+/**
+ * Constexpr functions for analyzing format attributes in templates.
+ */
+
+// Whether B comes before R in pixel memory layout.
+static constexpr bool IsBGRFormat(SurfaceFormat aFormat) {
+ return aFormat == SurfaceFormat::B8G8R8A8 ||
+#if MOZ_LITTLE_ENDIAN()
+ aFormat == SurfaceFormat::R5G6B5_UINT16 ||
+#endif
+ aFormat == SurfaceFormat::B8G8R8X8 || aFormat == SurfaceFormat::B8G8R8;
+}
+
+// Whether the order of B and R need to be swapped to map from src to dst.
+static constexpr bool ShouldSwapRB(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat) {
+ return IsBGRFormat(aSrcFormat) != IsBGRFormat(aDstFormat);
+}
+
+// The starting byte of the RGB components in pixel memory.
+static constexpr uint32_t RGBByteIndex(SurfaceFormat aFormat) {
+ return aFormat == SurfaceFormat::A8R8G8B8 ||
+ aFormat == SurfaceFormat::X8R8G8B8
+ ? 1
+ : 0;
+}
+
+// The byte of the alpha component, which just comes after RGB.
+static constexpr uint32_t AlphaByteIndex(SurfaceFormat aFormat) {
+ return (RGBByteIndex(aFormat) + 3) % 4;
+}
+
+// The endian-dependent bit shift to access RGB of a UINT32 pixel.
+static constexpr uint32_t RGBBitShift(SurfaceFormat aFormat) {
+#if MOZ_LITTLE_ENDIAN()
+ return 8 * RGBByteIndex(aFormat);
+#else
+ return 8 - 8 * RGBByteIndex(aFormat);
+#endif
+}
+
+// The endian-dependent bit shift to access alpha of a UINT32 pixel.
+static constexpr uint32_t AlphaBitShift(SurfaceFormat aFormat) {
+ return (RGBBitShift(aFormat) + 24) % 32;
+}
+
+// Whether the pixel format should ignore the value of the alpha channel and
+// treat it as opaque.
+static constexpr bool IgnoreAlpha(SurfaceFormat aFormat) {
+ return aFormat == SurfaceFormat::B8G8R8X8 ||
+ aFormat == SurfaceFormat::R8G8B8X8 ||
+ aFormat == SurfaceFormat::X8R8G8B8;
+}
+
+// Whether to force alpha to opaque to map from src to dst.
+static constexpr bool ShouldForceOpaque(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat) {
+ return IgnoreAlpha(aSrcFormat) != IgnoreAlpha(aDstFormat);
+}
+
+#ifdef USE_SSE2
+/**
+ * SSE2 optimizations
+ */
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Premultiply_SSE2(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize);
+
+# define PREMULTIPLY_SSE2(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ Premultiply_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void PremultiplyRow_SSE2(const uint8_t*, uint8_t*, int32_t);
+
+# define PREMULTIPLY_ROW_SSE2(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ PremultiplyRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB>
+void Unpremultiply_SSE2(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize);
+
+# define UNPREMULTIPLY_SSE2(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ Unpremultiply_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB>
+void UnpremultiplyRow_SSE2(const uint8_t*, uint8_t*, int32_t);
+
+# define UNPREMULTIPLY_ROW_SSE2(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ UnpremultiplyRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Swizzle_SSE2(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize);
+
+# define SWIZZLE_SSE2(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ Swizzle_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void SwizzleRow_SSE2(const uint8_t*, uint8_t*, int32_t);
+
+# define SWIZZLE_ROW_SSE2(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ SwizzleRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB>
+void UnpackRowRGB24_SSSE3(const uint8_t*, uint8_t*, int32_t);
+
+# define UNPACK_ROW_RGB_SSSE3(aDstFormat) \
+ FORMAT_CASE_ROW( \
+ SurfaceFormat::R8G8B8, aDstFormat, \
+ UnpackRowRGB24_SSSE3<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+
+template <bool aSwapRB>
+void UnpackRowRGB24_AVX2(const uint8_t*, uint8_t*, int32_t);
+
+# define UNPACK_ROW_RGB_AVX2(aDstFormat) \
+ FORMAT_CASE_ROW( \
+ SurfaceFormat::R8G8B8, aDstFormat, \
+ UnpackRowRGB24_AVX2<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+
+#endif
+
+#ifdef USE_NEON
+/**
+ * ARM NEON optimizations
+ */
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Premultiply_NEON(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize);
+
+# define PREMULTIPLY_NEON(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ Premultiply_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void PremultiplyRow_NEON(const uint8_t*, uint8_t*, int32_t);
+
+# define PREMULTIPLY_ROW_NEON(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ PremultiplyRow_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB>
+void Unpremultiply_NEON(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize);
+
+# define UNPREMULTIPLY_NEON(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ Unpremultiply_NEON<ShouldSwapRB(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB>
+void UnpremultiplyRow_NEON(const uint8_t*, uint8_t*, int32_t);
+
+# define UNPREMULTIPLY_ROW_NEON(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ UnpremultiplyRow_NEON<ShouldSwapRB(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Swizzle_NEON(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize);
+
+# define SWIZZLE_NEON(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ Swizzle_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void SwizzleRow_NEON(const uint8_t*, uint8_t*, int32_t);
+
+# define SWIZZLE_ROW_NEON(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ SwizzleRow_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat)>)
+
+template <bool aSwapRB>
+void UnpackRowRGB24_NEON(const uint8_t*, uint8_t*, int32_t);
+
+# define UNPACK_ROW_RGB_NEON(aDstFormat) \
+ FORMAT_CASE_ROW( \
+ SurfaceFormat::R8G8B8, aDstFormat, \
+ UnpackRowRGB24_NEON<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+#endif
+
+/**
+ * Premultiplying
+ */
+
+// Fallback premultiply implementation that uses splayed pixel math to reduce
+// the multiplications used. That is, the R and B components are isolated from
+// the G and A components, which then can be multiplied as if they were two
+// 2-component vectors. Otherwise, an approximation if divide-by-255 is used
+// which is faster than an actual division. These optimizations are also used
+// for the SSE2 and NEON implementations.
+template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift,
+ uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift>
+static void PremultiplyChunkFallback(const uint8_t*& aSrc, uint8_t*& aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 4 * aLength;
+ do {
+ // Load and process 1 entire pixel at a time.
+ uint32_t color = *reinterpret_cast<const uint32_t*>(aSrc);
+
+ uint32_t a = aSrcAShift ? color >> aSrcAShift : color & 0xFF;
+
+ // Isolate the R and B components.
+ uint32_t rb = (color >> aSrcRGBShift) & 0x00FF00FF;
+ // Swap the order of R and B if necessary.
+ if (aSwapRB) {
+ rb = (rb >> 16) | (rb << 16);
+ }
+ // Approximate the multiply by alpha and divide by 255 which is
+ // essentially:
+ // c = c*a + 255; c = (c + (c >> 8)) >> 8;
+ // However, we omit the final >> 8 to fold it with the final shift into
+ // place depending on desired output format.
+ rb = rb * a + 0x00FF00FF;
+ rb = (rb + ((rb >> 8) & 0x00FF00FF)) & 0xFF00FF00;
+
+ // Use same approximation as above, but G is shifted 8 bits left.
+ // Alpha is left out and handled separately.
+ uint32_t g = color & (0xFF00 << aSrcRGBShift);
+ g = g * a + (0xFF00 << aSrcRGBShift);
+ g = (g + (g >> 8)) & (0xFF0000 << aSrcRGBShift);
+
+ // The above math leaves RGB shifted left by 8 bits.
+ // Shift them right if required for the output format.
+ // then combine them back together to produce output pixel.
+ // Add the alpha back on if the output format is not opaque.
+ *reinterpret_cast<uint32_t*>(aDst) =
+ (rb >> (8 - aDstRGBShift)) | (g >> (8 + aSrcRGBShift - aDstRGBShift)) |
+ (aOpaqueAlpha ? 0xFF << aDstAShift : a << aDstAShift);
+
+ aSrc += 4;
+ aDst += 4;
+ } while (aSrc < end);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift,
+ uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift>
+static void PremultiplyRowFallback(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ PremultiplyChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift,
+ aDstRGBShift, aDstAShift>(aSrc, aDst, aLength);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift,
+ uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift>
+static void PremultiplyFallback(const uint8_t* aSrc, int32_t aSrcGap,
+ uint8_t* aDst, int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ PremultiplyChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift,
+ aDstRGBShift, aDstAShift>(aSrc, aDst, aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define PREMULTIPLY_FALLBACK_CASE(aSrcFormat, aDstFormat) \
+ FORMAT_CASE( \
+ aSrcFormat, aDstFormat, \
+ PremultiplyFallback<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat), \
+ RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \
+ RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>)
+
+#define PREMULTIPLY_FALLBACK(aSrcFormat) \
+ PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \
+ PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8X8) \
+ PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \
+ PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8X8) \
+ PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8) \
+ PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::X8R8G8B8)
+
+#define PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW(aSrcFormat, aDstFormat, \
+ PremultiplyRowFallback< \
+ ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat), \
+ RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \
+ RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>)
+
+#define PREMULTIPLY_ROW_FALLBACK(aSrcFormat) \
+ PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \
+ PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8X8) \
+ PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \
+ PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8X8) \
+ PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8) \
+ PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::X8R8G8B8)
+
+// If rows are tightly packed, and the size of the total area will fit within
+// the precision range of a single row, then process all the data as if it was
+// a single row.
+static inline IntSize CollapseSize(const IntSize& aSize, int32_t aSrcStride,
+ int32_t aDstStride) {
+ if (aSrcStride == aDstStride && (aSrcStride & 3) == 0 &&
+ aSrcStride / 4 == aSize.width) {
+ CheckedInt32 area = CheckedInt32(aSize.width) * CheckedInt32(aSize.height);
+ if (area.isValid()) {
+ return IntSize(area.value(), 1);
+ }
+ }
+ return aSize;
+}
+
+static inline int32_t GetStrideGap(int32_t aWidth, SurfaceFormat aFormat,
+ int32_t aStride) {
+ CheckedInt32 used = CheckedInt32(aWidth) * BytesPerPixel(aFormat);
+ if (!used.isValid() || used.value() < 0) {
+ return -1;
+ }
+ return aStride - used.value();
+}
+
+bool PremultiplyData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize) {
+ if (aSize.IsEmpty()) {
+ return true;
+ }
+ IntSize size = CollapseSize(aSize, aSrcStride, aDstStride);
+ // Find gap from end of row to the start of the next row.
+ int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride);
+ int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride);
+ MOZ_ASSERT(srcGap >= 0 && dstGap >= 0);
+ if (srcGap < 0 || dstGap < 0) {
+ return false;
+ }
+
+#define FORMAT_CASE_CALL(...) __VA_ARGS__(aSrc, srcGap, aDst, dstGap, size)
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8)
+ PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ default:
+ break;
+ }
+#endif
+
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8)
+ PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ default:
+ break;
+ }
+#endif
+
+ switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ PREMULTIPLY_FALLBACK(SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_FALLBACK(SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_FALLBACK(SurfaceFormat::A8R8G8B8)
+ default:
+ break;
+ }
+
+#undef FORMAT_CASE_CALL
+
+ MOZ_ASSERT(false, "Unsupported premultiply formats");
+ return false;
+}
+
+SwizzleRowFn PremultiplyRow(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat) {
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ default:
+ break;
+ }
+#endif
+
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ default:
+ break;
+ }
+#endif
+
+ switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ PREMULTIPLY_ROW_FALLBACK(SurfaceFormat::B8G8R8A8)
+ PREMULTIPLY_ROW_FALLBACK(SurfaceFormat::R8G8B8A8)
+ PREMULTIPLY_ROW_FALLBACK(SurfaceFormat::A8R8G8B8)
+ default:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unsupported premultiply formats");
+ return nullptr;
+}
+
+/**
+ * Unpremultiplying
+ */
+
+// Generate a table of 8.16 fixed-point reciprocals representing 1/alpha.
+#define UNPREMULQ(x) (0xFF00FFU / (x))
+#define UNPREMULQ_2(x) UNPREMULQ(x), UNPREMULQ((x) + 1)
+#define UNPREMULQ_4(x) UNPREMULQ_2(x), UNPREMULQ_2((x) + 2)
+#define UNPREMULQ_8(x) UNPREMULQ_4(x), UNPREMULQ_4((x) + 4)
+#define UNPREMULQ_16(x) UNPREMULQ_8(x), UNPREMULQ_8((x) + 8)
+#define UNPREMULQ_32(x) UNPREMULQ_16(x), UNPREMULQ_16((x) + 16)
+static const uint32_t sUnpremultiplyTable[256] = {0,
+ UNPREMULQ(1),
+ UNPREMULQ_2(2),
+ UNPREMULQ_4(4),
+ UNPREMULQ_8(8),
+ UNPREMULQ_16(16),
+ UNPREMULQ_32(32),
+ UNPREMULQ_32(64),
+ UNPREMULQ_32(96),
+ UNPREMULQ_32(128),
+ UNPREMULQ_32(160),
+ UNPREMULQ_32(192),
+ UNPREMULQ_32(224)};
+
+// Fallback unpremultiply implementation that uses 8.16 fixed-point reciprocal
+// math to eliminate any division by the alpha component. This optimization is
+// used for the SSE2 and NEON implementations, with some adaptations. This
+// implementation also accesses color components using individual byte accesses
+// as this profiles faster than accessing the pixel as a uint32_t and
+// shifting/masking to access components.
+template <bool aSwapRB, uint32_t aSrcRGBIndex, uint32_t aSrcAIndex,
+ uint32_t aDstRGBIndex, uint32_t aDstAIndex>
+static void UnpremultiplyChunkFallback(const uint8_t*& aSrc, uint8_t*& aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 4 * aLength;
+ do {
+ uint8_t r = aSrc[aSrcRGBIndex + (aSwapRB ? 2 : 0)];
+ uint8_t g = aSrc[aSrcRGBIndex + 1];
+ uint8_t b = aSrc[aSrcRGBIndex + (aSwapRB ? 0 : 2)];
+ uint8_t a = aSrc[aSrcAIndex];
+
+ // Access the 8.16 reciprocal from the table based on alpha. Multiply by
+ // the reciprocal and shift off the fraction bits to approximate the
+ // division by alpha.
+ uint32_t q = sUnpremultiplyTable[a];
+ aDst[aDstRGBIndex + 0] = (r * q) >> 16;
+ aDst[aDstRGBIndex + 1] = (g * q) >> 16;
+ aDst[aDstRGBIndex + 2] = (b * q) >> 16;
+ aDst[aDstAIndex] = a;
+
+ aSrc += 4;
+ aDst += 4;
+ } while (aSrc < end);
+}
+
+template <bool aSwapRB, uint32_t aSrcRGBIndex, uint32_t aSrcAIndex,
+ uint32_t aDstRGBIndex, uint32_t aDstAIndex>
+static void UnpremultiplyRowFallback(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ UnpremultiplyChunkFallback<aSwapRB, aSrcRGBIndex, aSrcAIndex, aDstRGBIndex,
+ aDstAIndex>(aSrc, aDst, aLength);
+}
+
+template <bool aSwapRB, uint32_t aSrcRGBIndex, uint32_t aSrcAIndex,
+ uint32_t aDstRGBIndex, uint32_t aDstAIndex>
+static void UnpremultiplyFallback(const uint8_t* aSrc, int32_t aSrcGap,
+ uint8_t* aDst, int32_t aDstGap,
+ IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ UnpremultiplyChunkFallback<aSwapRB, aSrcRGBIndex, aSrcAIndex, aDstRGBIndex,
+ aDstAIndex>(aSrc, aDst, aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ UnpremultiplyFallback< \
+ ShouldSwapRB(aSrcFormat, aDstFormat), \
+ RGBByteIndex(aSrcFormat), AlphaByteIndex(aSrcFormat), \
+ RGBByteIndex(aDstFormat), AlphaByteIndex(aDstFormat)>)
+
+#define UNPREMULTIPLY_FALLBACK(aSrcFormat) \
+ UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \
+ UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \
+ UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8)
+
+#define UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW(aSrcFormat, aDstFormat, \
+ UnpremultiplyRowFallback< \
+ ShouldSwapRB(aSrcFormat, aDstFormat), \
+ RGBByteIndex(aSrcFormat), AlphaByteIndex(aSrcFormat), \
+ RGBByteIndex(aDstFormat), AlphaByteIndex(aDstFormat)>)
+
+#define UNPREMULTIPLY_ROW_FALLBACK(aSrcFormat) \
+ UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \
+ UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \
+ UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8)
+
+bool UnpremultiplyData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize) {
+ if (aSize.IsEmpty()) {
+ return true;
+ }
+ IntSize size = CollapseSize(aSize, aSrcStride, aDstStride);
+ // Find gap from end of row to the start of the next row.
+ int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride);
+ int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride);
+ MOZ_ASSERT(srcGap >= 0 && dstGap >= 0);
+ if (srcGap < 0 || dstGap < 0) {
+ return false;
+ }
+
+#define FORMAT_CASE_CALL(...) __VA_ARGS__(aSrc, srcGap, aDst, dstGap, size)
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ UNPREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ UNPREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+ switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPREMULTIPLY_FALLBACK(SurfaceFormat::B8G8R8A8)
+ UNPREMULTIPLY_FALLBACK(SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_FALLBACK(SurfaceFormat::A8R8G8B8)
+ default:
+ break;
+ }
+
+#undef FORMAT_CASE_CALL
+
+ MOZ_ASSERT(false, "Unsupported unpremultiply formats");
+ return false;
+}
+
+SwizzleRowFn UnpremultiplyRow(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat) {
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8)
+ UNPREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+ switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPREMULTIPLY_ROW_FALLBACK(SurfaceFormat::B8G8R8A8)
+ UNPREMULTIPLY_ROW_FALLBACK(SurfaceFormat::R8G8B8A8)
+ UNPREMULTIPLY_ROW_FALLBACK(SurfaceFormat::A8R8G8B8)
+ default:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unsupported premultiply formats");
+ return nullptr;
+}
+
+/**
+ * Swizzling
+ */
+
+// Fallback swizzle implementation that uses shifting and masking to reorder
+// pixels.
+template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift,
+ uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift>
+static void SwizzleChunkFallback(const uint8_t*& aSrc, uint8_t*& aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 4 * aLength;
+ do {
+ uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc);
+
+ if (aSwapRB) {
+ // Handle R and B swaps by exchanging words and masking.
+ uint32_t rb =
+ ((rgba << 16) | (rgba >> 16)) & (0x00FF00FF << aSrcRGBShift);
+ uint32_t ga = rgba & ((0xFF << aSrcAShift) | (0xFF00 << aSrcRGBShift));
+ rgba = rb | ga;
+ }
+
+ // If src and dst shifts differ, rotate left or right to move RGB into
+ // place, i.e. ARGB -> RGBA or ARGB -> RGBA.
+ if (aDstRGBShift > aSrcRGBShift) {
+ rgba = (rgba << 8) | (aOpaqueAlpha ? 0x000000FF : rgba >> 24);
+ } else if (aSrcRGBShift > aDstRGBShift) {
+ rgba = (rgba >> 8) | (aOpaqueAlpha ? 0xFF000000 : rgba << 24);
+ } else if (aOpaqueAlpha) {
+ rgba |= 0xFF << aDstAShift;
+ }
+
+ *reinterpret_cast<uint32_t*>(aDst) = rgba;
+
+ aSrc += 4;
+ aDst += 4;
+ } while (aSrc < end);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift,
+ uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift>
+static void SwizzleRowFallback(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ SwizzleChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift,
+ aDstRGBShift, aDstAShift>(aSrc, aDst, aLength);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift,
+ uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift>
+static void SwizzleFallback(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift,
+ aDstRGBShift, aDstAShift>(aSrc, aDst, aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define SWIZZLE_FALLBACK(aSrcFormat, aDstFormat) \
+ FORMAT_CASE( \
+ aSrcFormat, aDstFormat, \
+ SwizzleFallback<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat), \
+ RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \
+ RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>)
+
+#define SWIZZLE_ROW_FALLBACK(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ SwizzleRowFallback<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ ShouldForceOpaque(aSrcFormat, aDstFormat), \
+ RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \
+ RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>)
+
+// Fast-path for matching formats.
+template <int32_t aBytesPerPixel>
+static void SwizzleRowCopy(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ if (aSrc != aDst) {
+ memcpy(aDst, aSrc, aLength * aBytesPerPixel);
+ }
+}
+
+// Fast-path for matching formats.
+static void SwizzleCopy(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize, int32_t aBPP) {
+ if (aSrc != aDst) {
+ int32_t rowLength = aBPP * aSize.width;
+ for (int32_t height = aSize.height; height > 0; height--) {
+ memcpy(aDst, aSrc, rowLength);
+ aSrc += rowLength + aSrcGap;
+ aDst += rowLength + aDstGap;
+ }
+ }
+}
+
+// Fast-path for conversions that swap all bytes.
+template <bool aOpaqueAlpha, uint32_t aSrcAShift, uint32_t aDstAShift>
+static void SwizzleChunkSwap(const uint8_t*& aSrc, uint8_t*& aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 4 * aLength;
+ do {
+ // Use an endian swap to move the bytes, i.e. BGRA -> ARGB.
+ uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc);
+#if MOZ_LITTLE_ENDIAN()
+ rgba = NativeEndian::swapToBigEndian(rgba);
+#else
+ rgba = NativeEndian::swapToLittleEndian(rgba);
+#endif
+ if (aOpaqueAlpha) {
+ rgba |= 0xFF << aDstAShift;
+ }
+ *reinterpret_cast<uint32_t*>(aDst) = rgba;
+ aSrc += 4;
+ aDst += 4;
+ } while (aSrc < end);
+}
+
+template <bool aOpaqueAlpha, uint32_t aSrcAShift, uint32_t aDstAShift>
+static void SwizzleRowSwap(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ SwizzleChunkSwap<aOpaqueAlpha, aSrcAShift, aDstAShift>(aSrc, aDst, aLength);
+}
+
+template <bool aOpaqueAlpha, uint32_t aSrcAShift, uint32_t aDstAShift>
+static void SwizzleSwap(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunkSwap<aOpaqueAlpha, aSrcAShift, aDstAShift>(aSrc, aDst,
+ aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define SWIZZLE_SWAP(aSrcFormat, aDstFormat) \
+ FORMAT_CASE( \
+ aSrcFormat, aDstFormat, \
+ SwizzleSwap<ShouldForceOpaque(aSrcFormat, aDstFormat), \
+ AlphaBitShift(aSrcFormat), AlphaBitShift(aDstFormat)>)
+
+#define SWIZZLE_ROW_SWAP(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ SwizzleRowSwap<ShouldForceOpaque(aSrcFormat, aDstFormat), \
+ AlphaBitShift(aSrcFormat), AlphaBitShift(aDstFormat)>)
+
+static void SwizzleChunkSwapRGB24(const uint8_t*& aSrc, uint8_t*& aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 3 * aLength;
+ do {
+ uint8_t r = aSrc[0];
+ uint8_t g = aSrc[1];
+ uint8_t b = aSrc[2];
+ aDst[0] = b;
+ aDst[1] = g;
+ aDst[2] = r;
+ aSrc += 3;
+ aDst += 3;
+ } while (aSrc < end);
+}
+
+static void SwizzleRowSwapRGB24(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ SwizzleChunkSwapRGB24(aSrc, aDst, aLength);
+}
+
+static void SwizzleSwapRGB24(const uint8_t* aSrc, int32_t aSrcGap,
+ uint8_t* aDst, int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunkSwapRGB24(aSrc, aDst, aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define SWIZZLE_SWAP_RGB24(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, SwizzleSwapRGB24)
+
+#define SWIZZLE_ROW_SWAP_RGB24(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW(aSrcFormat, aDstFormat, SwizzleRowSwapRGB24)
+
+// Fast-path for conversions that force alpha to opaque.
+template <uint32_t aDstAShift>
+static void SwizzleChunkOpaqueUpdate(uint8_t*& aBuffer, int32_t aLength) {
+ const uint8_t* end = aBuffer + 4 * aLength;
+ do {
+ uint32_t rgba = *reinterpret_cast<const uint32_t*>(aBuffer);
+ // Just add on the alpha bits to the source.
+ rgba |= 0xFF << aDstAShift;
+ *reinterpret_cast<uint32_t*>(aBuffer) = rgba;
+ aBuffer += 4;
+ } while (aBuffer < end);
+}
+
+template <uint32_t aDstAShift>
+static void SwizzleChunkOpaqueCopy(const uint8_t*& aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 4 * aLength;
+ do {
+ uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc);
+ // Just add on the alpha bits to the source.
+ rgba |= 0xFF << aDstAShift;
+ *reinterpret_cast<uint32_t*>(aDst) = rgba;
+ aSrc += 4;
+ aDst += 4;
+ } while (aSrc < end);
+}
+
+template <uint32_t aDstAShift>
+static void SwizzleRowOpaque(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ if (aSrc == aDst) {
+ SwizzleChunkOpaqueUpdate<aDstAShift>(aDst, aLength);
+ } else {
+ SwizzleChunkOpaqueCopy<aDstAShift>(aSrc, aDst, aLength);
+ }
+}
+
+template <uint32_t aDstAShift>
+static void SwizzleOpaque(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ if (aSrc == aDst) {
+ // Modifying in-place, so just write out the alpha.
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunkOpaqueUpdate<aDstAShift>(aDst, aSize.width);
+ aDst += aDstGap;
+ }
+ } else {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunkOpaqueCopy<aDstAShift>(aSrc, aDst, aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+ }
+}
+
+#define SWIZZLE_OPAQUE(aSrcFormat, aDstFormat) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, SwizzleOpaque<AlphaBitShift(aDstFormat)>)
+
+#define SWIZZLE_ROW_OPAQUE(aSrcFormat, aDstFormat) \
+ FORMAT_CASE_ROW(aSrcFormat, aDstFormat, \
+ SwizzleRowOpaque<AlphaBitShift(aDstFormat)>)
+
+// Packing of 32-bit formats to RGB565.
+template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex>
+static void PackToRGB565(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ const uint8_t* end = aSrc + 4 * aSize.width;
+ do {
+ uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc);
+
+ // Isolate the R, G, and B components and shift to final endian-dependent
+ // locations.
+ uint16_t rgb565;
+ if (aSwapRB) {
+ rgb565 = ((rgba & (0xF8 << aSrcRGBShift)) << (8 - aSrcRGBShift)) |
+ ((rgba & (0xFC00 << aSrcRGBShift)) >> (5 + aSrcRGBShift)) |
+ ((rgba & (0xF80000 << aSrcRGBShift)) >> (19 + aSrcRGBShift));
+ } else {
+ rgb565 = ((rgba & (0xF8 << aSrcRGBShift)) >> (3 + aSrcRGBShift)) |
+ ((rgba & (0xFC00 << aSrcRGBShift)) >> (5 + aSrcRGBShift)) |
+ ((rgba & (0xF80000 << aSrcRGBShift)) >> (8 + aSrcRGBShift));
+ }
+
+ *reinterpret_cast<uint16_t*>(aDst) = rgb565;
+
+ aSrc += 4;
+ aDst += 2;
+ } while (aSrc < end);
+
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Packing of 32-bit formats to 24-bit formats.
+template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex>
+static void PackChunkToRGB24(const uint8_t*& aSrc, uint8_t*& aDst,
+ int32_t aLength) {
+ const uint8_t* end = aSrc + 4 * aLength;
+ do {
+ uint8_t r = aSrc[aSrcRGBIndex + (aSwapRB ? 2 : 0)];
+ uint8_t g = aSrc[aSrcRGBIndex + 1];
+ uint8_t b = aSrc[aSrcRGBIndex + (aSwapRB ? 0 : 2)];
+
+ aDst[0] = r;
+ aDst[1] = g;
+ aDst[2] = b;
+
+ aSrc += 4;
+ aDst += 3;
+ } while (aSrc < end);
+}
+
+template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex>
+static void PackRowToRGB24(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ PackChunkToRGB24<aSwapRB, aSrcRGBShift, aSrcRGBIndex>(aSrc, aDst, aLength);
+}
+
+template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex>
+static void PackToRGB24(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ PackChunkToRGB24<aSwapRB, aSrcRGBShift, aSrcRGBIndex>(aSrc, aDst,
+ aSize.width);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define PACK_RGB_CASE(aSrcFormat, aDstFormat, aPackFunc) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, \
+ aPackFunc<ShouldSwapRB(aSrcFormat, aDstFormat), \
+ RGBBitShift(aSrcFormat), RGBByteIndex(aSrcFormat)>)
+
+#define PACK_RGB(aDstFormat, aPackFunc) \
+ PACK_RGB_CASE(SurfaceFormat::B8G8R8A8, aDstFormat, aPackFunc) \
+ PACK_RGB_CASE(SurfaceFormat::B8G8R8X8, aDstFormat, aPackFunc) \
+ PACK_RGB_CASE(SurfaceFormat::R8G8B8A8, aDstFormat, aPackFunc) \
+ PACK_RGB_CASE(SurfaceFormat::R8G8B8X8, aDstFormat, aPackFunc) \
+ PACK_RGB_CASE(SurfaceFormat::A8R8G8B8, aDstFormat, aPackFunc) \
+ PACK_RGB_CASE(SurfaceFormat::X8R8G8B8, aDstFormat, aPackFunc)
+
+#define PACK_ROW_RGB_CASE(aSrcFormat, aDstFormat, aPackFunc) \
+ FORMAT_CASE_ROW( \
+ aSrcFormat, aDstFormat, \
+ aPackFunc<ShouldSwapRB(aSrcFormat, aDstFormat), RGBBitShift(aSrcFormat), \
+ RGBByteIndex(aSrcFormat)>)
+
+#define PACK_ROW_RGB(aDstFormat, aPackFunc) \
+ PACK_ROW_RGB_CASE(SurfaceFormat::B8G8R8A8, aDstFormat, aPackFunc) \
+ PACK_ROW_RGB_CASE(SurfaceFormat::B8G8R8X8, aDstFormat, aPackFunc) \
+ PACK_ROW_RGB_CASE(SurfaceFormat::R8G8B8A8, aDstFormat, aPackFunc) \
+ PACK_ROW_RGB_CASE(SurfaceFormat::R8G8B8X8, aDstFormat, aPackFunc) \
+ PACK_ROW_RGB_CASE(SurfaceFormat::A8R8G8B8, aDstFormat, aPackFunc) \
+ PACK_ROW_RGB_CASE(SurfaceFormat::X8R8G8B8, aDstFormat, aPackFunc)
+
+// Packing of 32-bit formats to A8.
+template <uint32_t aSrcAIndex>
+static void PackToA8(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ for (int32_t height = aSize.height; height > 0; height--) {
+ const uint8_t* end = aSrc + 4 * aSize.width;
+ do {
+ *aDst++ = aSrc[aSrcAIndex];
+ aSrc += 4;
+ } while (aSrc < end);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+#define PACK_ALPHA_CASE(aSrcFormat, aDstFormat, aPackFunc) \
+ FORMAT_CASE(aSrcFormat, aDstFormat, aPackFunc<AlphaByteIndex(aSrcFormat)>)
+
+#define PACK_ALPHA(aDstFormat, aPackFunc) \
+ PACK_ALPHA_CASE(SurfaceFormat::B8G8R8A8, aDstFormat, aPackFunc) \
+ PACK_ALPHA_CASE(SurfaceFormat::R8G8B8A8, aDstFormat, aPackFunc) \
+ PACK_ALPHA_CASE(SurfaceFormat::A8R8G8B8, aDstFormat, aPackFunc)
+
+template <bool aSwapRB>
+void UnpackRowRGB24(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ // Because we are expanding, we can only process the data back to front in
+ // case we are performing this in place.
+ const uint8_t* src = aSrc + 3 * (aLength - 1);
+ uint32_t* dst = reinterpret_cast<uint32_t*>(aDst + 4 * aLength);
+ while (src >= aSrc) {
+ uint8_t r = src[aSwapRB ? 2 : 0];
+ uint8_t g = src[1];
+ uint8_t b = src[aSwapRB ? 0 : 2];
+#if MOZ_LITTLE_ENDIAN()
+ *--dst = 0xFF000000 | (b << 16) | (g << 8) | r;
+#else
+ *--dst = 0x000000FF | (b << 8) | (g << 16) | (r << 24);
+#endif
+ src -= 3;
+ }
+}
+
+// Force instantiation of swizzle variants here.
+template void UnpackRowRGB24<false>(const uint8_t*, uint8_t*, int32_t);
+template void UnpackRowRGB24<true>(const uint8_t*, uint8_t*, int32_t);
+
+#define UNPACK_ROW_RGB(aDstFormat) \
+ FORMAT_CASE_ROW( \
+ SurfaceFormat::R8G8B8, aDstFormat, \
+ UnpackRowRGB24<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+
+static void UnpackRowRGB24_To_ARGB(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ // Because we are expanding, we can only process the data back to front in
+ // case we are performing this in place.
+ const uint8_t* src = aSrc + 3 * (aLength - 1);
+ uint32_t* dst = reinterpret_cast<uint32_t*>(aDst + 4 * aLength);
+ while (src >= aSrc) {
+ uint8_t r = src[0];
+ uint8_t g = src[1];
+ uint8_t b = src[2];
+#if MOZ_LITTLE_ENDIAN()
+ *--dst = 0x000000FF | (r << 8) | (g << 16) | (b << 24);
+#else
+ *--dst = 0xFF000000 | (r << 24) | (g << 16) | b;
+#endif
+ src -= 3;
+ }
+}
+
+#define UNPACK_ROW_RGB_TO_ARGB(aDstFormat) \
+ FORMAT_CASE_ROW(SurfaceFormat::R8G8B8, aDstFormat, UnpackRowRGB24_To_ARGB)
+
+bool SwizzleData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst, int32_t aDstStride,
+ SurfaceFormat aDstFormat, const IntSize& aSize) {
+ if (aSize.IsEmpty()) {
+ return true;
+ }
+ IntSize size = CollapseSize(aSize, aSrcStride, aDstStride);
+ // Find gap from end of row to the start of the next row.
+ int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride);
+ int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride);
+ MOZ_ASSERT(srcGap >= 0 && dstGap >= 0);
+ if (srcGap < 0 || dstGap < 0) {
+ return false;
+ }
+
+#define FORMAT_CASE_CALL(...) __VA_ARGS__(aSrc, srcGap, aDst, dstGap, size)
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ SWIZZLE_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ SWIZZLE_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+ switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8)
+
+ SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::A8R8G8B8)
+ SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::X8R8G8B8)
+
+ SWIZZLE_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8A8)
+
+ SWIZZLE_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8)
+ SWIZZLE_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::X8R8G8B8)
+ SWIZZLE_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::X8R8G8B8)
+ SWIZZLE_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::A8R8G8B8)
+ SWIZZLE_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8A8)
+
+ SWIZZLE_SWAP_RGB24(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8)
+ SWIZZLE_SWAP_RGB24(SurfaceFormat::B8G8R8, SurfaceFormat::R8G8B8)
+
+ SWIZZLE_OPAQUE(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_OPAQUE(SurfaceFormat::B8G8R8X8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_OPAQUE(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_OPAQUE(SurfaceFormat::R8G8B8X8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_OPAQUE(SurfaceFormat::A8R8G8B8, SurfaceFormat::X8R8G8B8)
+ SWIZZLE_OPAQUE(SurfaceFormat::X8R8G8B8, SurfaceFormat::A8R8G8B8)
+
+ PACK_RGB(SurfaceFormat::R5G6B5_UINT16, PackToRGB565)
+ PACK_RGB(SurfaceFormat::B8G8R8, PackToRGB24)
+ PACK_RGB(SurfaceFormat::R8G8B8, PackToRGB24)
+ PACK_ALPHA(SurfaceFormat::A8, PackToA8)
+
+ default:
+ break;
+ }
+
+ if (aSrcFormat == aDstFormat) {
+ // If the formats match, just do a generic copy.
+ SwizzleCopy(aSrc, srcGap, aDst, dstGap, size, BytesPerPixel(aSrcFormat));
+ return true;
+ }
+
+#undef FORMAT_CASE_CALL
+
+ MOZ_ASSERT(false, "Unsupported swizzle formats");
+ return false;
+}
+
+static bool SwizzleYFlipDataInternal(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride,
+ SurfaceFormat aDstFormat,
+ const IntSize& aSize,
+ SwizzleRowFn aSwizzleFn) {
+ if (!aSwizzleFn) {
+ return false;
+ }
+
+ // Guarantee our width and height are both greater than zero.
+ if (aSize.IsEmpty()) {
+ return true;
+ }
+
+ // Unlike SwizzleData/PremultiplyData, we don't use the stride gaps directly,
+ // but we can use it to verify that the stride is valid for our width and
+ // format.
+ int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride);
+ int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride);
+ MOZ_ASSERT(srcGap >= 0 && dstGap >= 0);
+ if (srcGap < 0 || dstGap < 0) {
+ return false;
+ }
+
+ // Swapping/swizzling to a new buffer is trivial.
+ if (aSrc != aDst) {
+ const uint8_t* src = aSrc;
+ const uint8_t* srcEnd = aSrc + aSize.height * aSrcStride;
+ uint8_t* dst = aDst + (aSize.height - 1) * aDstStride;
+ while (src < srcEnd) {
+ aSwizzleFn(src, dst, aSize.width);
+ src += aSrcStride;
+ dst -= aDstStride;
+ }
+ return true;
+ }
+
+ if (aSrcStride != aDstStride) {
+ return false;
+ }
+
+ // If we are swizzling in place, then we need a temporary row buffer.
+ UniquePtr<uint8_t[]> rowBuffer(new (std::nothrow) uint8_t[aDstStride]);
+ if (!rowBuffer) {
+ return false;
+ }
+
+ // Swizzle and swap the top and bottom rows until we meet in the middle.
+ int32_t middleRow = aSize.height / 2;
+ uint8_t* top = aDst;
+ uint8_t* bottom = aDst + (aSize.height - 1) * aDstStride;
+ for (int32_t row = 0; row < middleRow; ++row) {
+ memcpy(rowBuffer.get(), bottom, aDstStride);
+ aSwizzleFn(top, bottom, aSize.width);
+ aSwizzleFn(rowBuffer.get(), top, aSize.width);
+ top += aDstStride;
+ bottom -= aDstStride;
+ }
+
+ // If there is an odd numbered row, we haven't swizzled it yet.
+ if (aSize.height % 2 == 1) {
+ top = aDst + middleRow * aDstStride;
+ aSwizzleFn(top, top, aSize.width);
+ }
+ return true;
+}
+
+bool SwizzleYFlipData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize) {
+ return SwizzleYFlipDataInternal(aSrc, aSrcStride, aSrcFormat, aDst,
+ aDstStride, aDstFormat, aSize,
+ SwizzleRow(aSrcFormat, aDstFormat));
+}
+
+bool PremultiplyYFlipData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize) {
+ return SwizzleYFlipDataInternal(aSrc, aSrcStride, aSrcFormat, aDst,
+ aDstStride, aDstFormat, aSize,
+ PremultiplyRow(aSrcFormat, aDstFormat));
+}
+
+SwizzleRowFn SwizzleRow(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat) {
+#ifdef USE_SSE2
+ if (mozilla::supports_avx2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPACK_ROW_RGB_AVX2(SurfaceFormat::R8G8B8X8)
+ UNPACK_ROW_RGB_AVX2(SurfaceFormat::R8G8B8A8)
+ UNPACK_ROW_RGB_AVX2(SurfaceFormat::B8G8R8X8)
+ UNPACK_ROW_RGB_AVX2(SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+
+ if (mozilla::supports_ssse3()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPACK_ROW_RGB_SSSE3(SurfaceFormat::R8G8B8X8)
+ UNPACK_ROW_RGB_SSSE3(SurfaceFormat::R8G8B8A8)
+ UNPACK_ROW_RGB_SSSE3(SurfaceFormat::B8G8R8X8)
+ UNPACK_ROW_RGB_SSSE3(SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+
+ if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ UNPACK_ROW_RGB_NEON(SurfaceFormat::R8G8B8X8)
+ UNPACK_ROW_RGB_NEON(SurfaceFormat::R8G8B8A8)
+ UNPACK_ROW_RGB_NEON(SurfaceFormat::B8G8R8X8)
+ UNPACK_ROW_RGB_NEON(SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8)
+ default:
+ break;
+ }
+#endif
+
+ switch (FORMAT_KEY(aSrcFormat, aDstFormat)) {
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8)
+
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::A8R8G8B8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::X8R8G8B8)
+
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8A8)
+
+ SWIZZLE_ROW_OPAQUE(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_OPAQUE(SurfaceFormat::B8G8R8X8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_OPAQUE(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8)
+ SWIZZLE_ROW_OPAQUE(SurfaceFormat::R8G8B8X8, SurfaceFormat::R8G8B8A8)
+ SWIZZLE_ROW_OPAQUE(SurfaceFormat::A8R8G8B8, SurfaceFormat::X8R8G8B8)
+ SWIZZLE_ROW_OPAQUE(SurfaceFormat::X8R8G8B8, SurfaceFormat::A8R8G8B8)
+
+ SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::X8R8G8B8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::X8R8G8B8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::A8R8G8B8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8A8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8X8)
+ SWIZZLE_ROW_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8A8)
+
+ SWIZZLE_ROW_SWAP_RGB24(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8)
+ SWIZZLE_ROW_SWAP_RGB24(SurfaceFormat::B8G8R8, SurfaceFormat::R8G8B8)
+
+ UNPACK_ROW_RGB(SurfaceFormat::R8G8B8X8)
+ UNPACK_ROW_RGB(SurfaceFormat::R8G8B8A8)
+ UNPACK_ROW_RGB(SurfaceFormat::B8G8R8X8)
+ UNPACK_ROW_RGB(SurfaceFormat::B8G8R8A8)
+ UNPACK_ROW_RGB_TO_ARGB(SurfaceFormat::A8R8G8B8)
+ UNPACK_ROW_RGB_TO_ARGB(SurfaceFormat::X8R8G8B8)
+
+ PACK_ROW_RGB(SurfaceFormat::R8G8B8, PackRowToRGB24)
+ PACK_ROW_RGB(SurfaceFormat::B8G8R8, PackRowToRGB24)
+
+ default:
+ break;
+ }
+
+ if (aSrcFormat == aDstFormat) {
+ switch (BytesPerPixel(aSrcFormat)) {
+ case 4:
+ return &SwizzleRowCopy<4>;
+ case 3:
+ return &SwizzleRowCopy<3>;
+ default:
+ break;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unsupported swizzle formats");
+ return nullptr;
+}
+
+static IntRect ReorientRowRotate0FlipFallback(const uint8_t* aSrc,
+ int32_t aSrcRow, uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Reverse order of pixels in the row.
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc);
+ const uint32_t* end = src + aDstSize.width;
+ uint32_t* dst = reinterpret_cast<uint32_t*>(aDst + aSrcRow * aDstStride) +
+ aDstSize.width - 1;
+ do {
+ *dst-- = *src++;
+ } while (src < end);
+
+ return IntRect(0, aSrcRow, aDstSize.width, 1);
+}
+
+static IntRect ReorientRowRotate90FlipFallback(const uint8_t* aSrc,
+ int32_t aSrcRow, uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels from top to bottom, into left to right columns.
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc);
+ const uint32_t* end = src + aDstSize.height;
+ uint32_t* dst = reinterpret_cast<uint32_t*>(aDst) + aSrcRow;
+ int32_t stride = aDstStride / sizeof(uint32_t);
+ do {
+ *dst = *src++;
+ dst += stride;
+ } while (src < end);
+
+ return IntRect(aSrcRow, 0, 1, aDstSize.height);
+}
+
+static IntRect ReorientRowRotate180FlipFallback(const uint8_t* aSrc,
+ int32_t aSrcRow, uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels from top to bottom, into bottom to top rows.
+ uint8_t* dst = aDst + (aDstSize.height - aSrcRow - 1) * aDstStride;
+ memcpy(dst, aSrc, aDstSize.width * sizeof(uint32_t));
+ return IntRect(0, aDstSize.height - aSrcRow - 1, aDstSize.width, 1);
+}
+
+static IntRect ReorientRowRotate270FlipFallback(const uint8_t* aSrc,
+ int32_t aSrcRow, uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels in reverse order from top to bottom, into right to left
+ // columns.
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc);
+ const uint32_t* end = src + aDstSize.height;
+ uint32_t* dst =
+ reinterpret_cast<uint32_t*>(aDst + (aDstSize.height - 1) * aDstStride) +
+ aDstSize.width - aSrcRow - 1;
+ int32_t stride = aDstStride / sizeof(uint32_t);
+ do {
+ *dst = *src++;
+ dst -= stride;
+ } while (src < end);
+
+ return IntRect(aDstSize.width - aSrcRow - 1, 0, 1, aDstSize.height);
+}
+
+static IntRect ReorientRowRotate0Fallback(const uint8_t* aSrc, int32_t aSrcRow,
+ uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels into the destination.
+ uint8_t* dst = aDst + aSrcRow * aDstStride;
+ memcpy(dst, aSrc, aDstSize.width * sizeof(uint32_t));
+ return IntRect(0, aSrcRow, aDstSize.width, 1);
+}
+
+static IntRect ReorientRowRotate90Fallback(const uint8_t* aSrc, int32_t aSrcRow,
+ uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels from top to bottom, into right to left columns.
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc);
+ const uint32_t* end = src + aDstSize.height;
+ uint32_t* dst =
+ reinterpret_cast<uint32_t*>(aDst) + aDstSize.width - aSrcRow - 1;
+ int32_t stride = aDstStride / sizeof(uint32_t);
+ do {
+ *dst = *src++;
+ dst += stride;
+ } while (src < end);
+
+ return IntRect(aDstSize.width - aSrcRow - 1, 0, 1, aDstSize.height);
+}
+
+static IntRect ReorientRowRotate180Fallback(const uint8_t* aSrc,
+ int32_t aSrcRow, uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels in reverse order from top to bottom, into bottom to top
+ // rows.
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc);
+ const uint32_t* end = src + aDstSize.width;
+ uint32_t* dst = reinterpret_cast<uint32_t*>(
+ aDst + (aDstSize.height - aSrcRow - 1) * aDstStride) +
+ aDstSize.width - 1;
+ do {
+ *dst-- = *src++;
+ } while (src < end);
+
+ return IntRect(0, aDstSize.height - aSrcRow - 1, aDstSize.width, 1);
+}
+
+static IntRect ReorientRowRotate270Fallback(const uint8_t* aSrc,
+ int32_t aSrcRow, uint8_t* aDst,
+ const IntSize& aDstSize,
+ int32_t aDstStride) {
+ // Copy row of pixels in reverse order from top to bottom, into left to right
+ // column.
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc);
+ const uint32_t* end = src + aDstSize.height;
+ uint32_t* dst =
+ reinterpret_cast<uint32_t*>(aDst + (aDstSize.height - 1) * aDstStride) +
+ aSrcRow;
+ int32_t stride = aDstStride / sizeof(uint32_t);
+ do {
+ *dst = *src++;
+ dst -= stride;
+ } while (src < end);
+
+ return IntRect(aSrcRow, 0, 1, aDstSize.height);
+}
+
+ReorientRowFn ReorientRow(const struct image::Orientation& aOrientation) {
+ switch (aOrientation.flip) {
+ case image::Flip::Unflipped:
+ switch (aOrientation.rotation) {
+ case image::Angle::D0:
+ return &ReorientRowRotate0Fallback;
+ case image::Angle::D90:
+ return &ReorientRowRotate90Fallback;
+ case image::Angle::D180:
+ return &ReorientRowRotate180Fallback;
+ case image::Angle::D270:
+ return &ReorientRowRotate270Fallback;
+ default:
+ break;
+ }
+ break;
+ case image::Flip::Horizontal:
+ switch (aOrientation.rotation) {
+ case image::Angle::D0:
+ return &ReorientRowRotate0FlipFallback;
+ case image::Angle::D90:
+ if (aOrientation.flipFirst) {
+ return &ReorientRowRotate270FlipFallback;
+ } else {
+ return &ReorientRowRotate90FlipFallback;
+ }
+ case image::Angle::D180:
+ return &ReorientRowRotate180FlipFallback;
+ case image::Angle::D270:
+ if (aOrientation.flipFirst) {
+ return &ReorientRowRotate90FlipFallback;
+ } else {
+ return &ReorientRowRotate270FlipFallback;
+ }
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unhandled orientation!");
+ return nullptr;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/Swizzle.h b/gfx/2d/Swizzle.h
new file mode 100644
index 0000000000..333490c8c0
--- /dev/null
+++ b/gfx/2d/Swizzle.h
@@ -0,0 +1,112 @@
+/* -*- 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_SWIZZLE_H_
+#define MOZILLA_GFX_SWIZZLE_H_
+
+#include "Point.h"
+#include "Rect.h"
+
+namespace mozilla {
+namespace image {
+struct Orientation;
+}
+
+namespace gfx {
+
+/**
+ * Premultiplies source and writes it to destination. Source and destination may
+ * be the same to premultiply in-place. The source format must have an alpha
+ * channel.
+ */
+GFX2D_API bool PremultiplyData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize);
+
+/**
+ * Unpremultiplies source and writes it to destination. Source and destination
+ * may be the same to unpremultiply in-place. Both the source and destination
+ * formats must have an alpha channel.
+ */
+GFX2D_API bool UnpremultiplyData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize);
+
+/**
+ * Swizzles source and writes it to destination. Source and destination may be
+ * the same to swizzle in-place.
+ */
+GFX2D_API bool SwizzleData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize);
+
+/**
+ * Flips rows of source and swizzles it to destination. Source and destination
+ * may be the same to swizzle in-place; this will fail if it cannot allocate a
+ * temporary buffer.
+ */
+GFX2D_API bool SwizzleYFlipData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride, SurfaceFormat aDstFormat,
+ const IntSize& aSize);
+
+/**
+ * Flips rows of source and premultiplies/swizzles it to destination. Source and
+ * destination may be the same to premultiply/swizzle in-place; this will fail
+ * if it cannot allocate a temporary buffer.
+ */
+GFX2D_API bool PremultiplyYFlipData(const uint8_t* aSrc, int32_t aSrcStride,
+ SurfaceFormat aSrcFormat, uint8_t* aDst,
+ int32_t aDstStride,
+ SurfaceFormat aDstFormat,
+ const IntSize& aSize);
+
+/**
+ * Swizzles source and writes it to destination. Source and destination may be
+ * the same to swizzle in-place.
+ */
+typedef void (*SwizzleRowFn)(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength);
+
+/**
+ * Get a function pointer to perform premultiplication between two formats.
+ */
+GFX2D_API SwizzleRowFn PremultiplyRow(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat);
+
+/**
+ * Get a function pointer to perform unpremultiplication between two formats.
+ */
+GFX2D_API SwizzleRowFn UnpremultiplyRow(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat);
+
+/**
+ * Get a function pointer to perform swizzling between two formats.
+ */
+GFX2D_API SwizzleRowFn SwizzleRow(SurfaceFormat aSrcFormat,
+ SurfaceFormat aDstFormat);
+
+/**
+ * Reorients source and writes it to destination. Returns the dirty rect of
+ * what was changed in aDst.
+ */
+typedef IntRect (*ReorientRowFn)(const uint8_t* aSrc, int32_t aSrcRow,
+ uint8_t* aDst, const IntSize& aDstSize,
+ int32_t aDstStride);
+
+/**
+ * Get a function pointer to perform reorientation by row.
+ */
+GFX2D_API ReorientRowFn
+ReorientRow(const struct image::Orientation& aOrientation);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SWIZZLE_H_ */
diff --git a/gfx/2d/SwizzleAVX2.cpp b/gfx/2d/SwizzleAVX2.cpp
new file mode 100644
index 0000000000..fe8fbf4530
--- /dev/null
+++ b/gfx/2d/SwizzleAVX2.cpp
@@ -0,0 +1,83 @@
+/* -*- 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 "Swizzle.h"
+
+#include <immintrin.h>
+#include <tmmintrin.h>
+
+namespace mozilla::gfx {
+
+template <bool aSwapRB>
+void UnpackRowRGB24_SSSE3(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength);
+
+template <bool aSwapRB>
+void UnpackRowRGB24_AVX2(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ // Because this implementation will read an additional 8 bytes of data that
+ // is ignored and masked over, we cannot use the accelerated version for the
+ // last 1-10 pixels (3-30 bytes remaining) to guarantee we don't access memory
+ // outside the buffer (we read in 32 byte chunks).
+ if (aLength < 11) {
+ UnpackRowRGB24_SSSE3<aSwapRB>(aSrc, aDst, aLength);
+ return;
+ }
+
+ // Because we are expanding, we can only process the data back to front in
+ // case we are performing this in place.
+ int32_t alignedRow = (aLength - 4) & ~7;
+ int32_t remainder = aLength - alignedRow;
+
+ const uint8_t* src = aSrc + alignedRow * 3;
+ uint8_t* dst = aDst + alignedRow * 4;
+
+ // Handle any 3-10 remaining pixels.
+ UnpackRowRGB24_SSSE3<aSwapRB>(src, dst, remainder);
+
+ // Used to shuffle the two final 32-bit words which we ignore into the last
+ // 32-bit word of each 128-bit lane, such that
+ // RGBR GBRG BRGB RGBR GBRG BRGB RGBR GBRG
+ // BRGB RGBR GBRG BRGB ZZZZ ZZZZ ZZZZ ZZZZ
+ // becomes
+ // RGBR GBRG BRGB RGBR GBRG BRGB ZZZZ ZZZZ
+ // RGBR GBRG BRGB RGBR GBRG BRGB ZZZZ ZZZZ
+ const __m256i discardMask = _mm256_set_epi32(7, 5, 4, 3, 6, 2, 1, 0);
+
+ // Used to shuffle 8-bit words within a 128-bit lane, such that we transform
+ // RGBR GBRG BRGB RGBR GBRG BRGB ZZZZ ZZZZ
+ // into
+ // RGBZ RGBZ RGBZ RGBZ RGBZ RGBZ RGBZ RGBZ
+ // or
+ // BGRZ BGRZ BGRZ BGRZ BGRZ BGRZ BGRZ BGRZ
+ const __m256i colorMask =
+ aSwapRB ? _mm256_set_epi8(15, 9, 10, 11, 14, 6, 7, 8, 13, 3, 4, 5, 12, 0,
+ 1, 2, 15, 9, 10, 11, 14, 6, 7, 8, 13, 3, 4, 5,
+ 12, 0, 1, 2)
+ : _mm256_set_epi8(15, 11, 10, 9, 14, 8, 7, 6, 13, 5, 4, 3, 12, 2,
+ 1, 0, 15, 11, 10, 9, 14, 8, 7, 6, 13, 5, 4, 3,
+ 12, 2, 1, 0);
+
+ // Used to transform RGBZ/BGRZ to RGBX/BGRX, or force the alpha opaque.
+ const __m256i alphaMask = _mm256_set1_epi32(0xFF000000);
+
+ // Process all 8-pixel chunks as one vector.
+ src -= 8 * 3;
+ dst -= 8 * 4;
+ while (src >= aSrc) {
+ __m256i px = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(src));
+ px = _mm256_permutevar8x32_epi32(px, discardMask);
+ px = _mm256_shuffle_epi8(px, colorMask);
+ px = _mm256_or_si256(px, alphaMask);
+ _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst), px);
+ src -= 8 * 3;
+ dst -= 8 * 4;
+ }
+}
+
+// Force instantiation of swizzle variants here.
+template void UnpackRowRGB24_AVX2<false>(const uint8_t*, uint8_t*, int32_t);
+template void UnpackRowRGB24_AVX2<true>(const uint8_t*, uint8_t*, int32_t);
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/SwizzleNEON.cpp b/gfx/2d/SwizzleNEON.cpp
new file mode 100644
index 0000000000..887e93d632
--- /dev/null
+++ b/gfx/2d/SwizzleNEON.cpp
@@ -0,0 +1,451 @@
+/* -*- 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 "Swizzle.h"
+
+#include <arm_neon.h>
+
+namespace mozilla {
+namespace gfx {
+
+// Load 1-3 pixels into a 4 pixel vector.
+static MOZ_ALWAYS_INLINE uint16x8_t LoadRemainder_NEON(const uint8_t* aSrc,
+ size_t aLength) {
+ const uint32_t* src32 = reinterpret_cast<const uint32_t*>(aSrc);
+ uint32x4_t dst32;
+ if (aLength >= 2) {
+ // Load first 2 pixels
+ dst32 = vcombine_u32(vld1_u32(src32), vdup_n_u32(0));
+ // Load third pixel
+ if (aLength >= 3) {
+ dst32 = vld1q_lane_u32(src32 + 2, dst32, 2);
+ }
+ } else {
+ // Load single pixel
+ dst32 = vld1q_lane_u32(src32, vdupq_n_u32(0), 0);
+ }
+ return vreinterpretq_u16_u32(dst32);
+}
+
+// Store 1-3 pixels from a vector into memory without overwriting.
+static MOZ_ALWAYS_INLINE void StoreRemainder_NEON(uint8_t* aDst, size_t aLength,
+ const uint16x8_t& aSrc) {
+ uint32_t* dst32 = reinterpret_cast<uint32_t*>(aDst);
+ uint32x4_t src32 = vreinterpretq_u32_u16(aSrc);
+ if (aLength >= 2) {
+ // Store first 2 pixels
+ vst1_u32(dst32, vget_low_u32(src32));
+ // Store third pixel
+ if (aLength >= 3) {
+ vst1q_lane_u32(dst32 + 2, src32, 2);
+ }
+ } else {
+ // Store single pixel
+ vst1q_lane_u32(dst32, src32, 0);
+ }
+}
+
+// Premultiply vector of 4 pixels using splayed math.
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE uint16x8_t
+PremultiplyVector_NEON(const uint16x8_t& aSrc) {
+ // Isolate R and B with mask.
+ const uint16x8_t mask = vdupq_n_u16(0x00FF);
+ uint16x8_t rb = vandq_u16(aSrc, mask);
+ // Swap R and B if necessary.
+ if (aSwapRB) {
+ rb = vrev32q_u16(rb);
+ }
+ // Isolate G and A by shifting down to bottom of word.
+ uint16x8_t ga = vshrq_n_u16(aSrc, 8);
+
+ // Duplicate alphas to get vector of A1 A1 A2 A2 A3 A3 A4 A4
+ uint16x8_t alphas = vtrnq_u16(ga, ga).val[1];
+
+ // rb = rb*a + 255; rb += rb >> 8;
+ rb = vmlaq_u16(mask, rb, alphas);
+ rb = vsraq_n_u16(rb, rb, 8);
+
+ // If format is not opaque, force A to 255 so that A*alpha/255 = alpha
+ if (!aOpaqueAlpha) {
+ ga = vorrq_u16(ga, vreinterpretq_u16_u32(vdupq_n_u32(0x00FF0000)));
+ }
+ // ga = ga*a + 255; ga += ga >> 8;
+ ga = vmlaq_u16(mask, ga, alphas);
+ ga = vsraq_n_u16(ga, ga, 8);
+ // If format is opaque, force output A to be 255.
+ if (aOpaqueAlpha) {
+ ga = vorrq_u16(ga, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000)));
+ }
+
+ // Combine back to final pixel with (rb >> 8) | (ga & 0xFF00FF00)
+ return vsriq_n_u16(ga, rb, 8);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE void PremultiplyChunk_NEON(const uint8_t*& aSrc,
+ uint8_t*& aDst,
+ int32_t aAlignedRow,
+ int32_t aRemainder) {
+ // Process all 4-pixel chunks as one vector.
+ for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) {
+ uint16x8_t px = vld1q_u16(reinterpret_cast<const uint16_t*>(aSrc));
+ px = PremultiplyVector_NEON<aSwapRB, aOpaqueAlpha>(px);
+ vst1q_u16(reinterpret_cast<uint16_t*>(aDst), px);
+ aSrc += 4 * 4;
+ aDst += 4 * 4;
+ }
+
+ // Handle any 1-3 remaining pixels.
+ if (aRemainder) {
+ uint16x8_t px = LoadRemainder_NEON(aSrc, aRemainder);
+ px = PremultiplyVector_NEON<aSwapRB, aOpaqueAlpha>(px);
+ StoreRemainder_NEON(aDst, aRemainder, px);
+ }
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void PremultiplyRow_NEON(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ int32_t alignedRow = 4 * (aLength & ~3);
+ int32_t remainder = aLength & 3;
+ PremultiplyChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow,
+ remainder);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Premultiply_NEON(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ int32_t alignedRow = 4 * (aSize.width & ~3);
+ int32_t remainder = aSize.width & 3;
+ // Fold remainder into stride gap.
+ aSrcGap += 4 * remainder;
+ aDstGap += 4 * remainder;
+
+ for (int32_t height = aSize.height; height > 0; height--) {
+ PremultiplyChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow,
+ remainder);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Force instantiation of premultiply variants here.
+template void PremultiplyRow_NEON<false, false>(const uint8_t*, uint8_t*,
+ int32_t);
+template void PremultiplyRow_NEON<false, true>(const uint8_t*, uint8_t*,
+ int32_t);
+template void PremultiplyRow_NEON<true, false>(const uint8_t*, uint8_t*,
+ int32_t);
+template void PremultiplyRow_NEON<true, true>(const uint8_t*, uint8_t*,
+ int32_t);
+template void Premultiply_NEON<false, false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Premultiply_NEON<false, true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Premultiply_NEON<true, false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Premultiply_NEON<true, true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+
+// This generates a table of fixed-point reciprocals representing 1/alpha
+// similar to the fallback implementation. However, the reciprocal must
+// ultimately be multiplied as an unsigned 9 bit upper part and a signed
+// 15 bit lower part to cheaply multiply. Thus, the lower 15 bits of the
+// reciprocal is stored 15 bits of the reciprocal are masked off and
+// stored in the low word. The upper 9 bits are masked and shifted to fit
+// into the high word. These then get independently multiplied with the
+// color component and recombined to provide the full recriprocal multiply.
+#define UNPREMULQ_NEON(x) \
+ ((((0xFF00FFU / (x)) & 0xFF8000U) << 1) | ((0xFF00FFU / (x)) & 0x7FFFU))
+#define UNPREMULQ_NEON_2(x) UNPREMULQ_NEON(x), UNPREMULQ_NEON((x) + 1)
+#define UNPREMULQ_NEON_4(x) UNPREMULQ_NEON_2(x), UNPREMULQ_NEON_2((x) + 2)
+#define UNPREMULQ_NEON_8(x) UNPREMULQ_NEON_4(x), UNPREMULQ_NEON_4((x) + 4)
+#define UNPREMULQ_NEON_16(x) UNPREMULQ_NEON_8(x), UNPREMULQ_NEON_8((x) + 8)
+#define UNPREMULQ_NEON_32(x) UNPREMULQ_NEON_16(x), UNPREMULQ_NEON_16((x) + 16)
+static const uint32_t sUnpremultiplyTable_NEON[256] = {0,
+ UNPREMULQ_NEON(1),
+ UNPREMULQ_NEON_2(2),
+ UNPREMULQ_NEON_4(4),
+ UNPREMULQ_NEON_8(8),
+ UNPREMULQ_NEON_16(16),
+ UNPREMULQ_NEON_32(32),
+ UNPREMULQ_NEON_32(64),
+ UNPREMULQ_NEON_32(96),
+ UNPREMULQ_NEON_32(128),
+ UNPREMULQ_NEON_32(160),
+ UNPREMULQ_NEON_32(192),
+ UNPREMULQ_NEON_32(224)};
+
+// Unpremultiply a vector of 4 pixels using splayed math and a reciprocal table
+// that avoids doing any actual division.
+template <bool aSwapRB>
+static MOZ_ALWAYS_INLINE uint16x8_t
+UnpremultiplyVector_NEON(const uint16x8_t& aSrc) {
+ // Isolate R and B with mask.
+ uint16x8_t rb = vandq_u16(aSrc, vdupq_n_u16(0x00FF));
+ // Swap R and B if necessary.
+ if (aSwapRB) {
+ rb = vrev32q_u16(rb);
+ }
+
+ // Isolate G and A by shifting down to bottom of word.
+ uint16x8_t ga = vshrq_n_u16(aSrc, 8);
+ // Extract the alphas for the 4 pixels from the now isolated words.
+ int a1 = vgetq_lane_u16(ga, 1);
+ int a2 = vgetq_lane_u16(ga, 3);
+ int a3 = vgetq_lane_u16(ga, 5);
+ int a4 = vgetq_lane_u16(ga, 7);
+
+ // First load all of the interleaved low and high portions of the reciprocals
+ // and combine them a single vector as lo1 hi1 lo2 hi2 lo3 hi3 lo4 hi4
+ uint16x8_t q1234 = vreinterpretq_u16_u32(vld1q_lane_u32(
+ &sUnpremultiplyTable_NEON[a4],
+ vld1q_lane_u32(
+ &sUnpremultiplyTable_NEON[a3],
+ vld1q_lane_u32(
+ &sUnpremultiplyTable_NEON[a2],
+ vld1q_lane_u32(&sUnpremultiplyTable_NEON[a1], vdupq_n_u32(0), 0),
+ 1),
+ 2),
+ 3));
+ // Transpose the interleaved low/high portions so that we produce
+ // two separate duplicated vectors for the low and high portions respectively:
+ // lo1 lo1 lo2 lo2 lo3 lo3 lo4 lo4 and hi1 hi1 hi2 hi2 hi3 hi3 hi4 hi4
+ uint16x8x2_t q1234lohi = vtrnq_u16(q1234, q1234);
+
+ // VQDMULH is a signed multiply that doubles (*2) the result, then takes the
+ // high word. To work around the signedness and the doubling, the low
+ // portion of the reciprocal only stores the lower 15 bits, which fits in a
+ // signed 16 bit integer. The high 9 bit portion is effectively also doubled
+ // by 2 as a side-effect of being shifted for storage. Thus the output scale
+ // of doing a normal multiply by the high portion and the VQDMULH by the low
+ // portion are both doubled and can be safely added together. The resulting
+ // sum just needs to be halved (via VHADD) to thus cancel out the doubling.
+ // All this combines to produce a reciprocal multiply of the form:
+ // rb = ((rb * hi) + ((rb * lo * 2) >> 16)) / 2
+ rb = vhaddq_u16(
+ vmulq_u16(rb, q1234lohi.val[1]),
+ vreinterpretq_u16_s16(vqdmulhq_s16(
+ vreinterpretq_s16_u16(rb), vreinterpretq_s16_u16(q1234lohi.val[0]))));
+
+ // ga = ((ga * hi) + ((ga * lo * 2) >> 16)) / 2
+ ga = vhaddq_u16(
+ vmulq_u16(ga, q1234lohi.val[1]),
+ vreinterpretq_u16_s16(vqdmulhq_s16(
+ vreinterpretq_s16_u16(ga), vreinterpretq_s16_u16(q1234lohi.val[0]))));
+
+ // Combine to the final pixel with ((rb | (ga << 8)) & ~0xFF000000) | (aSrc &
+ // 0xFF000000), which inserts back in the original alpha value unchanged.
+ return vbslq_u16(vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000)), aSrc,
+ vsliq_n_u16(rb, ga, 8));
+}
+
+template <bool aSwapRB>
+static MOZ_ALWAYS_INLINE void UnpremultiplyChunk_NEON(const uint8_t*& aSrc,
+ uint8_t*& aDst,
+ int32_t aAlignedRow,
+ int32_t aRemainder) {
+ // Process all 4-pixel chunks as one vector.
+ for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) {
+ uint16x8_t px = vld1q_u16(reinterpret_cast<const uint16_t*>(aSrc));
+ px = UnpremultiplyVector_NEON<aSwapRB>(px);
+ vst1q_u16(reinterpret_cast<uint16_t*>(aDst), px);
+ aSrc += 4 * 4;
+ aDst += 4 * 4;
+ }
+
+ // Handle any 1-3 remaining pixels.
+ if (aRemainder) {
+ uint16x8_t px = LoadRemainder_NEON(aSrc, aRemainder);
+ px = UnpremultiplyVector_NEON<aSwapRB>(px);
+ StoreRemainder_NEON(aDst, aRemainder, px);
+ }
+}
+
+template <bool aSwapRB>
+void UnpremultiplyRow_NEON(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ int32_t alignedRow = 4 * (aLength & ~3);
+ int32_t remainder = aLength & 3;
+ UnpremultiplyChunk_NEON<aSwapRB>(aSrc, aDst, alignedRow, remainder);
+}
+
+template <bool aSwapRB>
+void Unpremultiply_NEON(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ int32_t alignedRow = 4 * (aSize.width & ~3);
+ int32_t remainder = aSize.width & 3;
+ // Fold remainder into stride gap.
+ aSrcGap += 4 * remainder;
+ aDstGap += 4 * remainder;
+
+ for (int32_t height = aSize.height; height > 0; height--) {
+ UnpremultiplyChunk_NEON<aSwapRB>(aSrc, aDst, alignedRow, remainder);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Force instantiation of unpremultiply variants here.
+template void UnpremultiplyRow_NEON<false>(const uint8_t*, uint8_t*, int32_t);
+template void UnpremultiplyRow_NEON<true>(const uint8_t*, uint8_t*, int32_t);
+template void Unpremultiply_NEON<false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Unpremultiply_NEON<true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+
+// Swizzle a vector of 4 pixels providing swaps and opaquifying.
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE uint16x8_t SwizzleVector_NEON(const uint16x8_t& aSrc) {
+ // Swap R and B, then add to G and A (forced to 255):
+ // (((src>>16) | (src << 16)) & 0x00FF00FF) |
+ // ((src | 0xFF000000) & ~0x00FF00FF)
+ return vbslq_u16(
+ vdupq_n_u16(0x00FF), vrev32q_u16(aSrc),
+ aOpaqueAlpha
+ ? vorrq_u16(aSrc, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000)))
+ : aSrc);
+}
+
+#if 0
+// These specializations currently do not profile faster than the generic versions,
+// so disable them for now.
+
+// Optimized implementations for when there is no R and B swap.
+template<>
+static MOZ_ALWAYS_INLINE uint16x8_t
+SwizzleVector_NEON<false, true>(const uint16x8_t& aSrc)
+{
+ // Force alpha to 255.
+ return vorrq_u16(aSrc, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000)));
+}
+
+template<>
+static MOZ_ALWAYS_INLINE uint16x8_t
+SwizzleVector_NEON<false, false>(const uint16x8_t& aSrc)
+{
+ return aSrc;
+}
+#endif
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE void SwizzleChunk_NEON(const uint8_t*& aSrc,
+ uint8_t*& aDst,
+ int32_t aAlignedRow,
+ int32_t aRemainder) {
+ // Process all 4-pixel chunks as one vector.
+ for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) {
+ uint16x8_t px = vld1q_u16(reinterpret_cast<const uint16_t*>(aSrc));
+ px = SwizzleVector_NEON<aSwapRB, aOpaqueAlpha>(px);
+ vst1q_u16(reinterpret_cast<uint16_t*>(aDst), px);
+ aSrc += 4 * 4;
+ aDst += 4 * 4;
+ }
+
+ // Handle any 1-3 remaining pixels.
+ if (aRemainder) {
+ uint16x8_t px = LoadRemainder_NEON(aSrc, aRemainder);
+ px = SwizzleVector_NEON<aSwapRB, aOpaqueAlpha>(px);
+ StoreRemainder_NEON(aDst, aRemainder, px);
+ }
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void SwizzleRow_NEON(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ int32_t alignedRow = 4 * (aLength & ~3);
+ int32_t remainder = aLength & 3;
+ SwizzleChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Swizzle_NEON(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ int32_t alignedRow = 4 * (aSize.width & ~3);
+ int32_t remainder = aSize.width & 3;
+ // Fold remainder into stride gap.
+ aSrcGap += 4 * remainder;
+ aDstGap += 4 * remainder;
+
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Force instantiation of swizzle variants here.
+template void SwizzleRow_NEON<true, false>(const uint8_t*, uint8_t*, int32_t);
+template void SwizzleRow_NEON<true, true>(const uint8_t*, uint8_t*, int32_t);
+template void Swizzle_NEON<true, false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Swizzle_NEON<true, true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+
+template <bool aSwapRB>
+void UnpackRowRGB24(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength);
+
+template <bool aSwapRB>
+void UnpackRowRGB24_NEON(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ // Because this implementation will read an additional 4 bytes of data that
+ // is ignored and masked over, we cannot use the accelerated version for the
+ // last 1-5 pixels (3-15 bytes remaining) to guarantee we don't access memory
+ // outside the buffer (we read in 16 byte chunks).
+ if (aLength < 6) {
+ UnpackRowRGB24<aSwapRB>(aSrc, aDst, aLength);
+ return;
+ }
+
+ // Because we are expanding, we can only process the data back to front in
+ // case we are performing this in place.
+ int32_t alignedRow = (aLength - 2) & ~3;
+ int32_t remainder = aLength - alignedRow;
+
+ const uint8_t* src = aSrc + alignedRow * 3;
+ uint8_t* dst = aDst + alignedRow * 4;
+
+ // Handle 2-5 remaining pixels.
+ UnpackRowRGB24<aSwapRB>(src, dst, remainder);
+
+ uint8x8_t masklo;
+ uint8x8_t maskhi;
+ if (aSwapRB) {
+ static const uint8_t masklo_data[] = {2, 1, 0, 0, 5, 4, 3, 0};
+ static const uint8_t maskhi_data[] = {4, 3, 2, 0, 7, 6, 5, 0};
+ masklo = vld1_u8(masklo_data);
+ maskhi = vld1_u8(maskhi_data);
+ } else {
+ static const uint8_t masklo_data[] = {0, 1, 2, 0, 3, 4, 5, 0};
+ static const uint8_t maskhi_data[] = {2, 3, 4, 0, 5, 6, 7, 0};
+ masklo = vld1_u8(masklo_data);
+ maskhi = vld1_u8(maskhi_data);
+ }
+
+ uint8x16_t alpha = vreinterpretq_u8_u32(vdupq_n_u32(0xFF000000));
+
+ // Process all 4-pixel chunks as one vector.
+ src -= 4 * 3;
+ dst -= 4 * 4;
+ while (src >= aSrc) {
+ uint8x16_t px = vld1q_u8(src);
+ // G2R2B1G1 R1B0G0R0 -> X1R1G1B1 X0R0G0B0
+ uint8x8_t pxlo = vtbl1_u8(vget_low_u8(px), masklo);
+ // B3G3R3B2 G2R2B1G1 -> X3R3G3B3 X2R2G2B2
+ uint8x8_t pxhi =
+ vtbl1_u8(vext_u8(vget_low_u8(px), vget_high_u8(px), 4), maskhi);
+ px = vcombine_u8(pxlo, pxhi);
+ px = vorrq_u8(px, alpha);
+ vst1q_u8(dst, px);
+ src -= 4 * 3;
+ dst -= 4 * 4;
+ }
+}
+
+// Force instantiation of swizzle variants here.
+template void UnpackRowRGB24_NEON<false>(const uint8_t*, uint8_t*, int32_t);
+template void UnpackRowRGB24_NEON<true>(const uint8_t*, uint8_t*, int32_t);
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/SwizzleSSE2.cpp b/gfx/2d/SwizzleSSE2.cpp
new file mode 100644
index 0000000000..da0853f440
--- /dev/null
+++ b/gfx/2d/SwizzleSSE2.cpp
@@ -0,0 +1,391 @@
+/* -*- 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 "Swizzle.h"
+
+#include <emmintrin.h>
+
+namespace mozilla::gfx {
+
+// Load 1-3 pixels into a 4 pixel vector.
+static MOZ_ALWAYS_INLINE __m128i LoadRemainder_SSE2(const uint8_t* aSrc,
+ size_t aLength) {
+ __m128i px;
+ if (aLength >= 2) {
+ // Load first 2 pixels
+ px = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(aSrc));
+ // Load third pixel
+ if (aLength >= 3) {
+ px = _mm_unpacklo_epi64(
+ px,
+ _mm_cvtsi32_si128(*reinterpret_cast<const uint32_t*>(aSrc + 2 * 4)));
+ }
+ } else {
+ // Load single pixel
+ px = _mm_cvtsi32_si128(*reinterpret_cast<const uint32_t*>(aSrc));
+ }
+ return px;
+}
+
+// Store 1-3 pixels from a vector into memory without overwriting.
+static MOZ_ALWAYS_INLINE void StoreRemainder_SSE2(uint8_t* aDst, size_t aLength,
+ const __m128i& aSrc) {
+ if (aLength >= 2) {
+ // Store first 2 pixels
+ _mm_storel_epi64(reinterpret_cast<__m128i*>(aDst), aSrc);
+ // Store third pixel
+ if (aLength >= 3) {
+ *reinterpret_cast<uint32_t*>(aDst + 2 * 4) =
+ _mm_cvtsi128_si32(_mm_srli_si128(aSrc, 2 * 4));
+ }
+ } else {
+ // Store single pixel
+ *reinterpret_cast<uint32_t*>(aDst) = _mm_cvtsi128_si32(aSrc);
+ }
+}
+
+// Premultiply vector of 4 pixels using splayed math.
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE __m128i PremultiplyVector_SSE2(const __m128i& aSrc) {
+ // Isolate R and B with mask.
+ const __m128i mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i rb = _mm_and_si128(mask, aSrc);
+ // Swap R and B if necessary.
+ if (aSwapRB) {
+ rb = _mm_shufflelo_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1));
+ rb = _mm_shufflehi_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1));
+ }
+ // Isolate G and A by shifting down to bottom of word.
+ __m128i ga = _mm_srli_epi16(aSrc, 8);
+
+ // Duplicate alphas to get vector of A1 A1 A2 A2 A3 A3 A4 A4
+ __m128i alphas = _mm_shufflelo_epi16(ga, _MM_SHUFFLE(3, 3, 1, 1));
+ alphas = _mm_shufflehi_epi16(alphas, _MM_SHUFFLE(3, 3, 1, 1));
+
+ // rb = rb*a + 255; rb += rb >> 8;
+ rb = _mm_add_epi16(_mm_mullo_epi16(rb, alphas), mask);
+ rb = _mm_add_epi16(rb, _mm_srli_epi16(rb, 8));
+
+ // If format is not opaque, force A to 255 so that A*alpha/255 = alpha
+ if (!aOpaqueAlpha) {
+ ga = _mm_or_si128(ga, _mm_set1_epi32(0x00FF0000));
+ }
+ // ga = ga*a + 255; ga += ga >> 8;
+ ga = _mm_add_epi16(_mm_mullo_epi16(ga, alphas), mask);
+ ga = _mm_add_epi16(ga, _mm_srli_epi16(ga, 8));
+ // If format is opaque, force output A to be 255.
+ if (aOpaqueAlpha) {
+ ga = _mm_or_si128(ga, _mm_set1_epi32(0xFF000000));
+ }
+
+ // Combine back to final pixel with (rb >> 8) | (ga & 0xFF00FF00)
+ rb = _mm_srli_epi16(rb, 8);
+ ga = _mm_andnot_si128(mask, ga);
+ return _mm_or_si128(rb, ga);
+}
+
+// Premultiply vector of aAlignedRow + aRemainder pixels.
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE void PremultiplyChunk_SSE2(const uint8_t*& aSrc,
+ uint8_t*& aDst,
+ int32_t aAlignedRow,
+ int32_t aRemainder) {
+ // Process all 4-pixel chunks as one vector.
+ for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) {
+ __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(aSrc));
+ px = PremultiplyVector_SSE2<aSwapRB, aOpaqueAlpha>(px);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(aDst), px);
+ aSrc += 4 * 4;
+ aDst += 4 * 4;
+ }
+
+ // Handle any 1-3 remaining pixels.
+ if (aRemainder) {
+ __m128i px = LoadRemainder_SSE2(aSrc, aRemainder);
+ px = PremultiplyVector_SSE2<aSwapRB, aOpaqueAlpha>(px);
+ StoreRemainder_SSE2(aDst, aRemainder, px);
+ }
+}
+
+// Premultiply vector of aLength pixels.
+template <bool aSwapRB, bool aOpaqueAlpha>
+void PremultiplyRow_SSE2(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ int32_t alignedRow = 4 * (aLength & ~3);
+ int32_t remainder = aLength & 3;
+ PremultiplyChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow,
+ remainder);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Premultiply_SSE2(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ int32_t alignedRow = 4 * (aSize.width & ~3);
+ int32_t remainder = aSize.width & 3;
+ // Fold remainder into stride gap.
+ aSrcGap += 4 * remainder;
+ aDstGap += 4 * remainder;
+
+ for (int32_t height = aSize.height; height > 0; height--) {
+ PremultiplyChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow,
+ remainder);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Force instantiation of premultiply variants here.
+template void PremultiplyRow_SSE2<false, false>(const uint8_t*, uint8_t*,
+ int32_t);
+template void PremultiplyRow_SSE2<false, true>(const uint8_t*, uint8_t*,
+ int32_t);
+template void PremultiplyRow_SSE2<true, false>(const uint8_t*, uint8_t*,
+ int32_t);
+template void PremultiplyRow_SSE2<true, true>(const uint8_t*, uint8_t*,
+ int32_t);
+template void Premultiply_SSE2<false, false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Premultiply_SSE2<false, true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Premultiply_SSE2<true, false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Premultiply_SSE2<true, true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+
+// This generates a table of fixed-point reciprocals representing 1/alpha
+// similar to the fallback implementation. However, the reciprocal must fit
+// in 16 bits to multiply cheaply. Observe that reciprocals of smaller alphas
+// require more bits than for larger alphas. We take advantage of this by
+// shifting the reciprocal down by either 3 or 8 bits depending on whether
+// the alpha value is less than 0x20. This is easy to then undo by multiplying
+// the color component to be unpremultiplying by either 8 or 0x100,
+// respectively. The 16 bit reciprocal is duplicated into both words of a
+// uint32_t here to reduce unpacking overhead.
+#define UNPREMULQ_SSE2(x) \
+ (0x10001U * (0xFF0220U / ((x) * ((x) < 0x20 ? 0x100 : 8))))
+#define UNPREMULQ_SSE2_2(x) UNPREMULQ_SSE2(x), UNPREMULQ_SSE2((x) + 1)
+#define UNPREMULQ_SSE2_4(x) UNPREMULQ_SSE2_2(x), UNPREMULQ_SSE2_2((x) + 2)
+#define UNPREMULQ_SSE2_8(x) UNPREMULQ_SSE2_4(x), UNPREMULQ_SSE2_4((x) + 4)
+#define UNPREMULQ_SSE2_16(x) UNPREMULQ_SSE2_8(x), UNPREMULQ_SSE2_8((x) + 8)
+#define UNPREMULQ_SSE2_32(x) UNPREMULQ_SSE2_16(x), UNPREMULQ_SSE2_16((x) + 16)
+static const uint32_t sUnpremultiplyTable_SSE2[256] = {0,
+ UNPREMULQ_SSE2(1),
+ UNPREMULQ_SSE2_2(2),
+ UNPREMULQ_SSE2_4(4),
+ UNPREMULQ_SSE2_8(8),
+ UNPREMULQ_SSE2_16(16),
+ UNPREMULQ_SSE2_32(32),
+ UNPREMULQ_SSE2_32(64),
+ UNPREMULQ_SSE2_32(96),
+ UNPREMULQ_SSE2_32(128),
+ UNPREMULQ_SSE2_32(160),
+ UNPREMULQ_SSE2_32(192),
+ UNPREMULQ_SSE2_32(224)};
+
+// Unpremultiply a vector of 4 pixels using splayed math and a reciprocal table
+// that avoids doing any actual division.
+template <bool aSwapRB>
+static MOZ_ALWAYS_INLINE __m128i UnpremultiplyVector_SSE2(const __m128i& aSrc) {
+ // Isolate R and B with mask.
+ __m128i rb = _mm_and_si128(aSrc, _mm_set1_epi32(0x00FF00FF));
+ // Swap R and B if necessary.
+ if (aSwapRB) {
+ rb = _mm_shufflelo_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1));
+ rb = _mm_shufflehi_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1));
+ }
+
+ // Isolate G and A by shifting down to bottom of word.
+ __m128i ga = _mm_srli_epi16(aSrc, 8);
+ // Extract the alphas for the 4 pixels from the now isolated words.
+ int a1 = _mm_extract_epi16(ga, 1);
+ int a2 = _mm_extract_epi16(ga, 3);
+ int a3 = _mm_extract_epi16(ga, 5);
+ int a4 = _mm_extract_epi16(ga, 7);
+
+ // Load the 16 bit reciprocals from the table for each alpha.
+ // The reciprocals are doubled in each uint32_t entry.
+ // Unpack them to a final vector of duplicated reciprocals of
+ // the form Q1 Q1 Q2 Q2 Q3 Q3 Q4 Q4.
+ __m128i q12 =
+ _mm_unpacklo_epi32(_mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a1]),
+ _mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a2]));
+ __m128i q34 =
+ _mm_unpacklo_epi32(_mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a3]),
+ _mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a4]));
+ __m128i q1234 = _mm_unpacklo_epi64(q12, q34);
+
+ // Check if the alphas are less than 0x20, so that we can undo
+ // scaling of the reciprocals as appropriate.
+ __m128i scale = _mm_cmplt_epi32(ga, _mm_set1_epi32(0x00200000));
+ // Produce scale factors by ((a < 0x20) ^ 8) & 0x108,
+ // such that scale is 0x100 if < 0x20, and 8 otherwise.
+ scale = _mm_xor_si128(scale, _mm_set1_epi16(8));
+ scale = _mm_and_si128(scale, _mm_set1_epi16(0x108));
+ // Isolate G now so that we don't accidentally unpremultiply A.
+ ga = _mm_and_si128(ga, _mm_set1_epi32(0x000000FF));
+
+ // Scale R, B, and G as required depending on reciprocal precision.
+ rb = _mm_mullo_epi16(rb, scale);
+ ga = _mm_mullo_epi16(ga, scale);
+
+ // Multiply R, B, and G by the reciprocal, only taking the high word
+ // too effectively shift right by 16.
+ rb = _mm_mulhi_epu16(rb, q1234);
+ ga = _mm_mulhi_epu16(ga, q1234);
+
+ // Combine back to final pixel with rb | (ga << 8) | (aSrc & 0xFF000000),
+ // which will add back on the original alpha value unchanged.
+ ga = _mm_slli_si128(ga, 1);
+ ga = _mm_or_si128(ga, _mm_and_si128(aSrc, _mm_set1_epi32(0xFF000000)));
+ return _mm_or_si128(rb, ga);
+}
+
+template <bool aSwapRB>
+static MOZ_ALWAYS_INLINE void UnpremultiplyChunk_SSE2(const uint8_t*& aSrc,
+ uint8_t*& aDst,
+ int32_t aAlignedRow,
+ int32_t aRemainder) {
+ // Process all 4-pixel chunks as one vector.
+ for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) {
+ __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(aSrc));
+ px = UnpremultiplyVector_SSE2<aSwapRB>(px);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(aDst), px);
+ aSrc += 4 * 4;
+ aDst += 4 * 4;
+ }
+
+ // Handle any 1-3 remaining pixels.
+ if (aRemainder) {
+ __m128i px = LoadRemainder_SSE2(aSrc, aRemainder);
+ px = UnpremultiplyVector_SSE2<aSwapRB>(px);
+ StoreRemainder_SSE2(aDst, aRemainder, px);
+ }
+}
+
+template <bool aSwapRB>
+void UnpremultiplyRow_SSE2(const uint8_t* aSrc, uint8_t* aDst,
+ int32_t aLength) {
+ int32_t alignedRow = 4 * (aLength & ~3);
+ int32_t remainder = aLength & 3;
+ UnpremultiplyChunk_SSE2<aSwapRB>(aSrc, aDst, alignedRow, remainder);
+}
+
+template <bool aSwapRB>
+void Unpremultiply_SSE2(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ int32_t alignedRow = 4 * (aSize.width & ~3);
+ int32_t remainder = aSize.width & 3;
+ // Fold remainder into stride gap.
+ aSrcGap += 4 * remainder;
+ aDstGap += 4 * remainder;
+
+ for (int32_t height = aSize.height; height > 0; height--) {
+ UnpremultiplyChunk_SSE2<aSwapRB>(aSrc, aDst, alignedRow, remainder);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Force instantiation of unpremultiply variants here.
+template void UnpremultiplyRow_SSE2<false>(const uint8_t*, uint8_t*, int32_t);
+template void UnpremultiplyRow_SSE2<true>(const uint8_t*, uint8_t*, int32_t);
+template void Unpremultiply_SSE2<false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Unpremultiply_SSE2<true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+
+// Swizzle a vector of 4 pixels providing swaps and opaquifying.
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE __m128i SwizzleVector_SSE2(const __m128i& aSrc) {
+ // Isolate R and B.
+ __m128i rb = _mm_and_si128(aSrc, _mm_set1_epi32(0x00FF00FF));
+ // Swap R and B.
+ rb = _mm_shufflelo_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1));
+ rb = _mm_shufflehi_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1));
+ // Isolate G and A.
+ __m128i ga = _mm_and_si128(aSrc, _mm_set1_epi32(0xFF00FF00));
+ // Force alpha to 255 if necessary.
+ if (aOpaqueAlpha) {
+ ga = _mm_or_si128(ga, _mm_set1_epi32(0xFF000000));
+ }
+ // Combine everything back together.
+ return _mm_or_si128(rb, ga);
+}
+
+#if 0
+// These specializations currently do not profile faster than the generic versions,
+// so disable them for now.
+
+// Optimized implementations for when there is no R and B swap.
+template<>
+MOZ_ALWAYS_INLINE __m128i
+SwizzleVector_SSE2<false, true>(const __m128i& aSrc)
+{
+ // Force alpha to 255.
+ return _mm_or_si128(aSrc, _mm_set1_epi32(0xFF000000));
+}
+
+template<>
+MOZ_ALWAYS_INLINE __m128i
+SwizzleVector_SSE2<false, false>(const __m128i& aSrc)
+{
+ return aSrc;
+}
+#endif
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+static MOZ_ALWAYS_INLINE void SwizzleChunk_SSE2(const uint8_t*& aSrc,
+ uint8_t*& aDst,
+ int32_t aAlignedRow,
+ int32_t aRemainder) {
+ // Process all 4-pixel chunks as one vector.
+ for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) {
+ __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(aSrc));
+ px = SwizzleVector_SSE2<aSwapRB, aOpaqueAlpha>(px);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(aDst), px);
+ aSrc += 4 * 4;
+ aDst += 4 * 4;
+ }
+
+ // Handle any 1-3 remaining pixels.
+ if (aRemainder) {
+ __m128i px = LoadRemainder_SSE2(aSrc, aRemainder);
+ px = SwizzleVector_SSE2<aSwapRB, aOpaqueAlpha>(px);
+ StoreRemainder_SSE2(aDst, aRemainder, px);
+ }
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void SwizzleRow_SSE2(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ int32_t alignedRow = 4 * (aLength & ~3);
+ int32_t remainder = aLength & 3;
+ SwizzleChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder);
+}
+
+template <bool aSwapRB, bool aOpaqueAlpha>
+void Swizzle_SSE2(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst,
+ int32_t aDstGap, IntSize aSize) {
+ int32_t alignedRow = 4 * (aSize.width & ~3);
+ int32_t remainder = aSize.width & 3;
+ // Fold remainder into stride gap.
+ aSrcGap += 4 * remainder;
+ aDstGap += 4 * remainder;
+
+ for (int32_t height = aSize.height; height > 0; height--) {
+ SwizzleChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder);
+ aSrc += aSrcGap;
+ aDst += aDstGap;
+ }
+}
+
+// Force instantiation of swizzle variants here.
+template void SwizzleRow_SSE2<true, false>(const uint8_t*, uint8_t*, int32_t);
+template void SwizzleRow_SSE2<true, true>(const uint8_t*, uint8_t*, int32_t);
+template void Swizzle_SSE2<true, false>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+template void Swizzle_SSE2<true, true>(const uint8_t*, int32_t, uint8_t*,
+ int32_t, IntSize);
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/SwizzleSSSE3.cpp b/gfx/2d/SwizzleSSSE3.cpp
new file mode 100644
index 0000000000..eac5d856fb
--- /dev/null
+++ b/gfx/2d/SwizzleSSSE3.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "Swizzle.h"
+
+#include <emmintrin.h>
+#include <tmmintrin.h>
+
+namespace mozilla::gfx {
+
+template <bool aSwapRB>
+void UnpackRowRGB24(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength);
+
+template <bool aSwapRB>
+void UnpackRowRGB24_SSSE3(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) {
+ // Because this implementation will read an additional 4 bytes of data that
+ // is ignored and masked over, we cannot use the accelerated version for the
+ // last 1-5 pixels (3-15 bytes remaining) to guarantee we don't access memory
+ // outside the buffer (we read in 16 byte chunks).
+ if (aLength < 6) {
+ UnpackRowRGB24<aSwapRB>(aSrc, aDst, aLength);
+ return;
+ }
+
+ // Because we are expanding, we can only process the data back to front in
+ // case we are performing this in place.
+ int32_t alignedRow = (aLength - 2) & ~3;
+ int32_t remainder = aLength - alignedRow;
+
+ const uint8_t* src = aSrc + alignedRow * 3;
+ uint8_t* dst = aDst + alignedRow * 4;
+
+ // Handle 2-5 remaining pixels.
+ UnpackRowRGB24<aSwapRB>(src, dst, remainder);
+
+ __m128i mask;
+ if (aSwapRB) {
+ mask = _mm_set_epi8(15, 9, 10, 11, 14, 6, 7, 8, 13, 3, 4, 5, 12, 0, 1, 2);
+ } else {
+ mask = _mm_set_epi8(15, 11, 10, 9, 14, 8, 7, 6, 13, 5, 4, 3, 12, 2, 1, 0);
+ }
+
+ __m128i alpha = _mm_set1_epi32(0xFF000000);
+
+ // Process all 4-pixel chunks as one vector.
+ src -= 4 * 3;
+ dst -= 4 * 4;
+ while (src >= aSrc) {
+ __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src));
+ px = _mm_shuffle_epi8(px, mask);
+ px = _mm_or_si128(px, alpha);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), px);
+ src -= 4 * 3;
+ dst -= 4 * 4;
+ }
+}
+
+// Force instantiation of swizzle variants here.
+template void UnpackRowRGB24_SSSE3<false>(const uint8_t*, uint8_t*, int32_t);
+template void UnpackRowRGB24_SSSE3<true>(const uint8_t*, uint8_t*, int32_t);
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/Tools.h b/gfx/2d/Tools.h
new file mode 100644
index 0000000000..90ef272b48
--- /dev/null
+++ b/gfx/2d/Tools.h
@@ -0,0 +1,198 @@
+/* -*- 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_TOOLS_H_
+#define MOZILLA_GFX_TOOLS_H_
+
+#include <math.h>
+
+#include <utility>
+
+#include "Point.h"
+#include "Types.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MemoryReporting.h" // for MallocSizeOf
+
+namespace mozilla {
+namespace gfx {
+
+static inline bool IsOperatorBoundByMask(CompositionOp aOp) {
+ switch (aOp) {
+ case CompositionOp::OP_IN:
+ case CompositionOp::OP_OUT:
+ case CompositionOp::OP_DEST_IN:
+ case CompositionOp::OP_DEST_ATOP:
+ case CompositionOp::OP_SOURCE:
+ return false;
+ default:
+ return true;
+ }
+}
+
+template <class T>
+struct ClassStorage {
+ char bytes[sizeof(T)];
+
+ const T* addr() const { return (const T*)bytes; }
+ T* addr() { return (T*)(void*)bytes; }
+};
+
+static inline bool FuzzyEqual(Float aA, Float aB, Float aErr) {
+ if ((aA + aErr >= aB) && (aA - aErr <= aB)) {
+ return true;
+ }
+ return false;
+}
+
+static inline void NudgeToInteger(float* aVal) {
+ float r = floorf(*aVal + 0.5f);
+ // The error threshold should be proportional to the rounded value. This
+ // bounds the relative error introduced by the nudge operation. However,
+ // when the rounded value is 0, the error threshold can't be proportional
+ // to the rounded value (we'd never round), so we just choose the same
+ // threshold as for a rounded value of 1.
+ if (FuzzyEqual(r, *aVal, r == 0.0f ? 1e-6f : fabs(r * 1e-6f))) {
+ *aVal = r;
+ }
+}
+
+static inline void NudgeToInteger(float* aVal, float aErr) {
+ float r = floorf(*aVal + 0.5f);
+ if (FuzzyEqual(r, *aVal, aErr)) {
+ *aVal = r;
+ }
+}
+
+static inline void NudgeToInteger(double* aVal) {
+ float f = float(*aVal);
+ NudgeToInteger(&f);
+ *aVal = f;
+}
+
+static inline Float Distance(Point aA, Point aB) {
+ return hypotf(aB.x - aA.x, aB.y - aA.y);
+}
+
+template <typename T, int alignment = 16>
+struct AlignedArray final {
+ typedef T value_type;
+
+ AlignedArray() : mPtr(nullptr), mStorage(nullptr), mCount(0) {}
+
+ explicit MOZ_ALWAYS_INLINE AlignedArray(size_t aCount, bool aZero = false)
+ : mPtr(nullptr), mStorage(nullptr), mCount(0) {
+ Realloc(aCount, aZero);
+ }
+
+ MOZ_ALWAYS_INLINE ~AlignedArray() { Dealloc(); }
+
+ void Dealloc() {
+ // If we fail this assert we'll need to uncomment the loop below to make
+ // sure dtors are properly invoked. If we do that, we should check that the
+ // comment about compiler dead code elimination is in fact true for all the
+ // compilers that we care about.
+ static_assert(std::is_trivially_destructible<T>::value,
+ "Destructors must be invoked for this type");
+#if 0
+ for (size_t i = 0; i < mCount; ++i) {
+ // Since we used the placement |operator new| function to construct the
+ // elements of this array we need to invoke their destructors manually.
+ // For types where the destructor does nothing the compiler's dead code
+ // elimination step should optimize this loop away.
+ mPtr[i].~T();
+ }
+#endif
+
+ free(mStorage);
+ mStorage = nullptr;
+ mPtr = nullptr;
+ }
+
+ MOZ_ALWAYS_INLINE void Realloc(size_t aCount, bool aZero = false) {
+ free(mStorage);
+ CheckedInt32 storageByteCount =
+ CheckedInt32(sizeof(T)) * aCount + (alignment - 1);
+ if (!storageByteCount.isValid()) {
+ mStorage = nullptr;
+ mPtr = nullptr;
+ mCount = 0;
+ return;
+ }
+ // We don't create an array of T here, since we don't want ctors to be
+ // invoked at the wrong places if we realign below.
+ if (aZero) {
+ // calloc can be more efficient than new[] for large chunks,
+ // so we use calloc/malloc/free for everything.
+ mStorage = static_cast<uint8_t*>(calloc(1u, storageByteCount.value()));
+ } else {
+ mStorage = static_cast<uint8_t*>(malloc(storageByteCount.value()));
+ }
+ if (!mStorage) {
+ mStorage = nullptr;
+ mPtr = nullptr;
+ mCount = 0;
+ return;
+ }
+ if (uintptr_t(mStorage) % alignment) {
+ // Our storage does not start at a <alignment>-byte boundary. Make sure
+ // mPtr does!
+ mPtr = (T*)(uintptr_t(mStorage) + alignment -
+ (uintptr_t(mStorage) % alignment));
+ } else {
+ mPtr = (T*)(mStorage);
+ }
+ // Now that mPtr is pointing to the aligned position we can use placement
+ // |operator new| to invoke any ctors at the correct positions. For types
+ // that have a no-op default constructor the compiler's dead code
+ // elimination step should optimize this away.
+ mPtr = new (mPtr) T[aCount];
+ mCount = aCount;
+ }
+
+ void Swap(AlignedArray<T, alignment>& aOther) {
+ std::swap(mPtr, aOther.mPtr);
+ std::swap(mStorage, aOther.mStorage);
+ std::swap(mCount, aOther.mCount);
+ }
+
+ size_t HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(mStorage);
+ }
+
+ MOZ_ALWAYS_INLINE operator T*() { return mPtr; }
+
+ T* mPtr;
+
+ private:
+ uint8_t* mStorage;
+ size_t mCount;
+};
+
+/**
+ * Returns aWidth * aBytesPerPixel increased, if necessary, so that it divides
+ * exactly into |alignment|.
+ *
+ * Note that currently |alignment| must be a power-of-2. If for some reason we
+ * want to support NPOT alignment we can revert back to this functions old
+ * implementation.
+ */
+template <int alignment>
+int32_t GetAlignedStride(int32_t aWidth, int32_t aBytesPerPixel) {
+ static_assert(alignment > 0 && (alignment & (alignment - 1)) == 0,
+ "This implementation currently require power-of-two alignment");
+ const int32_t mask = alignment - 1;
+ CheckedInt32 stride =
+ CheckedInt32(aWidth) * CheckedInt32(aBytesPerPixel) + CheckedInt32(mask);
+ if (stride.isValid()) {
+ return stride.value() & ~mask;
+ }
+ return 0;
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_TOOLS_H_ */
diff --git a/gfx/2d/Triangle.h b/gfx/2d/Triangle.h
new file mode 100644
index 0000000000..2e6fa2f54c
--- /dev/null
+++ b/gfx/2d/Triangle.h
@@ -0,0 +1,61 @@
+/* -*- 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_TRIANGLE_H
+#define MOZILLA_GFX_TRIANGLE_H
+
+#include <algorithm>
+#include <utility>
+
+#include "Point.h"
+#include "Rect.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * A simple triangle data structure.
+ */
+template <class Units, class F = Float>
+struct TriangleTyped {
+ PointTyped<Units, F> p1, p2, p3;
+
+ TriangleTyped() : p1(), p2(), p3() {}
+
+ TriangleTyped(PointTyped<Units, F> aP1, PointTyped<Units, F> aP2,
+ PointTyped<Units, F> aP3)
+ : p1(aP1), p2(aP2), p3(aP3) {}
+
+ RectTyped<Units, F> BoundingBox() const {
+ F minX = std::min(std::min(p1.x, p2.x), p3.x);
+ F maxX = std::max(std::max(p1.x, p2.x), p3.x);
+
+ F minY = std::min(std::min(p1.y, p2.y), p3.y);
+ F maxY = std::max(std::max(p1.y, p2.y), p3.y);
+
+ return RectTyped<Units, F>(minX, minY, maxX - minX, maxY - minY);
+ }
+};
+
+typedef TriangleTyped<UnknownUnits, Float> Triangle;
+
+template <class Units, class F = Float>
+struct TexturedTriangleTyped : public TriangleTyped<Units, F> {
+ explicit TexturedTriangleTyped(const TriangleTyped<Units, F>& aTriangle)
+ : TriangleTyped<Units, F>(aTriangle) {}
+
+ explicit TexturedTriangleTyped(TriangleTyped<Units, F>&& aTriangle)
+ : TriangleTyped<Units, F>(std::move(aTriangle)) {}
+
+ TriangleTyped<Units, F> textureCoords;
+};
+
+typedef TexturedTriangleTyped<UnknownUnits, Float> TexturedTriangle;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_TRIANGLE_H */
diff --git a/gfx/2d/Types.cpp b/gfx/2d/Types.cpp
new file mode 100644
index 0000000000..89de1182eb
--- /dev/null
+++ b/gfx/2d/Types.cpp
@@ -0,0 +1,102 @@
+/* -*- 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 "Types.h"
+
+#include "nsPrintfCString.h"
+
+#include <ostream>
+
+namespace mozilla {
+
+std::ostream& operator<<(std::ostream& aOut, const Side& aSide) {
+#define Emit(x) \
+ case x: \
+ aOut << #x; \
+ break
+
+ switch (aSide) {
+ Emit(eSideTop);
+ Emit(eSideBottom);
+ Emit(eSideLeft);
+ Emit(eSideRight);
+ default:
+ NS_ERROR("unknown side");
+ aOut << int(aSide);
+ break;
+ }
+
+#undef Emit
+ return aOut;
+}
+
+namespace gfx {
+
+std::ostream& operator<<(std::ostream& aOut, const SurfaceFormat& aFormat) {
+#define Emit(x) \
+ case x: \
+ aOut << #x; \
+ break
+
+ switch (aFormat) {
+ Emit(SurfaceFormat::B8G8R8A8);
+ Emit(SurfaceFormat::B8G8R8X8);
+ Emit(SurfaceFormat::R8G8B8A8);
+ Emit(SurfaceFormat::R8G8B8X8);
+ Emit(SurfaceFormat::A8R8G8B8);
+ Emit(SurfaceFormat::X8R8G8B8);
+ Emit(SurfaceFormat::R8G8B8);
+ Emit(SurfaceFormat::B8G8R8);
+ Emit(SurfaceFormat::R5G6B5_UINT16);
+ Emit(SurfaceFormat::A8);
+ Emit(SurfaceFormat::A16);
+ Emit(SurfaceFormat::R8G8);
+ Emit(SurfaceFormat::R16G16);
+ Emit(SurfaceFormat::YUV);
+ Emit(SurfaceFormat::NV12);
+ Emit(SurfaceFormat::P016);
+ Emit(SurfaceFormat::P010);
+ Emit(SurfaceFormat::YUV422);
+ Emit(SurfaceFormat::HSV);
+ Emit(SurfaceFormat::Lab);
+ Emit(SurfaceFormat::Depth);
+ default:
+ NS_ERROR("unknown surface format");
+ aOut << "???";
+ }
+
+#undef Emit
+
+ return aOut;
+}
+
+std::ostream& operator<<(std::ostream& aOut, const DeviceColor& aColor) {
+ aOut << nsPrintfCString("dev_rgba(%d, %d, %d, %f)", uint8_t(aColor.r * 255.f),
+ uint8_t(aColor.g * 255.f), uint8_t(aColor.b * 255.f),
+ aColor.a)
+ .get();
+ return aOut;
+}
+
+std::ostream& operator<<(std::ostream& aOut, const SamplingFilter& aFilter) {
+ switch (aFilter) {
+ case SamplingFilter::GOOD:
+ aOut << "SamplingFilter::GOOD";
+ break;
+ case SamplingFilter::LINEAR:
+ aOut << "SamplingFilter::LINEAR";
+ break;
+ case SamplingFilter::POINT:
+ aOut << "SamplingFilter::POINT";
+ break;
+ default:
+ aOut << "???";
+ }
+ return aOut;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h
new file mode 100644
index 0000000000..e6f73eac3a
--- /dev/null
+++ b/gfx/2d/Types.h
@@ -0,0 +1,1135 @@
+/* -*- 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_TYPES_H_
+#define MOZILLA_GFX_TYPES_H_
+
+#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM_CLASS_WITH_BASE
+#include "mozilla/EndianUtils.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/MacroArgs.h" // for MOZ_CONCAT
+#include "mozilla/TypedEnumBits.h"
+
+#include <iosfwd> // for ostream
+#include <stddef.h>
+#include <stdint.h>
+#include <optional>
+
+namespace mozilla {
+namespace gfx {
+
+typedef float Float;
+typedef double Double;
+
+enum class SurfaceType : int8_t {
+ DATA, /* Data surface - bitmap in memory */
+ D2D1_BITMAP, /* Surface wrapping a ID2D1Bitmap */
+ D2D1_DRAWTARGET, /* Surface made from a D2D draw target */
+ CAIRO, /* Surface wrapping a cairo surface */
+ CAIRO_IMAGE, /* Data surface wrapping a cairo image surface */
+ COREGRAPHICS_IMAGE, /* Surface wrapping a CoreGraphics Image */
+ COREGRAPHICS_CGCONTEXT, /* Surface wrapping a CG context */
+ SKIA, /* Surface wrapping a Skia bitmap */
+ D2D1_1_IMAGE, /* A D2D 1.1 ID2D1Image SourceSurface */
+ RECORDING, /* Surface used for recording */
+ DATA_SHARED, /* Data surface using shared memory */
+ DATA_RECYCLING_SHARED, /* Data surface using shared memory */
+ OFFSET, /* Offset */
+ DATA_ALIGNED, /* Data surface using aligned heap memory */
+ DATA_SHARED_WRAPPER, /* Shared memory mapped in from another process */
+ BLOB_IMAGE, /* Recorded blob image */
+ DATA_MAPPED, /* Data surface wrapping a ScopedMap */
+ WEBGL, /* Surface wrapping a DrawTargetWebgl texture */
+};
+
+enum class SurfaceFormat : int8_t {
+ // The following values are named to reflect layout of colors in memory, from
+ // lowest byte to highest byte. The 32-bit value layout depends on machine
+ // endianness.
+ // in-memory 32-bit LE value 32-bit BE value
+ B8G8R8A8, // [BB, GG, RR, AA] 0xAARRGGBB 0xBBGGRRAA
+ B8G8R8X8, // [BB, GG, RR, 00] 0x00RRGGBB 0xBBGGRR00
+ R8G8B8A8, // [RR, GG, BB, AA] 0xAABBGGRR 0xRRGGBBAA
+ R8G8B8X8, // [RR, GG, BB, 00] 0x00BBGGRR 0xRRGGBB00
+ A8R8G8B8, // [AA, RR, GG, BB] 0xBBGGRRAA 0xAARRGGBB
+ X8R8G8B8, // [00, RR, GG, BB] 0xBBGGRR00 0x00RRGGBB
+
+ R8G8B8,
+ B8G8R8,
+
+ // The _UINT16 suffix here indicates that the name reflects the layout when
+ // viewed as a uint16_t value. In memory these values are stored using native
+ // endianness.
+ R5G6B5_UINT16, // 0bRRRRRGGGGGGBBBBB
+
+ // This one is a single-byte, so endianness isn't an issue.
+ A8,
+ A16,
+
+ R8G8,
+ R16G16,
+
+ // These ones are their own special cases.
+ YUV,
+ NV12, // YUV 4:2:0 image with a plane of 8 bit Y samples followed by
+ // an interleaved U/V plane containing 8 bit 2x2 subsampled
+ // colour difference samples.
+ P016, // Similar to NV12, but with 16 bits plane values
+ P010, // Identical to P016 but the 6 least significant bits are 0.
+ // With DXGI in theory entirely compatible, however practice has
+ // shown that it's not the case.
+ YUV422, // Single plane YUV 4:2:2 interleaved as Y`0 Cb Y`1 Cr.
+ HSV,
+ Lab,
+ Depth,
+
+ // This represents the unknown format.
+ UNKNOWN, // TODO: Replace uses with Maybe<SurfaceFormat>.
+
+// The following values are endian-independent synonyms. The _UINT32 suffix
+// indicates that the name reflects the layout when viewed as a uint32_t
+// value.
+#if MOZ_LITTLE_ENDIAN()
+ A8R8G8B8_UINT32 = B8G8R8A8, // 0xAARRGGBB
+ X8R8G8B8_UINT32 = B8G8R8X8, // 0x00RRGGBB
+#elif MOZ_BIG_ENDIAN()
+ A8R8G8B8_UINT32 = A8R8G8B8, // 0xAARRGGBB
+ X8R8G8B8_UINT32 = X8R8G8B8, // 0x00RRGGBB
+#else
+# error "bad endianness"
+#endif
+
+ // The following values are OS and endian-independent synonyms.
+ //
+ // TODO(aosmond): When everything blocking bug 1581828 has been resolved, we
+ // can make this use R8B8G8A8 and R8B8G8X8 for non-Windows platforms.
+ OS_RGBA = A8R8G8B8_UINT32,
+ OS_RGBX = X8R8G8B8_UINT32
+};
+
+struct SurfaceFormatInfo {
+ bool hasColor;
+ bool hasAlpha;
+ bool isYuv;
+ std::optional<uint8_t> bytesPerPixel;
+};
+inline std::optional<SurfaceFormatInfo> Info(const SurfaceFormat aFormat) {
+ auto info = SurfaceFormatInfo{};
+
+ switch (aFormat) {
+ case SurfaceFormat::B8G8R8A8:
+ case SurfaceFormat::R8G8B8A8:
+ case SurfaceFormat::A8R8G8B8:
+ info.hasColor = true;
+ info.hasAlpha = true;
+ break;
+
+ case SurfaceFormat::B8G8R8X8:
+ case SurfaceFormat::R8G8B8X8:
+ case SurfaceFormat::X8R8G8B8:
+ case SurfaceFormat::R8G8B8:
+ case SurfaceFormat::B8G8R8:
+ case SurfaceFormat::R5G6B5_UINT16:
+ case SurfaceFormat::R8G8:
+ case SurfaceFormat::R16G16:
+ case SurfaceFormat::HSV:
+ case SurfaceFormat::Lab:
+ info.hasColor = true;
+ info.hasAlpha = false;
+ break;
+
+ case SurfaceFormat::A8:
+ case SurfaceFormat::A16:
+ info.hasColor = false;
+ info.hasAlpha = true;
+ break;
+
+ case SurfaceFormat::YUV:
+ case SurfaceFormat::NV12:
+ case SurfaceFormat::P016:
+ case SurfaceFormat::P010:
+ case SurfaceFormat::YUV422:
+ info.hasColor = true;
+ info.hasAlpha = false;
+ info.isYuv = true;
+ break;
+
+ case SurfaceFormat::Depth:
+ info.hasColor = false;
+ info.hasAlpha = false;
+ info.isYuv = false;
+ break;
+
+ case SurfaceFormat::UNKNOWN:
+ break;
+ }
+
+ // -
+ // bytesPerPixel
+
+ switch (aFormat) {
+ case SurfaceFormat::B8G8R8A8:
+ case SurfaceFormat::R8G8B8A8:
+ case SurfaceFormat::A8R8G8B8:
+ case SurfaceFormat::B8G8R8X8:
+ case SurfaceFormat::R8G8B8X8:
+ case SurfaceFormat::X8R8G8B8:
+ case SurfaceFormat::R16G16:
+ info.bytesPerPixel = 4;
+ break;
+
+ case SurfaceFormat::R8G8B8:
+ case SurfaceFormat::B8G8R8:
+ info.bytesPerPixel = 3;
+ break;
+
+ case SurfaceFormat::R5G6B5_UINT16:
+ case SurfaceFormat::R8G8:
+ case SurfaceFormat::A16:
+ case SurfaceFormat::Depth: // uint16_t
+ info.bytesPerPixel = 2;
+ break;
+
+ case SurfaceFormat::A8:
+ info.bytesPerPixel = 1;
+ break;
+
+ case SurfaceFormat::HSV:
+ case SurfaceFormat::Lab:
+ info.bytesPerPixel = 3 * sizeof(float);
+ break;
+
+ case SurfaceFormat::YUV:
+ case SurfaceFormat::NV12:
+ case SurfaceFormat::P016:
+ case SurfaceFormat::P010:
+ case SurfaceFormat::YUV422:
+ case SurfaceFormat::UNKNOWN:
+ break; // No bytesPerPixel per se.
+ }
+
+ // -
+
+ if (aFormat == SurfaceFormat::UNKNOWN) {
+ return {};
+ }
+ return info;
+}
+
+std::ostream& operator<<(std::ostream& aOut, const SurfaceFormat& aFormat);
+
+// Represents the bit-shifts required to access color channels when the layout
+// is viewed as a uint32_t value.
+enum class SurfaceFormatBit : uint32_t {
+#if MOZ_LITTLE_ENDIAN()
+ R8G8B8A8_R = 0,
+ R8G8B8A8_G = 8,
+ R8G8B8A8_B = 16,
+ R8G8B8A8_A = 24,
+#elif MOZ_BIG_ENDIAN()
+ R8G8B8A8_A = 0,
+ R8G8B8A8_B = 8,
+ R8G8B8A8_G = 16,
+ R8G8B8A8_R = 24,
+#else
+# error "bad endianness"
+#endif
+
+ // The following values are endian-independent for A8R8G8B8_UINT32.
+ A8R8G8B8_UINT32_B = 0,
+ A8R8G8B8_UINT32_G = 8,
+ A8R8G8B8_UINT32_R = 16,
+ A8R8G8B8_UINT32_A = 24,
+
+ // The following values are OS and endian-independent.
+ //
+ // TODO(aosmond): When everything blocking bug 1581828 has been resolved, we
+ // can make this use R8G8B8A8_X for non-Windows platforms.
+ OS_R = A8R8G8B8_UINT32_R,
+ OS_G = A8R8G8B8_UINT32_G,
+ OS_B = A8R8G8B8_UINT32_B,
+ OS_A = A8R8G8B8_UINT32_A,
+};
+
+inline uint32_t operator<<(uint8_t a, SurfaceFormatBit b) {
+ return a << static_cast<uint32_t>(b);
+}
+
+inline uint32_t operator>>(uint32_t a, SurfaceFormatBit b) {
+ return a >> static_cast<uint32_t>(b);
+}
+
+static inline int BytesPerPixel(SurfaceFormat aFormat) {
+ // TODO: return Info(aFormat).value().bytesPerPixel.value();
+ switch (aFormat) {
+ case SurfaceFormat::A8:
+ return 1;
+ case SurfaceFormat::R5G6B5_UINT16:
+ case SurfaceFormat::A16:
+ return 2;
+ case SurfaceFormat::R8G8B8:
+ case SurfaceFormat::B8G8R8:
+ return 3;
+ case SurfaceFormat::HSV:
+ case SurfaceFormat::Lab:
+ return 3 * sizeof(float);
+ case SurfaceFormat::Depth:
+ return sizeof(uint16_t);
+ default:
+ return 4;
+ }
+}
+
+inline bool IsOpaque(SurfaceFormat aFormat) {
+ // TODO: return Info(aFormat).value().hasAlpha;
+ switch (aFormat) {
+ case SurfaceFormat::B8G8R8X8:
+ case SurfaceFormat::R8G8B8X8:
+ case SurfaceFormat::X8R8G8B8:
+ case SurfaceFormat::R5G6B5_UINT16:
+ case SurfaceFormat::R8G8B8:
+ case SurfaceFormat::B8G8R8:
+ case SurfaceFormat::R8G8:
+ case SurfaceFormat::HSV:
+ case SurfaceFormat::Lab:
+ case SurfaceFormat::Depth:
+ case SurfaceFormat::YUV:
+ case SurfaceFormat::NV12:
+ case SurfaceFormat::P010:
+ case SurfaceFormat::P016:
+ case SurfaceFormat::YUV422:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// These are standardized Coding-independent Code Points
+// See [Rec. ITU-T H.273
+// (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en)
+//
+// We deliberately use an unscoped enum with fixed uint8_t representation since
+// all possible values [0, 255] are legal, but it's unwieldy to declare 200+
+// "RESERVED" enumeration values. Having a fixed underlying type avoids any
+// potential UB and avoids the need for a cast when passing these values across
+// FFI to functions like qcms_profile_create_cicp.
+namespace CICP {
+enum ColourPrimaries : uint8_t {
+ CP_RESERVED_MIN = 0, // 0, 3, [13, 21], [23, 255] are all reserved
+ CP_BT709 = 1,
+ CP_UNSPECIFIED = 2,
+ CP_BT470M = 4,
+ CP_BT470BG = 5,
+ CP_BT601 = 6,
+ CP_SMPTE240 = 7,
+ CP_GENERIC_FILM = 8,
+ CP_BT2020 = 9,
+ CP_XYZ = 10,
+ CP_SMPTE431 = 11,
+ CP_SMPTE432 = 12,
+ CP_EBU3213 = 22,
+};
+
+inline bool IsReserved(ColourPrimaries aIn) {
+ switch (aIn) {
+ case CP_BT709:
+ case CP_UNSPECIFIED:
+ case CP_BT470M:
+ case CP_BT470BG:
+ case CP_BT601:
+ case CP_SMPTE240:
+ case CP_GENERIC_FILM:
+ case CP_BT2020:
+ case CP_XYZ:
+ case CP_SMPTE431:
+ case CP_SMPTE432:
+ case CP_EBU3213:
+ return false;
+ default:
+ return true;
+ }
+}
+
+enum TransferCharacteristics : uint8_t {
+ TC_RESERVED_MIN = 0, // 0, 3, [19, 255] are all reserved
+ TC_BT709 = 1,
+ TC_UNSPECIFIED = 2,
+ TC_BT470M = 4,
+ TC_BT470BG = 5,
+ TC_BT601 = 6,
+ TC_SMPTE240 = 7,
+ TC_LINEAR = 8,
+ TC_LOG_100 = 9,
+ TC_LOG_100_SQRT10 = 10,
+ TC_IEC61966 = 11,
+ TC_BT_1361 = 12,
+ TC_SRGB = 13,
+ TC_BT2020_10BIT = 14,
+ TC_BT2020_12BIT = 15,
+ TC_SMPTE2084 = 16,
+ TC_SMPTE428 = 17,
+ TC_HLG = 18,
+};
+
+inline bool IsReserved(TransferCharacteristics aIn) {
+ switch (aIn) {
+ case TC_BT709:
+ case TC_UNSPECIFIED:
+ case TC_BT470M:
+ case TC_BT470BG:
+ case TC_BT601:
+ case TC_SMPTE240:
+ case TC_LINEAR:
+ case TC_LOG_100:
+ case TC_LOG_100_SQRT10:
+ case TC_IEC61966:
+ case TC_BT_1361:
+ case TC_SRGB:
+ case TC_BT2020_10BIT:
+ case TC_BT2020_12BIT:
+ case TC_SMPTE2084:
+ case TC_SMPTE428:
+ case TC_HLG:
+ return false;
+ default:
+ return true;
+ }
+}
+
+enum MatrixCoefficients : uint8_t {
+ MC_IDENTITY = 0,
+ MC_BT709 = 1,
+ MC_UNSPECIFIED = 2,
+ MC_RESERVED_MIN = 3, // 3, [15, 255] are all reserved
+ MC_FCC = 4,
+ MC_BT470BG = 5,
+ MC_BT601 = 6,
+ MC_SMPTE240 = 7,
+ MC_YCGCO = 8,
+ MC_BT2020_NCL = 9,
+ MC_BT2020_CL = 10,
+ MC_SMPTE2085 = 11,
+ MC_CHROMAT_NCL = 12,
+ MC_CHROMAT_CL = 13,
+ MC_ICTCP = 14,
+};
+
+inline bool IsReserved(MatrixCoefficients aIn) {
+ switch (aIn) {
+ case MC_IDENTITY:
+ case MC_BT709:
+ case MC_UNSPECIFIED:
+ case MC_RESERVED_MIN:
+ case MC_FCC:
+ case MC_BT470BG:
+ case MC_BT601:
+ case MC_SMPTE240:
+ case MC_YCGCO:
+ case MC_BT2020_NCL:
+ case MC_BT2020_CL:
+ case MC_SMPTE2085:
+ case MC_CHROMAT_NCL:
+ case MC_CHROMAT_CL:
+ case MC_ICTCP:
+ return false;
+ default:
+ return true;
+ }
+}
+} // namespace CICP
+
+// The matrix coeffiecients used for YUV to RGB conversion.
+enum class YUVColorSpace : uint8_t {
+ BT601,
+ BT709,
+ BT2020,
+ Identity, // Todo: s/YUVColorSpace/ColorSpace/, s/Identity/SRGB/
+ Default = BT709,
+ _First = BT601,
+ _Last = Identity,
+};
+
+enum class ColorDepth : uint8_t {
+ COLOR_8,
+ COLOR_10,
+ COLOR_12,
+ COLOR_16,
+ _First = COLOR_8,
+ _Last = COLOR_16,
+};
+
+enum class TransferFunction : uint8_t {
+ BT709,
+ SRGB,
+ PQ,
+ HLG,
+ _First = BT709,
+ _Last = HLG,
+ Default = BT709,
+};
+
+enum class ColorRange : uint8_t {
+ LIMITED,
+ FULL,
+ _First = LIMITED,
+ _Last = FULL,
+};
+
+// Really "YcbcrColorColorSpace"
+enum class YUVRangedColorSpace : uint8_t {
+ BT601_Narrow = 0,
+ BT601_Full,
+ BT709_Narrow,
+ BT709_Full,
+ BT2020_Narrow,
+ BT2020_Full,
+ GbrIdentity,
+
+ _First = BT601_Narrow,
+ _Last = GbrIdentity,
+ Default = BT709_Narrow,
+};
+
+// I can either come up with a longer "very clever" name that doesn't conflict
+// with FilterSupport.h, embrace and expand FilterSupport, or rename the old
+// one.
+// Some times Worse Is Better.
+enum class ColorSpace2 : uint8_t {
+ Display,
+ UNKNOWN = Display, // We feel sufficiently bad about this TODO.
+ SRGB,
+ DISPLAY_P3,
+ BT601_525, // aka smpte170m NTSC
+ BT709, // Same gamut as SRGB, but different gamma.
+ BT601_625 =
+ BT709, // aka bt470bg PAL. Basically BT709, just Xg is 0.290 not 0.300.
+ BT2020,
+ _First = Display,
+ _Last = BT2020,
+};
+
+inline ColorSpace2 ToColorSpace2(const YUVColorSpace in) {
+ switch (in) {
+ case YUVColorSpace::BT601:
+ return ColorSpace2::BT601_525;
+ case YUVColorSpace::BT709:
+ return ColorSpace2::BT709;
+ case YUVColorSpace::BT2020:
+ return ColorSpace2::BT2020;
+ case YUVColorSpace::Identity:
+ return ColorSpace2::SRGB;
+ }
+ MOZ_ASSERT_UNREACHABLE();
+}
+
+inline YUVColorSpace ToYUVColorSpace(const ColorSpace2 in) {
+ switch (in) {
+ case ColorSpace2::BT601_525:
+ return YUVColorSpace::BT601;
+ case ColorSpace2::BT709:
+ return YUVColorSpace::BT709;
+ case ColorSpace2::BT2020:
+ return YUVColorSpace::BT2020;
+ case ColorSpace2::SRGB:
+ return YUVColorSpace::Identity;
+
+ case ColorSpace2::UNKNOWN:
+ case ColorSpace2::DISPLAY_P3:
+ MOZ_CRASH("Bad ColorSpace2 for ToYUVColorSpace");
+ }
+ MOZ_ASSERT_UNREACHABLE();
+}
+
+struct FromYUVRangedColorSpaceT final {
+ const YUVColorSpace space;
+ const ColorRange range;
+};
+
+inline FromYUVRangedColorSpaceT FromYUVRangedColorSpace(
+ const YUVRangedColorSpace s) {
+ switch (s) {
+ case YUVRangedColorSpace::BT601_Narrow:
+ return {YUVColorSpace::BT601, ColorRange::LIMITED};
+ case YUVRangedColorSpace::BT601_Full:
+ return {YUVColorSpace::BT601, ColorRange::FULL};
+
+ case YUVRangedColorSpace::BT709_Narrow:
+ return {YUVColorSpace::BT709, ColorRange::LIMITED};
+ case YUVRangedColorSpace::BT709_Full:
+ return {YUVColorSpace::BT709, ColorRange::FULL};
+
+ case YUVRangedColorSpace::BT2020_Narrow:
+ return {YUVColorSpace::BT2020, ColorRange::LIMITED};
+ case YUVRangedColorSpace::BT2020_Full:
+ return {YUVColorSpace::BT2020, ColorRange::FULL};
+
+ case YUVRangedColorSpace::GbrIdentity:
+ return {YUVColorSpace::Identity, ColorRange::FULL};
+ }
+ MOZ_CRASH("bad YUVRangedColorSpace");
+}
+
+// Todo: This should go in the CPP.
+inline YUVRangedColorSpace ToYUVRangedColorSpace(const YUVColorSpace space,
+ const ColorRange range) {
+ bool narrow;
+ switch (range) {
+ case ColorRange::FULL:
+ narrow = false;
+ break;
+ case ColorRange::LIMITED:
+ narrow = true;
+ break;
+ }
+
+ switch (space) {
+ case YUVColorSpace::Identity:
+ MOZ_ASSERT(range == ColorRange::FULL);
+ return YUVRangedColorSpace::GbrIdentity;
+
+ case YUVColorSpace::BT601:
+ return narrow ? YUVRangedColorSpace::BT601_Narrow
+ : YUVRangedColorSpace::BT601_Full;
+
+ case YUVColorSpace::BT709:
+ return narrow ? YUVRangedColorSpace::BT709_Narrow
+ : YUVRangedColorSpace::BT709_Full;
+
+ case YUVColorSpace::BT2020:
+ return narrow ? YUVRangedColorSpace::BT2020_Narrow
+ : YUVRangedColorSpace::BT2020_Full;
+ }
+ MOZ_CRASH("bad YUVColorSpace");
+}
+
+template <typename DescriptorT>
+inline YUVRangedColorSpace GetYUVRangedColorSpace(const DescriptorT& d) {
+ return ToYUVRangedColorSpace(d.yUVColorSpace(), d.colorRange());
+}
+
+static inline SurfaceFormat SurfaceFormatForColorDepth(ColorDepth aColorDepth) {
+ SurfaceFormat format = SurfaceFormat::A8;
+ switch (aColorDepth) {
+ case ColorDepth::COLOR_8:
+ break;
+ case ColorDepth::COLOR_10:
+ case ColorDepth::COLOR_12:
+ case ColorDepth::COLOR_16:
+ format = SurfaceFormat::A16;
+ break;
+ }
+ return format;
+}
+
+static inline uint8_t BitDepthForColorDepth(ColorDepth aColorDepth) {
+ uint8_t depth = 8;
+ switch (aColorDepth) {
+ case ColorDepth::COLOR_8:
+ break;
+ case ColorDepth::COLOR_10:
+ depth = 10;
+ break;
+ case ColorDepth::COLOR_12:
+ depth = 12;
+ break;
+ case ColorDepth::COLOR_16:
+ depth = 16;
+ break;
+ }
+ return depth;
+}
+
+static inline ColorDepth ColorDepthForBitDepth(uint8_t aBitDepth) {
+ ColorDepth depth = ColorDepth::COLOR_8;
+ switch (aBitDepth) {
+ case 8:
+ break;
+ case 10:
+ depth = ColorDepth::COLOR_10;
+ break;
+ case 12:
+ depth = ColorDepth::COLOR_12;
+ break;
+ case 16:
+ depth = ColorDepth::COLOR_16;
+ break;
+ }
+ return depth;
+}
+
+// 10 and 12 bits color depth image are using 16 bits integers for storage
+// As such we need to rescale the value from 10 or 12 bits to 16.
+static inline uint32_t RescalingFactorForColorDepth(ColorDepth aColorDepth) {
+ uint32_t factor = 1;
+ switch (aColorDepth) {
+ case ColorDepth::COLOR_8:
+ break;
+ case ColorDepth::COLOR_10:
+ factor = 64;
+ break;
+ case ColorDepth::COLOR_12:
+ factor = 16;
+ break;
+ case ColorDepth::COLOR_16:
+ break;
+ }
+ return factor;
+}
+
+enum class ChromaSubsampling : uint8_t {
+ FULL,
+ HALF_WIDTH,
+ HALF_WIDTH_AND_HEIGHT,
+ _First = FULL,
+ _Last = HALF_WIDTH_AND_HEIGHT,
+};
+
+template <typename T>
+static inline T ChromaSize(const T& aYSize, ChromaSubsampling aSubsampling) {
+ switch (aSubsampling) {
+ case ChromaSubsampling::FULL:
+ return aYSize;
+ case ChromaSubsampling::HALF_WIDTH:
+ return T((aYSize.width + 1) / 2, aYSize.height);
+ case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
+ return T((aYSize.width + 1) / 2, (aYSize.height + 1) / 2);
+ }
+ MOZ_CRASH("bad ChromaSubsampling");
+}
+
+enum class FilterType : int8_t {
+ BLEND = 0,
+ TRANSFORM,
+ MORPHOLOGY,
+ COLOR_MATRIX,
+ FLOOD,
+ TILE,
+ TABLE_TRANSFER,
+ DISCRETE_TRANSFER,
+ LINEAR_TRANSFER,
+ GAMMA_TRANSFER,
+ CONVOLVE_MATRIX,
+ DISPLACEMENT_MAP,
+ TURBULENCE,
+ ARITHMETIC_COMBINE,
+ COMPOSITE,
+ DIRECTIONAL_BLUR,
+ GAUSSIAN_BLUR,
+ POINT_DIFFUSE,
+ POINT_SPECULAR,
+ SPOT_DIFFUSE,
+ SPOT_SPECULAR,
+ DISTANT_DIFFUSE,
+ DISTANT_SPECULAR,
+ CROP,
+ PREMULTIPLY,
+ UNPREMULTIPLY,
+ OPACITY
+};
+
+enum class DrawTargetType : int8_t {
+ SOFTWARE_RASTER = 0,
+ HARDWARE_RASTER,
+ VECTOR
+};
+
+enum class BackendType : int8_t {
+ NONE = 0,
+ DIRECT2D, // Used for version independent D2D objects.
+ CAIRO,
+ SKIA,
+ RECORDING,
+ DIRECT2D1_1,
+ WEBRENDER_TEXT,
+ WEBGL,
+
+ // Add new entries above this line.
+ BACKEND_LAST
+};
+
+enum class FontType : int8_t {
+ DWRITE,
+ GDI,
+ MAC,
+ FONTCONFIG,
+ FREETYPE,
+ UNKNOWN
+};
+
+enum class NativeSurfaceType : int8_t {
+ D3D10_TEXTURE,
+ CAIRO_CONTEXT,
+ CGCONTEXT,
+ CGCONTEXT_ACCELERATED,
+ OPENGL_TEXTURE,
+ WEBGL_CONTEXT
+};
+
+enum class FontStyle : int8_t { NORMAL, ITALIC, BOLD, BOLD_ITALIC };
+
+enum class FontHinting : int8_t { NONE, LIGHT, NORMAL, FULL };
+
+enum class CompositionOp : int8_t {
+ OP_CLEAR,
+ OP_OVER,
+ OP_ADD,
+ OP_ATOP,
+ OP_OUT,
+ OP_IN,
+ OP_SOURCE,
+ OP_DEST_IN,
+ OP_DEST_OUT,
+ OP_DEST_OVER,
+ OP_DEST_ATOP,
+ OP_XOR,
+ OP_MULTIPLY,
+ OP_SCREEN,
+ OP_OVERLAY,
+ OP_DARKEN,
+ OP_LIGHTEN,
+ OP_COLOR_DODGE,
+ OP_COLOR_BURN,
+ OP_HARD_LIGHT,
+ OP_SOFT_LIGHT,
+ OP_DIFFERENCE,
+ OP_EXCLUSION,
+ OP_HUE,
+ OP_SATURATION,
+ OP_COLOR,
+ OP_LUMINOSITY,
+ OP_COUNT
+};
+
+enum class Axis : int8_t { X_AXIS, Y_AXIS, BOTH };
+
+enum class ExtendMode : int8_t {
+ CLAMP, // Do not repeat
+ REPEAT, // Repeat in both axis
+ REPEAT_X, // Only X axis
+ REPEAT_Y, // Only Y axis
+ REFLECT // Mirror the image
+};
+
+enum class FillRule : int8_t { FILL_WINDING, FILL_EVEN_ODD };
+
+enum class AntialiasMode : int8_t { NONE, GRAY, SUBPIXEL, DEFAULT };
+
+// See https://en.wikipedia.org/wiki/Texture_filtering
+enum class SamplingFilter : int8_t {
+ GOOD,
+ LINEAR,
+ POINT,
+ SENTINEL // one past the last valid value
+};
+
+std::ostream& operator<<(std::ostream& aOut, const SamplingFilter& aFilter);
+
+// clang-format off
+MOZ_DEFINE_ENUM_CLASS_WITH_BASE(PatternType, int8_t, (
+ COLOR,
+ SURFACE,
+ LINEAR_GRADIENT,
+ RADIAL_GRADIENT,
+ CONIC_GRADIENT
+));
+// clang-format on
+
+enum class JoinStyle : int8_t {
+ BEVEL,
+ ROUND,
+ MITER, //!< Mitered if within the miter limit, else, if the backed supports
+ //!< it (D2D), the miter is clamped. If the backend does not support
+ //!< miter clamping the behavior is as for MITER_OR_BEVEL.
+ MITER_OR_BEVEL //!< Mitered if within the miter limit, else beveled.
+};
+
+enum class CapStyle : int8_t { BUTT, ROUND, SQUARE };
+
+enum class SamplingBounds : int8_t { UNBOUNDED, BOUNDED };
+
+// Moz2d version for SVG mask types
+enum class LuminanceType : int8_t {
+ LUMINANCE,
+ LINEARRGB,
+};
+
+/* Color is stored in non-premultiplied form in sRGB color space */
+struct sRGBColor {
+ public:
+ constexpr sRGBColor() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {}
+ constexpr sRGBColor(Float aR, Float aG, Float aB, Float aA)
+ : r(aR), g(aG), b(aB), a(aA) {}
+ constexpr sRGBColor(Float aR, Float aG, Float aB)
+ : r(aR), g(aG), b(aB), a(1.0f) {}
+
+ static constexpr sRGBColor White(float aA) {
+ return sRGBColor(1.f, 1.f, 1.f, aA);
+ }
+
+ static constexpr sRGBColor Black(float aA) {
+ return sRGBColor(0.f, 0.f, 0.f, aA);
+ }
+
+ static constexpr sRGBColor OpaqueWhite() { return White(1.f); }
+
+ static constexpr sRGBColor OpaqueBlack() { return Black(1.f); }
+
+ static constexpr sRGBColor FromU8(uint8_t aR, uint8_t aG, uint8_t aB,
+ uint8_t aA) {
+ return sRGBColor(float(aR) / 255.f, float(aG) / 255.f, float(aB) / 255.f,
+ float(aA) / 255.f);
+ }
+
+ static constexpr sRGBColor FromABGR(uint32_t aColor) {
+ return sRGBColor(((aColor >> 0) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 16) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 24) & 0xff) * (1.0f / 255.0f));
+ }
+
+ // The "Unusual" prefix is to avoid unintentionally using this function when
+ // FromABGR(), which is much more common, is needed.
+ static constexpr sRGBColor UnusualFromARGB(uint32_t aColor) {
+ return sRGBColor(((aColor >> 16) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 0) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 24) & 0xff) * (1.0f / 255.0f));
+ }
+
+ constexpr uint32_t ToABGR() const {
+ return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 |
+ uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
+ }
+
+ constexpr sRGBColor Premultiplied() const {
+ return sRGBColor(r * a, g * a, b * a, a);
+ }
+
+ constexpr sRGBColor Unpremultiplied() const {
+ return a > 0.f ? sRGBColor(r / a, g / a, b / a, a) : *this;
+ }
+
+ // The "Unusual" prefix is to avoid unintentionally using this function when
+ // ToABGR(), which is much more common, is needed.
+ uint32_t UnusualToARGB() const {
+ return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 |
+ uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
+ }
+
+ bool operator==(const sRGBColor& aColor) const {
+ return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a;
+ }
+
+ bool operator!=(const sRGBColor& aColor) const { return !(*this == aColor); }
+
+ Float r, g, b, a;
+};
+
+/* Color is stored in non-premultiplied form in device color space */
+struct DeviceColor {
+ public:
+ DeviceColor() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {}
+ DeviceColor(Float aR, Float aG, Float aB, Float aA)
+ : r(aR), g(aG), b(aB), a(aA) {}
+ DeviceColor(Float aR, Float aG, Float aB) : r(aR), g(aG), b(aB), a(1.0f) {}
+
+ /* The following Mask* variants are helpers used to make it clear when a
+ * particular color is being used for masking purposes. These masks should
+ * never be colored managed. */
+ static DeviceColor Mask(float aC, float aA) {
+ return DeviceColor(aC, aC, aC, aA);
+ }
+
+ static DeviceColor MaskWhite(float aA) { return Mask(1.f, aA); }
+
+ static DeviceColor MaskBlack(float aA) { return Mask(0.f, aA); }
+
+ static DeviceColor MaskOpaqueWhite() { return MaskWhite(1.f); }
+
+ static DeviceColor MaskOpaqueBlack() { return MaskBlack(1.f); }
+
+ static DeviceColor FromU8(uint8_t aR, uint8_t aG, uint8_t aB, uint8_t aA) {
+ return DeviceColor(float(aR) / 255.f, float(aG) / 255.f, float(aB) / 255.f,
+ float(aA) / 255.f);
+ }
+
+ static DeviceColor FromABGR(uint32_t aColor) {
+ DeviceColor newColor(((aColor >> 0) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 16) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 24) & 0xff) * (1.0f / 255.0f));
+
+ return newColor;
+ }
+
+ // The "Unusual" prefix is to avoid unintentionally using this function when
+ // FromABGR(), which is much more common, is needed.
+ static DeviceColor UnusualFromARGB(uint32_t aColor) {
+ DeviceColor newColor(((aColor >> 16) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 0) & 0xff) * (1.0f / 255.0f),
+ ((aColor >> 24) & 0xff) * (1.0f / 255.0f));
+
+ return newColor;
+ }
+
+ uint32_t ToABGR() const {
+ return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 |
+ uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
+ }
+
+ // The "Unusual" prefix is to avoid unintentionally using this function when
+ // ToABGR(), which is much more common, is needed.
+ uint32_t UnusualToARGB() const {
+ return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 |
+ uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
+ }
+
+ bool operator==(const DeviceColor& aColor) const {
+ return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a;
+ }
+
+ bool operator!=(const DeviceColor& aColor) const {
+ return !(*this == aColor);
+ }
+
+ friend std::ostream& operator<<(std::ostream& aOut,
+ const DeviceColor& aColor);
+
+ Float r, g, b, a;
+};
+
+struct GradientStop {
+ bool operator<(const GradientStop& aOther) const {
+ return offset < aOther.offset;
+ }
+
+ Float offset;
+ DeviceColor color;
+};
+
+enum class JobStatus { Complete, Wait, Yield, Error };
+
+} // namespace gfx
+} // namespace mozilla
+
+// XXX: temporary
+typedef mozilla::gfx::SurfaceFormat gfxImageFormat;
+
+#if defined(XP_WIN) && defined(MOZ_GFX)
+# ifdef GFX2D_INTERNAL
+# define GFX2D_API __declspec(dllexport)
+# else
+# define GFX2D_API __declspec(dllimport)
+# endif
+#else
+# define GFX2D_API
+#endif
+
+namespace mozilla {
+
+// Side constants for use in various places.
+enum Side : uint8_t { eSideTop, eSideRight, eSideBottom, eSideLeft };
+
+std::ostream& operator<<(std::ostream&, const mozilla::Side&);
+
+constexpr auto AllPhysicalSides() {
+ return mozilla::MakeInclusiveEnumeratedRange(eSideTop, eSideLeft);
+}
+
+enum class SideBits {
+ eNone = 0,
+ eTop = 1 << eSideTop,
+ eRight = 1 << eSideRight,
+ eBottom = 1 << eSideBottom,
+ eLeft = 1 << eSideLeft,
+ eTopBottom = SideBits::eTop | SideBits::eBottom,
+ eLeftRight = SideBits::eLeft | SideBits::eRight,
+ eAll = SideBits::eTopBottom | SideBits::eLeftRight
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SideBits)
+
+inline constexpr SideBits SideToSideBit(mozilla::Side aSide) {
+ return SideBits(1 << aSide);
+}
+
+enum Corner : uint8_t {
+ // This order is important!
+ eCornerTopLeft = 0,
+ eCornerTopRight = 1,
+ eCornerBottomRight = 2,
+ eCornerBottomLeft = 3
+};
+
+// RectCornerRadii::radii depends on this value. It is not being added to
+// Corner because we want to lift the responsibility to handle it in the
+// switch-case.
+constexpr int eCornerCount = 4;
+
+constexpr auto AllPhysicalCorners() {
+ return mozilla::MakeInclusiveEnumeratedRange(eCornerTopLeft,
+ eCornerBottomLeft);
+}
+
+// Indices into "half corner" arrays (nsStyleCorners e.g.)
+enum HalfCorner : uint8_t {
+ // This order is important!
+ eCornerTopLeftX = 0,
+ eCornerTopLeftY = 1,
+ eCornerTopRightX = 2,
+ eCornerTopRightY = 3,
+ eCornerBottomRightX = 4,
+ eCornerBottomRightY = 5,
+ eCornerBottomLeftX = 6,
+ eCornerBottomLeftY = 7
+};
+
+constexpr auto AllPhysicalHalfCorners() {
+ return mozilla::MakeInclusiveEnumeratedRange(eCornerTopLeftX,
+ eCornerBottomLeftY);
+}
+
+// The result of these conversion functions are exhaustively checked in
+// nsFrame.cpp, which also serves as usage examples.
+
+constexpr bool HalfCornerIsX(HalfCorner aHalfCorner) {
+ return !(aHalfCorner % 2);
+}
+
+constexpr Corner HalfToFullCorner(HalfCorner aHalfCorner) {
+ return Corner(aHalfCorner / 2);
+}
+
+constexpr HalfCorner FullToHalfCorner(Corner aCorner, bool aIsVertical) {
+ return HalfCorner(aCorner * 2 + aIsVertical);
+}
+
+constexpr bool SideIsVertical(mozilla::Side aSide) { return aSide % 2; }
+
+// @param aIsSecond when true, return the clockwise second of the two
+// corners associated with aSide. For example, with aSide = eSideBottom the
+// result is eCornerBottomRight when aIsSecond is false, and
+// eCornerBottomLeft when aIsSecond is true.
+constexpr Corner SideToFullCorner(mozilla::Side aSide, bool aIsSecond) {
+ return Corner((aSide + aIsSecond) % 4);
+}
+
+// @param aIsSecond see SideToFullCorner.
+// @param aIsParallel return the half-corner that is parallel with aSide
+// when aIsParallel is true. For example with aSide=eSideTop, aIsSecond=true
+// the result is eCornerTopRightX when aIsParallel is true, and
+// eCornerTopRightY when aIsParallel is false (because "X" is parallel with
+// eSideTop/eSideBottom, similarly "Y" is parallel with
+// eSideLeft/eSideRight)
+constexpr HalfCorner SideToHalfCorner(mozilla::Side aSide, bool aIsSecond,
+ bool aIsParallel) {
+ return HalfCorner(((aSide + aIsSecond) * 2 + (aSide + !aIsParallel) % 2) % 8);
+}
+
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_TYPES_H_ */
diff --git a/gfx/2d/UnscaledFontDWrite.h b/gfx/2d/UnscaledFontDWrite.h
new file mode 100644
index 0000000000..feed553790
--- /dev/null
+++ b/gfx/2d/UnscaledFontDWrite.h
@@ -0,0 +1,59 @@
+/* -*- 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_UNSCALEDFONTDWRITE_H_
+#define MOZILLA_GFX_UNSCALEDFONTDWRITE_H_
+
+#include <dwrite.h>
+
+#include "2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class ScaledFontDWrite;
+
+class UnscaledFontDWrite final : public UnscaledFont {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontDWrite, override)
+ UnscaledFontDWrite(const RefPtr<IDWriteFontFace>& aFontFace,
+ const RefPtr<IDWriteFont>& aFont)
+ : mFontFace(aFontFace), mFont(aFont) {}
+
+ FontType GetType() const override { return FontType::DWRITE; }
+
+ const RefPtr<IDWriteFontFace>& GetFontFace() const { return mFontFace; }
+ const RefPtr<IDWriteFont>& GetFont() const { return mFont; }
+
+ bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override;
+
+ already_AddRefed<ScaledFont> CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) override;
+
+ already_AddRefed<ScaledFont> CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) override;
+
+ bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override;
+
+ static already_AddRefed<UnscaledFont> CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex);
+
+ private:
+ bool InitBold();
+
+ RefPtr<IDWriteFontFace> mFontFace;
+ RefPtr<IDWriteFontFace> mFontFaceBold;
+ RefPtr<IDWriteFont> mFont;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_UNSCALEDFONTDWRITE_H_ */
diff --git a/gfx/2d/UnscaledFontFreeType.cpp b/gfx/2d/UnscaledFontFreeType.cpp
new file mode 100644
index 0000000000..97af653ed6
--- /dev/null
+++ b/gfx/2d/UnscaledFontFreeType.cpp
@@ -0,0 +1,242 @@
+/* -*- 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 "UnscaledFontFreeType.h"
+#include "NativeFontResourceFreeType.h"
+#include "ScaledFontFreeType.h"
+#include "Logging.h"
+#include "StackArray.h"
+
+#include FT_MULTIPLE_MASTERS_H
+#include FT_TRUETYPE_TABLES_H
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+namespace mozilla::gfx {
+
+bool UnscaledFontFreeType::GetFontFileData(FontFileDataOutput aDataCallback,
+ void* aBaton) {
+ if (!mFile.empty()) {
+ int fd = open(mFile.c_str(), O_RDONLY);
+ if (fd < 0) {
+ return false;
+ }
+ struct stat buf;
+ if (fstat(fd, &buf) < 0 ||
+ // Don't erroneously read directories as files.
+ !S_ISREG(buf.st_mode) ||
+ // Verify the file size fits in a uint32_t.
+ buf.st_size <= 0 || off_t(uint32_t(buf.st_size)) != buf.st_size) {
+ close(fd);
+ return false;
+ }
+ uint32_t length = buf.st_size;
+ uint8_t* fontData = reinterpret_cast<uint8_t*>(
+ mmap(nullptr, length, PROT_READ, MAP_PRIVATE, fd, 0));
+ close(fd);
+ if (fontData == MAP_FAILED) {
+ return false;
+ }
+ aDataCallback(fontData, length, mIndex, aBaton);
+ munmap(fontData, length);
+ return true;
+ }
+
+ bool success = false;
+ FT_ULong length = 0;
+ // Request the SFNT file. This may not always succeed for all font types.
+ if (FT_Load_Sfnt_Table(mFace->GetFace(), 0, 0, nullptr, &length) ==
+ FT_Err_Ok) {
+ uint8_t* fontData = new uint8_t[length];
+ if (FT_Load_Sfnt_Table(mFace->GetFace(), 0, 0, fontData, &length) ==
+ FT_Err_Ok) {
+ aDataCallback(fontData, length, 0, aBaton);
+ success = true;
+ }
+ delete[] fontData;
+ }
+ return success;
+}
+
+bool UnscaledFontFreeType::GetFontDescriptor(FontDescriptorOutput aCb,
+ void* aBaton) {
+ if (mFile.empty()) {
+ return false;
+ }
+
+ aCb(reinterpret_cast<const uint8_t*>(mFile.data()), mFile.size(), mIndex,
+ aBaton);
+ return true;
+}
+
+RefPtr<SharedFTFace> UnscaledFontFreeType::InitFace() {
+ if (mFace) {
+ return mFace;
+ }
+ if (mFile.empty()) {
+ return nullptr;
+ }
+ mFace = Factory::NewSharedFTFace(nullptr, mFile.c_str(), mIndex);
+ if (!mFace) {
+ gfxWarning() << "Failed initializing FreeType face from filename";
+ return nullptr;
+ }
+ return mFace;
+}
+
+void UnscaledFontFreeType::GetVariationSettingsFromFace(
+ std::vector<FontVariation>* aVariations, FT_Face aFace) {
+ if (!aFace || !(aFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
+ return;
+ }
+
+ typedef FT_Error (*GetVarFunc)(FT_Face, FT_MM_Var**);
+ typedef FT_Error (*DoneVarFunc)(FT_Library, FT_MM_Var*);
+ typedef FT_Error (*GetVarDesignCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
+#if MOZ_TREE_FREETYPE
+ GetVarFunc getVar = &FT_Get_MM_Var;
+ DoneVarFunc doneVar = &FT_Done_MM_Var;
+ GetVarDesignCoordsFunc getCoords = &FT_Get_Var_Design_Coordinates;
+#else
+ static GetVarFunc getVar;
+ static DoneVarFunc doneVar;
+ static GetVarDesignCoordsFunc getCoords;
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ getVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var");
+ doneVar = (DoneVarFunc)dlsym(RTLD_DEFAULT, "FT_Done_MM_Var");
+ getCoords = (GetVarDesignCoordsFunc)dlsym(RTLD_DEFAULT,
+ "FT_Get_Var_Design_Coordinates");
+ }
+ if (!getVar || !getCoords) {
+ return;
+ }
+#endif
+
+ FT_MM_Var* mmVar = nullptr;
+ if ((*getVar)(aFace, &mmVar) == FT_Err_Ok) {
+ aVariations->reserve(mmVar->num_axis);
+ StackArray<FT_Fixed, 32> coords(mmVar->num_axis);
+ if ((*getCoords)(aFace, mmVar->num_axis, coords.data()) == FT_Err_Ok) {
+ bool changed = false;
+ for (uint32_t i = 0; i < mmVar->num_axis; i++) {
+ if (coords[i] != mmVar->axis[i].def) {
+ changed = true;
+ }
+ aVariations->push_back(FontVariation{uint32_t(mmVar->axis[i].tag),
+ float(coords[i] / 65536.0)});
+ }
+ if (!changed) {
+ aVariations->clear();
+ }
+ }
+ if (doneVar) {
+ (*doneVar)(aFace->glyph->library, mmVar);
+ } else {
+ free(mmVar);
+ }
+ }
+}
+
+void UnscaledFontFreeType::ApplyVariationsToFace(
+ const FontVariation* aVariations, uint32_t aNumVariations, FT_Face aFace) {
+ if (!aFace || !(aFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
+ return;
+ }
+
+ typedef FT_Error (*SetVarDesignCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
+#ifdef MOZ_TREE_FREETYPE
+ SetVarDesignCoordsFunc setCoords = &FT_Set_Var_Design_Coordinates;
+#else
+ typedef FT_Error (*SetVarDesignCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
+ static SetVarDesignCoordsFunc setCoords;
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ setCoords = (SetVarDesignCoordsFunc)dlsym(RTLD_DEFAULT,
+ "FT_Set_Var_Design_Coordinates");
+ }
+ if (!setCoords) {
+ return;
+ }
+#endif
+
+ StackArray<FT_Fixed, 32> coords(aNumVariations);
+ for (uint32_t i = 0; i < aNumVariations; i++) {
+ coords[i] = std::round(aVariations[i].mValue * 65536.0f);
+ }
+ if ((*setCoords)(aFace, aNumVariations, coords.data()) != FT_Err_Ok) {
+ // ignore the problem?
+ }
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+
+already_AddRefed<ScaledFont> UnscaledFontFreeType::CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) {
+ if (aInstanceDataLength < sizeof(ScaledFontFreeType::InstanceData)) {
+ gfxWarning() << "FreeType scaled font instance data is truncated.";
+ return nullptr;
+ }
+ const ScaledFontFreeType::InstanceData& instanceData =
+ *reinterpret_cast<const ScaledFontFreeType::InstanceData*>(aInstanceData);
+
+ RefPtr<SharedFTFace> face(InitFace());
+ if (!face) {
+ gfxWarning() << "Attempted to deserialize FreeType scaled font without "
+ "FreeType face";
+ return nullptr;
+ }
+
+ if (aNumVariations > 0 && face->GetData()) {
+ if (RefPtr<SharedFTFace> varFace = face->GetData()->CloneFace()) {
+ face = varFace;
+ }
+ }
+
+ // Only apply variations if we have an explicitly cloned face.
+ if (aNumVariations > 0 && face != GetFace()) {
+ ApplyVariationsToFace(aVariations, aNumVariations, face->GetFace());
+ }
+
+ RefPtr<ScaledFontFreeType> scaledFont = new ScaledFontFreeType(
+ std::move(face), this, aGlyphSize, instanceData.mApplySyntheticBold);
+
+ return scaledFont.forget();
+}
+
+already_AddRefed<ScaledFont> UnscaledFontFreeType::CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ ScaledFontFreeType::InstanceData instanceData(aOptions, aPlatformOptions);
+ return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData),
+ sizeof(instanceData), aVariations, aNumVariations);
+}
+
+already_AddRefed<UnscaledFont> UnscaledFontFreeType::CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
+ if (aDataLength == 0) {
+ gfxWarning() << "FreeType font descriptor is truncated.";
+ return nullptr;
+ }
+ const char* path = reinterpret_cast<const char*>(aData);
+ RefPtr<UnscaledFont> unscaledFont =
+ new UnscaledFontFreeType(std::string(path, aDataLength), aIndex);
+ return unscaledFont.forget();
+}
+
+#endif // MOZ_WIDGET_ANDROID
+
+} // namespace mozilla::gfx
diff --git a/gfx/2d/UnscaledFontFreeType.h b/gfx/2d/UnscaledFontFreeType.h
new file mode 100644
index 0000000000..2194c8e776
--- /dev/null
+++ b/gfx/2d/UnscaledFontFreeType.h
@@ -0,0 +1,110 @@
+/* -*- 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_UNSCALEDFONTFREETYPE_H_
+#define MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_
+
+#include <cairo-ft.h>
+
+#include "2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class ScaledFontFreeType;
+class ScaledFontFontconfig;
+
+class UnscaledFontFreeType : public UnscaledFont {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontFreeType, override)
+ explicit UnscaledFontFreeType(const RefPtr<SharedFTFace>& aFace)
+ : mFace(aFace), mIndex(0) {}
+ explicit UnscaledFontFreeType(const char* aFile, uint32_t aIndex = 0,
+ RefPtr<SharedFTFace> aFace = nullptr)
+ : mFace(std::move(aFace)), mFile(aFile), mIndex(aIndex) {}
+ explicit UnscaledFontFreeType(std::string&& aFile, uint32_t aIndex = 0,
+ RefPtr<SharedFTFace> aFace = nullptr)
+ : mFace(std::move(aFace)), mFile(std::move(aFile)), mIndex(aIndex) {}
+
+ FontType GetType() const override { return FontType::FREETYPE; }
+
+ const RefPtr<SharedFTFace>& GetFace() const { return mFace; }
+ const std::string& GetFile() const { return mFile; }
+ uint32_t GetIndex() const { return mIndex; }
+
+ bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override;
+
+ bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override;
+
+ RefPtr<SharedFTFace> InitFace();
+
+#ifdef MOZ_WIDGET_ANDROID
+ static already_AddRefed<UnscaledFont> CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex);
+
+ already_AddRefed<ScaledFont> CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) override;
+
+ already_AddRefed<ScaledFont> CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) override;
+#endif
+
+ protected:
+ RefPtr<SharedFTFace> mFace;
+ std::string mFile;
+ uint32_t mIndex;
+
+ friend class ScaledFontFreeType;
+ friend class ScaledFontFontconfig;
+
+ static void GetVariationSettingsFromFace(
+ std::vector<FontVariation>* aVariations, FT_Face aFace);
+
+ static void ApplyVariationsToFace(const FontVariation* aVariations,
+ uint32_t aNumVariations, FT_Face aFace);
+};
+
+#ifdef MOZ_WIDGET_GTK
+class UnscaledFontFontconfig : public UnscaledFontFreeType {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontFontconfig, override)
+ explicit UnscaledFontFontconfig(const RefPtr<SharedFTFace>& aFace)
+ : UnscaledFontFreeType(aFace) {}
+ explicit UnscaledFontFontconfig(const char* aFile, uint32_t aIndex = 0,
+ RefPtr<SharedFTFace> aFace = nullptr)
+ : UnscaledFontFreeType(aFile, aIndex, std::move(aFace)) {}
+ explicit UnscaledFontFontconfig(std::string&& aFile, uint32_t aIndex = 0,
+ RefPtr<SharedFTFace> aFace = nullptr)
+ : UnscaledFontFreeType(std::move(aFile), aIndex, std::move(aFace)) {}
+
+ FontType GetType() const override { return FontType::FONTCONFIG; }
+
+ static already_AddRefed<UnscaledFont> CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex);
+
+ already_AddRefed<ScaledFont> CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) override;
+
+ already_AddRefed<ScaledFont> CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) override;
+};
+
+extern bool FcPatternAllowsBitmaps(FcPattern* aPattern, bool aAntialias,
+ bool aHinting);
+#endif
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_ */
diff --git a/gfx/2d/UnscaledFontGDI.h b/gfx/2d/UnscaledFontGDI.h
new file mode 100644
index 0000000000..3601110491
--- /dev/null
+++ b/gfx/2d/UnscaledFontGDI.h
@@ -0,0 +1,46 @@
+/* -*- 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_UNSCALEDFONTGDI_H_
+#define MOZILLA_GFX_UNSCALEDFONTGDI_H_
+
+#include "2D.h"
+#include <windows.h>
+
+namespace mozilla {
+namespace gfx {
+
+class UnscaledFontGDI final : public UnscaledFont {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontGDI, override)
+ explicit UnscaledFontGDI(const LOGFONT& aLogFont) : mLogFont(aLogFont) {}
+
+ FontType GetType() const override { return FontType::GDI; }
+
+ const LOGFONT& GetLogFont() const { return mLogFont; }
+
+ bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override;
+
+ bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override;
+
+ bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
+
+ static already_AddRefed<UnscaledFont> CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex);
+
+ already_AddRefed<ScaledFont> CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) override;
+
+ private:
+ LOGFONT mLogFont;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_UNSCALEDFONTGDI_H_ */
diff --git a/gfx/2d/UnscaledFontMac.h b/gfx/2d/UnscaledFontMac.h
new file mode 100644
index 0000000000..d958e59fde
--- /dev/null
+++ b/gfx/2d/UnscaledFontMac.h
@@ -0,0 +1,94 @@
+/* -*- 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_UNSCALEDFONTMAC_H_
+#define MOZILLA_GFX_UNSCALEDFONTMAC_H_
+
+#ifdef MOZ_WIDGET_COCOA
+# include <ApplicationServices/ApplicationServices.h>
+#else
+# include <CoreGraphics/CoreGraphics.h>
+# include <CoreText/CoreText.h>
+#endif
+
+#include "2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class UnscaledFontMac final : public UnscaledFont {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontMac, override)
+ explicit UnscaledFontMac(CGFontRef aFont, bool aIsDataFont = false)
+ : mFont(aFont), mIsDataFont(aIsDataFont) {
+ CFRetain(mFont);
+ }
+ explicit UnscaledFontMac(CTFontDescriptorRef aFontDesc, CGFontRef aFont,
+ bool aIsDataFont = false)
+ : mFontDesc(aFontDesc), mFont(aFont), mIsDataFont(aIsDataFont) {
+ CFRetain(mFontDesc);
+ CFRetain(mFont);
+ }
+
+ virtual ~UnscaledFontMac() {
+ if (mCTAxesCache) {
+ CFRelease(mCTAxesCache);
+ }
+ if (mCGAxesCache) {
+ CFRelease(mCGAxesCache);
+ }
+ if (mFontDesc) {
+ CFRelease(mFontDesc);
+ }
+ if (mFont) {
+ CFRelease(mFont);
+ }
+ }
+
+ FontType GetType() const override { return FontType::MAC; }
+
+ CGFontRef GetFont() const { return mFont; }
+
+ bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override;
+
+ bool IsDataFont() const { return mIsDataFont; }
+
+ already_AddRefed<ScaledFont> CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations) override;
+
+ already_AddRefed<ScaledFont> CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) override;
+
+ static CGFontRef CreateCGFontWithVariations(CGFontRef aFont,
+ CFArrayRef& aCGAxesCache,
+ CFArrayRef& aCTAxesCache,
+ uint32_t aVariationCount,
+ const FontVariation* aVariations);
+
+ bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override;
+
+ CFArrayRef& CGAxesCache() { return mCGAxesCache; }
+ CFArrayRef& CTAxesCache() { return mCTAxesCache; }
+
+ static already_AddRefed<UnscaledFont> CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex);
+
+ private:
+ CTFontDescriptorRef mFontDesc = nullptr;
+ CGFontRef mFont = nullptr;
+ CFArrayRef mCGAxesCache = nullptr; // Cached arrays of variation axis details
+ CFArrayRef mCTAxesCache = nullptr;
+ bool mIsDataFont;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_UNSCALEDFONTMAC_H_ */
diff --git a/gfx/2d/UserData.h b/gfx/2d/UserData.h
new file mode 100644
index 0000000000..16a7db343e
--- /dev/null
+++ b/gfx/2d/UserData.h
@@ -0,0 +1,214 @@
+/* -*- 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_USERDATA_H_
+#define MOZILLA_GFX_USERDATA_H_
+
+#include <stdlib.h>
+#include "Types.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace gfx {
+
+struct UserDataKey {
+ int unused;
+};
+
+/* this class is basically a clone of the user data concept from cairo */
+class UserData {
+ public:
+ typedef void (*DestroyFunc)(void* data);
+
+ UserData() : count(0), entries(nullptr) {}
+
+ /* Attaches untyped userData associated with key. destroy is called on
+ * destruction */
+ void Add(UserDataKey* key, void* userData, DestroyFunc destroy) {
+ for (int i = 0; i < count; i++) {
+ if (key == entries[i].key) {
+ if (entries[i].destroy) {
+ entries[i].destroy(entries[i].userData);
+ }
+ entries[i].userData = userData;
+ entries[i].destroy = destroy;
+ return;
+ }
+ }
+
+ // We could keep entries in a std::vector instead of managing it by hand
+ // but that would propagate an stl dependency out which we'd rather not
+ // do (see bug 666609). Plus, the entries array is expect to stay small
+ // so doing a realloc everytime we add a new entry shouldn't be too costly
+ entries =
+ static_cast<Entry*>(realloc(entries, sizeof(Entry) * (count + 1)));
+
+ if (!entries) {
+ MOZ_CRASH("GFX: UserData::Add");
+ }
+
+ entries[count].key = key;
+ entries[count].userData = userData;
+ entries[count].destroy = destroy;
+
+ count++;
+ }
+
+ /* Remove and return user data associated with key, without destroying it */
+ void* Remove(UserDataKey* key) {
+ for (int i = 0; i < count; i++) {
+ if (key == entries[i].key) {
+ void* userData = entries[i].userData;
+ // decrement before looping so entries[i+1] doesn't read past the end:
+ --count;
+ for (; i < count; i++) {
+ entries[i] = entries[i + 1];
+ }
+ return userData;
+ }
+ }
+ return nullptr;
+ }
+
+ /* Remove and destroy a given key */
+ void RemoveAndDestroy(UserDataKey* key) {
+ for (int i = 0; i < count; i++) {
+ if (key == entries[i].key) {
+ if (entries[i].destroy) {
+ entries[i].destroy(entries[i].userData);
+ }
+ // decrement before looping so entries[i+1] doesn't read past the end:
+ --count;
+ for (; i < count; i++) {
+ entries[i] = entries[i + 1];
+ }
+ }
+ }
+ }
+
+ /* Retrives the userData for the associated key */
+ void* Get(UserDataKey* key) const {
+ for (int i = 0; i < count; i++) {
+ if (key == entries[i].key) {
+ return entries[i].userData;
+ }
+ }
+ return nullptr;
+ }
+
+ bool Has(UserDataKey* key) {
+ for (int i = 0; i < count; i++) {
+ if (key == entries[i].key) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void Destroy() {
+ if (!entries) {
+ return;
+ }
+ for (int i = 0; i < count; i++) {
+ if (entries[i].destroy) {
+ entries[i].destroy(entries[i].userData);
+ }
+ }
+ free(entries);
+ entries = nullptr;
+ count = 0;
+ }
+
+ ~UserData() { Destroy(); }
+
+ private:
+ struct Entry {
+ const UserDataKey* key;
+ void* userData;
+ DestroyFunc destroy;
+ };
+
+ int count;
+ Entry* entries;
+};
+
+class ThreadSafeUserData {
+ protected:
+ struct LockedUserData : public UserData {
+ Mutex mLock;
+
+ LockedUserData() : mLock("LockedUserData::mLock") {}
+ };
+
+ public:
+ ~ThreadSafeUserData() {
+ if (LockedUserData* userData = mUserData.exchange(nullptr)) {
+ {
+ MutexAutoLock lock(userData->mLock);
+ userData->Destroy();
+ }
+ delete userData;
+ }
+ }
+
+ void Add(UserDataKey* key, void* value, UserData::DestroyFunc destroy) {
+ LockedUserData* userData = GetUserData();
+ MutexAutoLock lock(userData->mLock);
+ userData->Add(key, value, destroy);
+ }
+
+ void* Remove(UserDataKey* key) {
+ LockedUserData* userData = GetUserData();
+ MutexAutoLock lock(userData->mLock);
+ return userData->Remove(key);
+ }
+
+ void RemoveAndDestroy(UserDataKey* key) {
+ LockedUserData* userData = GetUserData();
+ MutexAutoLock lock(userData->mLock);
+ userData->RemoveAndDestroy(key);
+ }
+
+ void* Get(UserDataKey* key) const {
+ LockedUserData* userData = GetUserData();
+ MutexAutoLock lock(userData->mLock);
+ return userData->Get(key);
+ }
+
+ bool Has(UserDataKey* key) {
+ LockedUserData* userData = GetUserData();
+ MutexAutoLock lock(userData->mLock);
+ return userData->Has(key);
+ }
+
+ private:
+ LockedUserData* GetUserData() const {
+ LockedUserData* userData = mUserData;
+ if (!userData) {
+ userData = new LockedUserData;
+ if (!mUserData.compareExchange(nullptr, userData)) {
+ delete userData;
+ userData = mUserData;
+ MOZ_ASSERT(userData);
+ }
+ }
+ return userData;
+ }
+
+ // The Mutex class is quite large. For small, frequent classes (ScaledFont,
+ // SourceSurface, etc.) this can add a lot of memory overhead, especially if
+ // UserData is only infrequently used. To avoid this, we only allocate the
+ // LockedUserData if it is actually used. If unused, it only adds a single
+ // pointer as overhead.
+ mutable Atomic<LockedUserData*> mUserData;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_USERDATA_H_ */
diff --git a/gfx/2d/dw-extra.h b/gfx/2d/dw-extra.h
new file mode 100644
index 0000000000..15de36b1fc
--- /dev/null
+++ b/gfx/2d/dw-extra.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+/*
+ * New DirectWrite interfaces based on Win10 Fall Creators Update versions
+ * of dwrite_3.h and dcommon.h (from SDK 10.0.17061.0). This particular
+ * subset of declarations is intended to be just sufficient to compile the
+ * Gecko DirectWrite font code; it omits many other new interfaces, etc.
+ */
+
+#ifndef DWRITE_EXTRA_H
+#define DWRITE_EXTRA_H
+
+#pragma once
+
+interface IDWriteFontResource;
+interface IDWriteFontFaceReference1;
+
+enum DWRITE_GLYPH_IMAGE_FORMATS {
+ DWRITE_GLYPH_IMAGE_FORMATS_NONE = 0x00000000,
+ DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE = 0x00000001,
+ DWRITE_GLYPH_IMAGE_FORMATS_CFF = 0x00000002,
+ DWRITE_GLYPH_IMAGE_FORMATS_COLR = 0x00000004,
+ DWRITE_GLYPH_IMAGE_FORMATS_SVG = 0x00000008,
+ DWRITE_GLYPH_IMAGE_FORMATS_PNG = 0x00000010,
+ DWRITE_GLYPH_IMAGE_FORMATS_JPEG = 0x00000020,
+ DWRITE_GLYPH_IMAGE_FORMATS_TIFF = 0x00000040,
+ DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 = 0x00000080,
+};
+
+#ifdef DEFINE_ENUM_FLAG_OPERATORS
+DEFINE_ENUM_FLAG_OPERATORS(DWRITE_GLYPH_IMAGE_FORMATS);
+#endif
+
+#define DWRITE_MAKE_FONT_AXIS_TAG(a, b, c, d) \
+ (static_cast<DWRITE_FONT_AXIS_TAG>(DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d)))
+
+enum DWRITE_FONT_AXIS_TAG : UINT32 {
+ DWRITE_FONT_AXIS_TAG_WEIGHT = DWRITE_MAKE_FONT_AXIS_TAG('w', 'g', 'h', 't'),
+ DWRITE_FONT_AXIS_TAG_WIDTH = DWRITE_MAKE_FONT_AXIS_TAG('w', 'd', 't', 'h'),
+ DWRITE_FONT_AXIS_TAG_SLANT = DWRITE_MAKE_FONT_AXIS_TAG('s', 'l', 'n', 't'),
+ DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE =
+ DWRITE_MAKE_FONT_AXIS_TAG('o', 'p', 's', 'z'),
+ DWRITE_FONT_AXIS_TAG_ITALIC = DWRITE_MAKE_FONT_AXIS_TAG('i', 't', 'a', 'l'),
+};
+
+enum DWRITE_FONT_AXIS_ATTRIBUTES {
+ DWRITE_FONT_AXIS_ATTRIBUTES_NONE = 0x0000,
+ DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE = 0x0001,
+ DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN = 0x0002,
+};
+
+struct DWRITE_FONT_AXIS_VALUE {
+ DWRITE_FONT_AXIS_TAG axisTag;
+ FLOAT value;
+};
+
+struct DWRITE_FONT_AXIS_RANGE {
+ DWRITE_FONT_AXIS_TAG axisTag;
+ FLOAT minValue;
+ FLOAT maxValue;
+};
+
+struct DWRITE_GLYPH_IMAGE_DATA {
+ const void* imageData;
+ UINT32 imageDataSize;
+ UINT32 uniqueDataId;
+ UINT32 pixelsPerEm;
+ D2D1_SIZE_U pixelSize;
+ D2D1_POINT_2L horizontalLeftOrigin;
+ D2D1_POINT_2L horizontalRightOrigin;
+ D2D1_POINT_2L verticalTopOrigin;
+ D2D1_POINT_2L verticalBottomOrigin;
+};
+
+interface DWRITE_DECLARE_INTERFACE("27F2A904-4EB8-441D-9678-0563F53E3E2F")
+ IDWriteFontFace4 : public IDWriteFontFace3 {
+ STDMETHOD_(DWRITE_GLYPH_IMAGE_FORMATS, GetGlyphImageFormats)() PURE;
+ STDMETHOD(GetGlyphImageFormats)
+ (UINT16 glyphId, UINT32 pixelsPerEmFirst, UINT32 pixelsPerEmLast,
+ _Out_ DWRITE_GLYPH_IMAGE_FORMATS* glyphImageFormats) PURE;
+ STDMETHOD(GetGlyphImageData)
+ (_In_ UINT16 glyphId, UINT32 pixelsPerEm,
+ DWRITE_GLYPH_IMAGE_FORMATS glyphImageFormat,
+ _Out_ DWRITE_GLYPH_IMAGE_DATA* glyphData,
+ _Outptr_result_maybenull_ void** glyphDataContext) PURE;
+ STDMETHOD_(void, ReleaseGlyphImageData)(void* glyphDataContext) PURE;
+};
+
+interface DWRITE_DECLARE_INTERFACE("98EFF3A5-B667-479A-B145-E2FA5B9FDC29")
+ IDWriteFontFace5 : public IDWriteFontFace4 {
+ STDMETHOD_(UINT32, GetFontAxisValueCount)() PURE;
+ STDMETHOD(GetFontAxisValues)
+ (_Out_writes_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE* fontAxisValues,
+ UINT32 fontAxisValueCount) PURE;
+ STDMETHOD_(BOOL, HasVariations)() PURE;
+ STDMETHOD(GetFontResource)
+ (_COM_Outptr_ IDWriteFontResource** fontResource) PURE;
+ STDMETHOD_(BOOL, Equals)(IDWriteFontFace* fontFace) PURE;
+};
+
+interface DWRITE_DECLARE_INTERFACE("1F803A76-6871-48E8-987F-B975551C50F2")
+ IDWriteFontResource : public IUnknown {
+ STDMETHOD(GetFontFile)(_COM_Outptr_ IDWriteFontFile** fontFile) PURE;
+ STDMETHOD_(UINT32, GetFontFaceIndex)() PURE;
+ STDMETHOD_(UINT32, GetFontAxisCount)() PURE;
+ STDMETHOD(GetDefaultFontAxisValues)
+ (_Out_writes_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE* fontAxisValues,
+ UINT32 fontAxisValueCount) PURE;
+ STDMETHOD(GetFontAxisRanges)
+ (_Out_writes_(fontAxisRangeCount) DWRITE_FONT_AXIS_RANGE* fontAxisRanges,
+ UINT32 fontAxisRangeCount) PURE;
+ STDMETHOD_(DWRITE_FONT_AXIS_ATTRIBUTES, GetFontAxisAttributes)
+ (UINT32 axisIndex) PURE;
+ STDMETHOD(GetAxisNames)
+ (UINT32 axisIndex, _COM_Outptr_ IDWriteLocalizedStrings** names) PURE;
+ STDMETHOD_(UINT32, GetAxisValueNameCount)(UINT32 axisIndex) PURE;
+ STDMETHOD(GetAxisValueNames)
+ (UINT32 axisIndex, UINT32 axisValueIndex,
+ _Out_ DWRITE_FONT_AXIS_RANGE* fontAxisRange,
+ _COM_Outptr_ IDWriteLocalizedStrings** names) PURE;
+ STDMETHOD_(BOOL, HasVariations)() PURE;
+ STDMETHOD(CreateFontFace)
+ (DWRITE_FONT_SIMULATIONS fontSimulations,
+ _In_reads_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE const* fontAxisValues,
+ UINT32 fontAxisValueCount, _COM_Outptr_ IDWriteFontFace5** fontFace) PURE;
+ STDMETHOD(CreateFontFaceReference)
+ (DWRITE_FONT_SIMULATIONS fontSimulations,
+ _In_reads_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE const* fontAxisValues,
+ UINT32 fontAxisValueCount,
+ _COM_Outptr_ IDWriteFontFaceReference1** fontFaceReference) PURE;
+};
+
+#endif /* DWRITE_EXTRA_H */
diff --git a/gfx/2d/genshaders.sh b/gfx/2d/genshaders.sh
new file mode 100644
index 0000000000..ce1b750676
--- /dev/null
+++ b/gfx/2d/genshaders.sh
@@ -0,0 +1,12 @@
+# 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/.
+
+fxc ShadersD2D.fx -nologo -FhShadersD2D.h -Tfx_4_0 -Vn d2deffect
+fxc ShadersD2D1.hlsl -ESampleRadialGradientPS -nologo -Tps_4_0_level_9_3 -Fhtmpfile -VnSampleRadialGradientPS
+cat tmpfile > ShadersD2D1.h
+fxc ShadersD2D1.hlsl -ESampleRadialGradientA0PS -nologo -Tps_4_0_level_9_3 -Fhtmpfile -VnSampleRadialGradientA0PS
+cat tmpfile >> ShadersD2D1.h
+fxc ShadersD2D1.hlsl -ESampleConicGradientPS -nologo -Tps_4_0_level_9_3 -Fhtmpfile -VnSampleConicGradientPS
+cat tmpfile >> ShadersD2D1.h
+rm tmpfile
diff --git a/gfx/2d/gfx2d.sln b/gfx/2d/gfx2d.sln
new file mode 100644
index 0000000000..40a137a1c9
--- /dev/null
+++ b/gfx/2d/gfx2d.sln
@@ -0,0 +1,29 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gfx2d", "gfx2d.vcxproj", "{49E973D7-53C9-3D66-BE58-52125FAE193D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unittest", "unittest\unittest.vcxproj", "{CCF4BC8B-0CED-47CA-B621-ABF1832527D9}"
+ ProjectSection(ProjectDependencies) = postProject
+ {49E973D7-53C9-3D66-BE58-52125FAE193D} = {49E973D7-53C9-3D66-BE58-52125FAE193D}
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Debug|Win32.Build.0 = Debug|Win32
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Release|Win32.ActiveCfg = Release|Win32
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Release|Win32.Build.0 = Release|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Debug|Win32.ActiveCfg = Debug|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Debug|Win32.Build.0 = Debug|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Release|Win32.ActiveCfg = Release|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/gfx/2d/gfx2d.vcxproj b/gfx/2d/gfx2d.vcxproj
new file mode 100644
index 0000000000..8b46078998
--- /dev/null
+++ b/gfx/2d/gfx2d.vcxproj
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <ExecutablePath>$(DXSDK_DIR)\Utilities\bin\x86;$(ExecutablePath)</ExecutablePath>
+ <IncludePath>$(ProjectDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>USE_SSE2;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions);MFBT_STAND_ALONE;XP_WIN</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <EntryPointSymbol>
+ </EntryPointSymbol>
+ </Link>
+ <PreBuildEvent>
+ <Command>xcopy $(ProjectDir)..\..\mfbt\*.h mozilla\ /Y</Command>
+ <Message>Copying MFBT files</Message>
+ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>USE_SSE2;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <AdditionalIncludeDirectories>./</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="2D.h" />
+ <ClInclude Include="BaseMargin.h" />
+ <ClInclude Include="BasePoint.h" />
+ <ClInclude Include="BaseRect.h" />
+ <ClInclude Include="BaseSize.h" />
+ <ClInclude Include="DrawEventRecorder.h" />
+ <ClInclude Include="DrawTargetD2D.h" />
+ <ClInclude Include="DrawTargetDual.h" />
+ <ClInclude Include="DrawTargetRecording.h" />
+ <ClInclude Include="GradientStopsD2D.h" />
+ <ClInclude Include="HelpersD2D.h" />
+ <ClInclude Include="ImageScaling.h" />
+ <ClInclude Include="Logging.h" />
+ <ClInclude Include="Matrix.h" />
+ <ClInclude Include="PathD2D.h" />
+ <ClInclude Include="PathHelpers.h" />
+ <ClInclude Include="PathRecording.h" />
+ <ClInclude Include="Point.h" />
+ <ClInclude Include="RecordedEvent.h" />
+ <ClInclude Include="RecordingTypes.h" />
+ <ClInclude Include="Rect.h" />
+ <ClInclude Include="ScaledFontBase.h" />
+ <ClInclude Include="ScaledFontDWrite.h" />
+ <ClInclude Include="SourceSurfaceD2D.h" />
+ <ClInclude Include="SourceSurfaceD2DTarget.h" />
+ <ClInclude Include="SourceSurfaceRawData.h" />
+ <ClInclude Include="Tools.h" />
+ <ClInclude Include="Types.h" />
+ <ClInclude Include="UserData.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DrawEventRecorder.cpp" />
+ <ClCompile Include="DrawTargetD2D.cpp" />
+ <ClCompile Include="DrawTargetDual.cpp" />
+ <ClCompile Include="DrawTargetRecording.cpp" />
+ <ClCompile Include="Factory.cpp" />
+ <ClCompile Include="ImageScaling.cpp" />
+ <ClCompile Include="ImageScalingSSE2.cpp" />
+ <ClCompile Include="Matrix.cpp" />
+ <ClCompile Include="PathD2D.cpp" />
+ <ClCompile Include="PathRecording.cpp" />
+ <ClCompile Include="RecordedEvent.cpp" />
+ <ClCompile Include="ScaledFontBase.cpp" />
+ <ClCompile Include="ScaledFontDWrite.cpp" />
+ <ClCompile Include="SourceSurfaceD2D.cpp" />
+ <ClCompile Include="SourceSurfaceD2DTarget.cpp" />
+ <ClCompile Include="SourceSurfaceRawData.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Makefile.in" />
+ <CustomBuild Include="ShadersD2D.fx">
+ <FileType>Document</FileType>
+ <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">fxc /Tfx_4_0 /FhShadersD2D.h ShadersD2D.fx /Vn d2deffect</Command>
+ <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">ShadersD2D.h</Outputs>
+ </CustomBuild>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build
new file mode 100644
index 0000000000..5465f9d563
--- /dev/null
+++ b/gfx/2d/moz.build
@@ -0,0 +1,236 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla += [
+ "GenericRefCounted.h",
+]
+
+EXPORTS.mozilla.gfx += [
+ "2D.h",
+ "BaseCoord.h",
+ "BaseMargin.h",
+ "BasePoint.h",
+ "BasePoint3D.h",
+ "BasePoint4D.h",
+ "BaseRect.h",
+ "BaseSize.h",
+ "BezierUtils.h",
+ "Blur.h",
+ "BorrowedContext.h",
+ "Coord.h",
+ "CriticalSection.h",
+ "DataSurfaceHelpers.h",
+ "DrawEventRecorder.h",
+ "DrawTargetOffset.h",
+ "DrawTargetRecording.h",
+ "DrawTargetSkia.h",
+ "Filters.h",
+ "FontVariation.h",
+ "Helpers.h",
+ "HelpersCairo.h",
+ "InlineTranslator.h",
+ "IterableArena.h",
+ "Logging.h",
+ "LoggingConstants.h",
+ "Matrix.h",
+ "MatrixFwd.h",
+ "NumericTools.h",
+ "PathHelpers.h",
+ "PathSkia.h",
+ "PatternHelpers.h",
+ "Point.h",
+ "Polygon.h",
+ "Quaternion.h",
+ "RecordedEvent.h",
+ "RecordingTypes.h",
+ "Rect.h",
+ "RectAbsolute.h",
+ "Scale.h",
+ "ScaleFactor.h",
+ "ScaleFactors2D.h",
+ "SourceSurfaceCairo.h",
+ "SourceSurfaceRawData.h",
+ "StackArray.h",
+ "Swizzle.h",
+ "Tools.h",
+ "Triangle.h",
+ "Types.h",
+ "UserData.h",
+]
+
+EXPORTS.mozilla.gfx += ["ssse3-scaler.h"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("cocoa", "uikit"):
+ EXPORTS.mozilla.gfx += [
+ "MacIOSurface.h",
+ "ScaledFontBase.h",
+ "ScaledFontMac.h",
+ "UnscaledFontMac.h",
+ ]
+ UNIFIED_SOURCES += [
+ "NativeFontResourceMac.cpp",
+ "ScaledFontMac.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ EXPORTS.mozilla.gfx += [
+ "dw-extra.h",
+ "DWriteSettings.h",
+ "UnscaledFontDWrite.h",
+ "UnscaledFontGDI.h",
+ ]
+ SOURCES += [
+ "ConicGradientEffectD2D1.cpp",
+ "DrawTargetD2D1.cpp",
+ "DWriteSettings.cpp",
+ "ExtendInputEffectD2D1.cpp",
+ "FilterNodeD2D1.cpp",
+ "NativeFontResourceDWrite.cpp",
+ "NativeFontResourceGDI.cpp",
+ "PathD2D.cpp",
+ "RadialGradientEffectD2D1.cpp",
+ "ScaledFontDWrite.cpp",
+ "ScaledFontWin.cpp",
+ "SourceSurfaceD2D1.cpp",
+ ]
+ DEFINES["WIN32"] = True
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
+ EXPORTS.mozilla.gfx += [
+ "UnscaledFontFreeType.h",
+ ]
+ SOURCES += [
+ "NativeFontResourceFreeType.cpp",
+ "UnscaledFontFreeType.cpp",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ SOURCES += [
+ "ScaledFontFontconfig.cpp",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ SOURCES += [
+ "ScaledFontFreeType.cpp",
+ ]
+
+EXPORTS.mozilla.gfx += [
+ "ConvolutionFilter.h",
+ "HelpersSkia.h",
+]
+
+# Are we targeting x86 or x64? If so, build SSE2 files.
+if CONFIG["INTEL_ARCHITECTURE"]:
+ SOURCES += [
+ "BlurSSE2.cpp",
+ "ConvolutionFilterAVX2.cpp",
+ "ConvolutionFilterSSE2.cpp",
+ "FilterProcessingSSE2.cpp",
+ "ImageScalingSSE2.cpp",
+ "ssse3-scaler.c",
+ "SwizzleAVX2.cpp",
+ "SwizzleSSE2.cpp",
+ "SwizzleSSSE3.cpp",
+ ]
+ DEFINES["USE_SSE2"] = True
+ # The file uses SSE2 intrinsics, so it needs special compile flags on some
+ # compilers.
+ SOURCES["BlurSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+ SOURCES["ConvolutionFilterAVX2.cpp"].flags += ["-mavx2"]
+ SOURCES["ConvolutionFilterSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+ SOURCES["FilterProcessingSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+ SOURCES["ImageScalingSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+ SOURCES["SwizzleAVX2.cpp"].flags += ["-mavx2"]
+ SOURCES["SwizzleSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+ SOURCES["SwizzleSSSE3.cpp"].flags += CONFIG["SSSE3_FLAGS"]
+ SOURCES["ssse3-scaler.c"].flags += CONFIG["SSSE3_FLAGS"]
+elif CONFIG["CPU_ARCH"].startswith("mips"):
+ SOURCES += [
+ "BlurLS3.cpp",
+ ]
+
+UNIFIED_SOURCES += [
+ "BezierUtils.cpp",
+ "Blur.cpp",
+ "BufferEdgePad.cpp",
+ "BufferUnrotate.cpp",
+ "ConvolutionFilter.cpp",
+ "DataSourceSurface.cpp",
+ "DataSurfaceHelpers.cpp",
+ "DrawEventRecorder.cpp",
+ "DrawTarget.cpp",
+ "DrawTargetCairo.cpp",
+ "DrawTargetOffset.cpp",
+ "DrawTargetRecording.cpp",
+ "DrawTargetSkia.cpp",
+ "Factory.cpp",
+ "FilterNodeSoftware.cpp",
+ "FilterProcessing.cpp",
+ "FilterProcessingScalar.cpp",
+ "ImageScaling.cpp",
+ "Matrix.cpp",
+ "NativeFontResource.cpp",
+ "Path.cpp",
+ "PathCairo.cpp",
+ "PathHelpers.cpp",
+ "PathRecording.cpp",
+ "PathSkia.cpp",
+ "Quaternion.cpp",
+ "RecordedEvent.cpp",
+ "ScaledFontBase.cpp",
+ "SFNTData.cpp",
+ "SkConvolver.cpp",
+ "SourceSurfaceCairo.cpp",
+ "SourceSurfaceRawData.cpp",
+ "SourceSurfaceSkia.cpp",
+ "Swizzle.cpp",
+ "Types.cpp",
+]
+
+SOURCES += [
+ "InlineTranslator.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ EXPORTS.mozilla.gfx += [
+ "QuartzSupport.h",
+ ]
+ SOURCES += [
+ "MacIOSurface.cpp",
+ "QuartzSupport.mm",
+ ]
+
+if CONFIG["CPU_ARCH"] == "aarch64" or CONFIG["BUILD_ARM_NEON"]:
+ SOURCES += [
+ "BlurNEON.cpp",
+ "ConvolutionFilterNEON.cpp",
+ "LuminanceNEON.cpp",
+ "SwizzleNEON.cpp",
+ ]
+ DEFINES["USE_NEON"] = True
+ SOURCES["BlurNEON.cpp"].flags += CONFIG["NEON_FLAGS"]
+ SOURCES["ConvolutionFilterNEON.cpp"].flags += CONFIG["NEON_FLAGS"]
+ SOURCES["LuminanceNEON.cpp"].flags += CONFIG["NEON_FLAGS"]
+ SOURCES["SwizzleNEON.cpp"].flags += CONFIG["NEON_FLAGS"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+for var in ("USE_CAIRO", "MOZ2D_HAS_MOZ_CAIRO"):
+ DEFINES[var] = True
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
+ DEFINES["MOZ_ENABLE_FREETYPE"] = True
+
+CXXFLAGS += ["-Werror=switch"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
+ CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"]
+
+LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+LOCAL_INCLUDES += [
+ "/gfx/cairo/cairo/src",
+]
diff --git a/gfx/2d/ssse3-scaler.c b/gfx/2d/ssse3-scaler.c
new file mode 100644
index 0000000000..4f7419cc80
--- /dev/null
+++ b/gfx/2d/ssse3-scaler.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright © 2013 Soren Sandmann Pedersen
+ * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Author: Soren Sandmann (soren.sandmann@gmail.com)
+ * Jeff Muizelaar (jmuizelaar@mozilla.com)
+ */
+
+/* This has been adapted from the ssse3 code from pixman. It's currently
+ * a mess as I want to try it out in practice before finalizing the details.
+ */
+
+#include <stdlib.h>
+#include <xmmintrin.h>
+#include <emmintrin.h>
+#include <tmmintrin.h>
+#include <stdint.h>
+#include <assert.h>
+#include "ssse3-scaler.h"
+
+typedef int32_t pixman_fixed_16_16_t;
+typedef pixman_fixed_16_16_t pixman_fixed_t;
+#define pixman_fixed_1 (pixman_int_to_fixed(1))
+#define pixman_fixed_to_int(f) ((int)((f) >> 16))
+#define pixman_int_to_fixed(i) ((pixman_fixed_t)((i) << 16))
+#define pixman_double_to_fixed(d) ((pixman_fixed_t)((d)*65536.0))
+#define PIXMAN_FIXED_INT_MAX 32767
+#define PIXMAN_FIXED_INT_MIN -32768
+typedef struct pixman_vector pixman_vector_t;
+
+typedef int pixman_bool_t;
+typedef int64_t pixman_fixed_32_32_t;
+typedef pixman_fixed_32_32_t pixman_fixed_48_16_t;
+typedef struct {
+ pixman_fixed_48_16_t v[3];
+} pixman_vector_48_16_t;
+
+struct pixman_vector {
+ pixman_fixed_t vector[3];
+};
+typedef struct pixman_transform pixman_transform_t;
+
+struct pixman_transform {
+ pixman_fixed_t matrix[3][3];
+};
+
+#ifdef _MSC_VER
+# define force_inline __forceinline
+#else
+# define force_inline __inline__ __attribute__((always_inline))
+#endif
+
+#define BILINEAR_INTERPOLATION_BITS 6
+
+static force_inline int pixman_fixed_to_bilinear_weight(pixman_fixed_t x) {
+ return (x >> (16 - BILINEAR_INTERPOLATION_BITS)) &
+ ((1 << BILINEAR_INTERPOLATION_BITS) - 1);
+}
+
+static void pixman_transform_point_31_16_3d(const pixman_transform_t* t,
+ const pixman_vector_48_16_t* v,
+ pixman_vector_48_16_t* result) {
+ int i;
+ int64_t tmp[3][2];
+
+ /* input vector values must have no more than 31 bits (including sign)
+ * in the integer part */
+ assert(v->v[0] < ((pixman_fixed_48_16_t)1 << (30 + 16)));
+ assert(v->v[0] >= -((pixman_fixed_48_16_t)1 << (30 + 16)));
+ assert(v->v[1] < ((pixman_fixed_48_16_t)1 << (30 + 16)));
+ assert(v->v[1] >= -((pixman_fixed_48_16_t)1 << (30 + 16)));
+ assert(v->v[2] < ((pixman_fixed_48_16_t)1 << (30 + 16)));
+ assert(v->v[2] >= -((pixman_fixed_48_16_t)1 << (30 + 16)));
+
+ for (i = 0; i < 3; i++) {
+ tmp[i][0] = (int64_t)t->matrix[i][0] * (v->v[0] >> 16);
+ tmp[i][1] = (int64_t)t->matrix[i][0] * (v->v[0] & 0xFFFF);
+ tmp[i][0] += (int64_t)t->matrix[i][1] * (v->v[1] >> 16);
+ tmp[i][1] += (int64_t)t->matrix[i][1] * (v->v[1] & 0xFFFF);
+ tmp[i][0] += (int64_t)t->matrix[i][2] * (v->v[2] >> 16);
+ tmp[i][1] += (int64_t)t->matrix[i][2] * (v->v[2] & 0xFFFF);
+ }
+
+ result->v[0] = tmp[0][0] + ((tmp[0][1] + 0x8000) >> 16);
+ result->v[1] = tmp[1][0] + ((tmp[1][1] + 0x8000) >> 16);
+ result->v[2] = tmp[2][0] + ((tmp[2][1] + 0x8000) >> 16);
+}
+
+static pixman_bool_t pixman_transform_point_3d(
+ const struct pixman_transform* transform, struct pixman_vector* vector) {
+ pixman_vector_48_16_t tmp;
+ tmp.v[0] = vector->vector[0];
+ tmp.v[1] = vector->vector[1];
+ tmp.v[2] = vector->vector[2];
+
+ pixman_transform_point_31_16_3d(transform, &tmp, &tmp);
+
+ vector->vector[0] = tmp.v[0];
+ vector->vector[1] = tmp.v[1];
+ vector->vector[2] = tmp.v[2];
+
+ return vector->vector[0] == tmp.v[0] && vector->vector[1] == tmp.v[1] &&
+ vector->vector[2] == tmp.v[2];
+}
+
+struct bits_image_t {
+ uint32_t* bits;
+ int rowstride;
+ pixman_transform_t* transform;
+};
+
+typedef struct bits_image_t bits_image_t;
+typedef struct {
+ int unused;
+} pixman_iter_info_t;
+
+typedef struct pixman_iter_t pixman_iter_t;
+typedef void (*pixman_iter_fini_t)(pixman_iter_t* iter);
+
+struct pixman_iter_t {
+ int x, y;
+ pixman_iter_fini_t fini;
+ bits_image_t* image;
+ uint32_t* buffer;
+ int width;
+ int height;
+ void* data;
+};
+
+typedef struct {
+ int y;
+ uint64_t* buffer;
+} line_t;
+
+typedef struct {
+ line_t lines[2];
+ pixman_fixed_t y;
+ pixman_fixed_t x;
+ uint64_t data[1];
+} bilinear_info_t;
+
+static void ssse3_fetch_horizontal(bits_image_t* image, line_t* line, int y,
+ pixman_fixed_t x, pixman_fixed_t ux, int n) {
+ uint32_t* bits = image->bits + y * image->rowstride;
+ __m128i vx = _mm_set_epi16(-(x + 1), x, -(x + 1), x, -(x + ux + 1), x + ux,
+ -(x + ux + 1), x + ux);
+ __m128i vux = _mm_set_epi16(-2 * ux, 2 * ux, -2 * ux, 2 * ux, -2 * ux, 2 * ux,
+ -2 * ux, 2 * ux);
+ __m128i vaddc = _mm_set_epi16(1, 0, 1, 0, 1, 0, 1, 0);
+ __m128i* b = (__m128i*)line->buffer;
+ __m128i vrl0, vrl1;
+
+ while ((n -= 2) >= 0) {
+ __m128i vw, vr, s;
+#ifdef HACKY_PADDING
+ if (pixman_fixed_to_int(x + ux) >= image->rowstride) {
+ vrl1 = _mm_setzero_si128();
+ printf("overread 2loop\n");
+ } else {
+ if (pixman_fixed_to_int(x + ux) < 0) printf("underflow\n");
+ vrl1 = _mm_loadl_epi64(
+ (__m128i*)(bits + (pixman_fixed_to_int(x + ux) < 0
+ ? 0
+ : pixman_fixed_to_int(x + ux))));
+ }
+#else
+ vrl1 = _mm_loadl_epi64((__m128i*)(bits + pixman_fixed_to_int(x + ux)));
+#endif
+ /* vrl1: R1, L1 */
+
+ final_pixel:
+#ifdef HACKY_PADDING
+ vrl0 = _mm_loadl_epi64(
+ (__m128i*)(bits +
+ (pixman_fixed_to_int(x) < 0 ? 0 : pixman_fixed_to_int(x))));
+#else
+ vrl0 = _mm_loadl_epi64((__m128i*)(bits + pixman_fixed_to_int(x)));
+#endif
+ /* vrl0: R0, L0 */
+
+ /* The weights are based on vx which is a vector of
+ *
+ * - (x + 1), x, - (x + 1), x,
+ * - (x + ux + 1), x + ux, - (x + ux + 1), x + ux
+ *
+ * so the 16 bit weights end up like this:
+ *
+ * iw0, w0, iw0, w0, iw1, w1, iw1, w1
+ *
+ * and after shifting and packing, we get these bytes:
+ *
+ * iw0, w0, iw0, w0, iw1, w1, iw1, w1,
+ * iw0, w0, iw0, w0, iw1, w1, iw1, w1,
+ *
+ * which means the first and the second input pixel
+ * have to be interleaved like this:
+ *
+ * la0, ra0, lr0, rr0, la1, ra1, lr1, rr1,
+ * lg0, rg0, lb0, rb0, lg1, rg1, lb1, rb1
+ *
+ * before maddubsw can be used.
+ */
+
+ vw = _mm_add_epi16(vaddc,
+ _mm_srli_epi16(vx, 16 - BILINEAR_INTERPOLATION_BITS));
+ /* vw: iw0, w0, iw0, w0, iw1, w1, iw1, w1
+ */
+
+ vw = _mm_packus_epi16(vw, vw);
+ /* vw: iw0, w0, iw0, w0, iw1, w1, iw1, w1,
+ * iw0, w0, iw0, w0, iw1, w1, iw1, w1
+ */
+ vx = _mm_add_epi16(vx, vux);
+
+ x += 2 * ux;
+
+ vr = _mm_unpacklo_epi16(vrl1, vrl0);
+ /* vr: rar0, rar1, rgb0, rgb1, lar0, lar1, lgb0, lgb1 */
+
+ s = _mm_shuffle_epi32(vr, _MM_SHUFFLE(1, 0, 3, 2));
+ /* s: lar0, lar1, lgb0, lgb1, rar0, rar1, rgb0, rgb1 */
+
+ vr = _mm_unpackhi_epi8(vr, s);
+ /* vr: la0, ra0, lr0, rr0, la1, ra1, lr1, rr1,
+ * lg0, rg0, lb0, rb0, lg1, rg1, lb1, rb1
+ */
+
+ vr = _mm_maddubs_epi16(vr, vw);
+
+ /* When the weight is 0, the inverse weight is
+ * 128 which can't be represented in a signed byte.
+ * As a result maddubsw computes the following:
+ *
+ * r = l * -128 + r * 0
+ *
+ * rather than the desired
+ *
+ * r = l * 128 + r * 0
+ *
+ * We fix this by taking the absolute value of the
+ * result.
+ */
+ // we can drop this if we use lower precision
+
+ vr = _mm_shuffle_epi32(vr, _MM_SHUFFLE(2, 0, 3, 1));
+ /* vr: A0, R0, A1, R1, G0, B0, G1, B1 */
+ _mm_store_si128(b++, vr);
+ }
+
+ if (n == -1) {
+ vrl1 = _mm_setzero_si128();
+ goto final_pixel;
+ }
+
+ line->y = y;
+}
+
+// scale a line of destination pixels
+static uint32_t* ssse3_fetch_bilinear_cover(pixman_iter_t* iter,
+ const uint32_t* mask) {
+ pixman_fixed_t fx, ux;
+ bilinear_info_t* info = iter->data;
+ line_t *line0, *line1;
+ int y0, y1;
+ int32_t dist_y;
+ __m128i vw, uvw;
+ int i;
+
+ fx = info->x;
+ ux = iter->image->transform->matrix[0][0];
+
+ y0 = pixman_fixed_to_int(info->y);
+ if (y0 < 0) *(volatile char*)0 = 9;
+ y1 = y0 + 1;
+
+ // clamping in y direction
+ if (y1 >= iter->height) {
+ y1 = iter->height - 1;
+ }
+
+ line0 = &info->lines[y0 & 0x01];
+ line1 = &info->lines[y1 & 0x01];
+
+ if (line0->y != y0) {
+ ssse3_fetch_horizontal(iter->image, line0, y0, fx, ux, iter->width);
+ }
+
+ if (line1->y != y1) {
+ ssse3_fetch_horizontal(iter->image, line1, y1, fx, ux, iter->width);
+ }
+
+#ifdef PIXMAN_STYLE_INTERPOLATION
+ dist_y = pixman_fixed_to_bilinear_weight(info->y);
+ dist_y <<= (16 - BILINEAR_INTERPOLATION_BITS);
+
+ vw = _mm_set_epi16(dist_y, dist_y, dist_y, dist_y, dist_y, dist_y, dist_y,
+ dist_y);
+
+#else
+ // setup the weights for the top (vw) and bottom (uvw) lines
+ dist_y = pixman_fixed_to_bilinear_weight(info->y);
+ // we use 15 instead of 16 because we need an extra bit to handle when the
+ // weights are 0 and 1
+ dist_y <<= (15 - BILINEAR_INTERPOLATION_BITS);
+
+ vw = _mm_set_epi16(dist_y, dist_y, dist_y, dist_y, dist_y, dist_y, dist_y,
+ dist_y);
+
+ dist_y = (1 << BILINEAR_INTERPOLATION_BITS) -
+ pixman_fixed_to_bilinear_weight(info->y);
+ dist_y <<= (15 - BILINEAR_INTERPOLATION_BITS);
+ uvw = _mm_set_epi16(dist_y, dist_y, dist_y, dist_y, dist_y, dist_y, dist_y,
+ dist_y);
+#endif
+
+ for (i = 0; i + 3 < iter->width; i += 4) {
+ __m128i top0 = _mm_load_si128((__m128i*)(line0->buffer + i));
+ __m128i bot0 = _mm_load_si128((__m128i*)(line1->buffer + i));
+ __m128i top1 = _mm_load_si128((__m128i*)(line0->buffer + i + 2));
+ __m128i bot1 = _mm_load_si128((__m128i*)(line1->buffer + i + 2));
+#ifdef PIXMAN_STYLE_INTERPOLATION
+ __m128i r0, r1, tmp, p;
+
+ r0 = _mm_mulhi_epu16(_mm_sub_epi16(bot0, top0), vw);
+ tmp = _mm_cmplt_epi16(bot0, top0);
+ tmp = _mm_and_si128(tmp, vw);
+ r0 = _mm_sub_epi16(r0, tmp);
+ r0 = _mm_add_epi16(r0, top0);
+ r0 = _mm_srli_epi16(r0, BILINEAR_INTERPOLATION_BITS);
+ /* r0: A0 R0 A1 R1 G0 B0 G1 B1 */
+ // r0 = _mm_shuffle_epi32 (r0, _MM_SHUFFLE (2, 0, 3, 1));
+ /* r0: A1 R1 G1 B1 A0 R0 G0 B0 */
+
+ // tmp = bot1 < top1 ? vw : 0;
+ // r1 = (bot1 - top1)*vw + top1 - tmp
+ // r1 = bot1*vw - vw*top1 + top1 - tmp
+ // r1 = bot1*vw + top1 - vw*top1 - tmp
+ // r1 = bot1*vw + top1*(1 - vw) - tmp
+ r1 = _mm_mulhi_epu16(_mm_sub_epi16(bot1, top1), vw);
+ tmp = _mm_cmplt_epi16(bot1, top1);
+ tmp = _mm_and_si128(tmp, vw);
+ r1 = _mm_sub_epi16(r1, tmp);
+ r1 = _mm_add_epi16(r1, top1);
+ r1 = _mm_srli_epi16(r1, BILINEAR_INTERPOLATION_BITS);
+ // r1 = _mm_shuffle_epi32 (r1, _MM_SHUFFLE (2, 0, 3, 1));
+ /* r1: A3 R3 G3 B3 A2 R2 G2 B2 */
+#else
+ __m128i r0, r1, p;
+ top0 = _mm_mulhi_epu16(top0, uvw);
+ bot0 = _mm_mulhi_epu16(bot0, vw);
+ r0 = _mm_add_epi16(top0, bot0);
+ r0 = _mm_srli_epi16(r0, BILINEAR_INTERPOLATION_BITS - 1);
+
+ top1 = _mm_mulhi_epu16(top1, uvw);
+ bot1 = _mm_mulhi_epu16(bot1, vw);
+ r1 = _mm_add_epi16(top1, bot1);
+ r1 = _mm_srli_epi16(r1, BILINEAR_INTERPOLATION_BITS - 1);
+#endif
+
+ p = _mm_packus_epi16(r0, r1);
+ _mm_storeu_si128((__m128i*)(iter->buffer + i), p);
+ }
+
+ while (i < iter->width) {
+ __m128i top0 = _mm_load_si128((__m128i*)(line0->buffer + i));
+ __m128i bot0 = _mm_load_si128((__m128i*)(line1->buffer + i));
+
+#ifdef PIXMAN_STYLE_INTERPOLATION
+ __m128i r0, tmp, p;
+ r0 = _mm_mulhi_epu16(_mm_sub_epi16(bot0, top0), vw);
+ tmp = _mm_cmplt_epi16(bot0, top0);
+ tmp = _mm_and_si128(tmp, vw);
+ r0 = _mm_sub_epi16(r0, tmp);
+ r0 = _mm_add_epi16(r0, top0);
+ r0 = _mm_srli_epi16(r0, BILINEAR_INTERPOLATION_BITS);
+ /* r0: A0 R0 A1 R1 G0 B0 G1 B1 */
+ r0 = _mm_shuffle_epi32(r0, _MM_SHUFFLE(2, 0, 3, 1));
+ /* r0: A1 R1 G1 B1 A0 R0 G0 B0 */
+#else
+ __m128i r0, p;
+ top0 = _mm_mulhi_epu16(top0, uvw);
+ bot0 = _mm_mulhi_epu16(bot0, vw);
+ r0 = _mm_add_epi16(top0, bot0);
+ r0 = _mm_srli_epi16(r0, BILINEAR_INTERPOLATION_BITS - 1);
+#endif
+
+ p = _mm_packus_epi16(r0, r0);
+
+ if (iter->width - i == 1) {
+ *(uint32_t*)(iter->buffer + i) = _mm_cvtsi128_si32(p);
+ i++;
+ } else {
+ _mm_storel_epi64((__m128i*)(iter->buffer + i), p);
+ i += 2;
+ }
+ }
+
+ info->y += iter->image->transform->matrix[1][1];
+
+ return iter->buffer;
+}
+
+static void ssse3_bilinear_cover_iter_fini(pixman_iter_t* iter) {
+ free(iter->data);
+}
+
+static void ssse3_bilinear_cover_iter_init(pixman_iter_t* iter) {
+ int width = iter->width;
+ bilinear_info_t* info;
+ pixman_vector_t v;
+
+ if (iter->x > PIXMAN_FIXED_INT_MAX || iter->x < PIXMAN_FIXED_INT_MIN ||
+ iter->y > PIXMAN_FIXED_INT_MAX || iter->y < PIXMAN_FIXED_INT_MIN)
+ goto fail;
+
+ /* Reference point is the center of the pixel */
+ v.vector[0] = pixman_int_to_fixed(iter->x) + pixman_fixed_1 / 2;
+ v.vector[1] = pixman_int_to_fixed(iter->y) + pixman_fixed_1 / 2;
+ v.vector[2] = pixman_fixed_1;
+
+ if (!pixman_transform_point_3d(iter->image->transform, &v)) goto fail;
+
+ info = malloc(sizeof(*info) + (2 * width - 1) * sizeof(uint64_t) + 64);
+ if (!info) goto fail;
+
+ info->x = v.vector[0] - pixman_fixed_1 / 2;
+ info->y = v.vector[1] - pixman_fixed_1 / 2;
+
+#define ALIGN(addr) ((void*)((((uintptr_t)(addr)) + 15) & (~15)))
+
+ /* It is safe to set the y coordinates to -1 initially
+ * because COVER_CLIP_BILINEAR ensures that we will only
+ * be asked to fetch lines in the [0, height) interval
+ */
+ info->lines[0].y = -1;
+ info->lines[0].buffer = ALIGN(&(info->data[0]));
+ info->lines[1].y = -1;
+ info->lines[1].buffer = ALIGN(info->lines[0].buffer + width);
+
+ iter->fini = ssse3_bilinear_cover_iter_fini;
+
+ iter->data = info;
+ return;
+
+fail:
+ /* Something went wrong, either a bad matrix or OOM; in such cases,
+ * we don't guarantee any particular rendering.
+ */
+ iter->fini = NULL;
+}
+
+/* scale the src from src_width/height to dest_width/height drawn
+ * into the rectangle x,y width,height
+ * src_stride and dst_stride are 4 byte units */
+bool ssse3_scale_data(uint32_t* src, int src_width, int src_height,
+ int src_stride, uint32_t* dest, int dest_width,
+ int dest_height, int dest_stride, int x, int y, int width,
+ int height) {
+ // XXX: assert(src_width > 1)
+ pixman_transform_t transform = {
+ {{pixman_fixed_1, 0, 0}, {0, pixman_fixed_1, 0}, {0, 0, pixman_fixed_1}}};
+ double width_scale = ((double)src_width) / dest_width;
+ double height_scale = ((double)src_height) / dest_height;
+#define AVOID_PADDING
+#ifdef AVOID_PADDING
+ // scale up by enough that we don't read outside of the bounds of the source
+ // surface currently this is required to avoid reading out of bounds.
+ if (width_scale < 1) {
+ width_scale = (double)(src_width - 1) / dest_width;
+ transform.matrix[0][2] = pixman_fixed_1 / 2;
+ }
+ if (height_scale < 1) {
+ height_scale = (double)(src_height - 1) / dest_height;
+ transform.matrix[1][2] = pixman_fixed_1 / 2;
+ }
+#endif
+ transform.matrix[0][0] = pixman_double_to_fixed(width_scale);
+ transform.matrix[1][1] = pixman_double_to_fixed(height_scale);
+ transform.matrix[2][2] = pixman_fixed_1;
+
+ bits_image_t image;
+ image.bits = src;
+ image.transform = &transform;
+ image.rowstride = src_stride;
+
+ pixman_iter_t iter;
+ iter.image = &image;
+ iter.x = x;
+ iter.y = y;
+ iter.width = width;
+ iter.height = src_height;
+ iter.buffer = dest;
+ iter.data = NULL;
+
+ ssse3_bilinear_cover_iter_init(&iter);
+
+ if (!iter.fini) return false;
+
+ if (iter.data) {
+ for (int iy = 0; iy < height; iy++) {
+ ssse3_fetch_bilinear_cover(&iter, NULL);
+ iter.buffer += dest_stride;
+ }
+ ssse3_bilinear_cover_iter_fini(&iter);
+ }
+ return true;
+}
diff --git a/gfx/2d/ssse3-scaler.h b/gfx/2d/ssse3-scaler.h
new file mode 100644
index 0000000000..371bc86210
--- /dev/null
+++ b/gfx/2d/ssse3-scaler.h
@@ -0,0 +1,24 @@
+/* -*- 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_2D_SSSE3_SCALER_H_
+#define MOZILLA_GFX_2D_SSSE3_SCALER_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+bool ssse3_scale_data(uint32_t* src, int src_width, int src_height,
+ int src_stride, uint32_t* dest, int dest_width,
+ int dest_height, int dest_rowstride, int x, int y,
+ int width, int height);
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MOZILLA_GFX_2D_SSS3_SCALER_H_
diff --git a/gfx/2d/unittest/Main.cpp b/gfx/2d/unittest/Main.cpp
new file mode 100644
index 0000000000..0a6c9b0401
--- /dev/null
+++ b/gfx/2d/unittest/Main.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "SanityChecks.h"
+#include "TestPoint.h"
+#include "TestScaling.h"
+#include "TestBugs.h"
+#ifdef WIN32
+# include "TestDrawTargetD2D.h"
+#endif
+
+#include <string>
+#include <sstream>
+
+struct TestObject {
+ TestBase* test;
+ std::string name;
+};
+
+int main() {
+ TestObject tests[] = {
+ {new SanityChecks(), "Sanity Checks"},
+#ifdef WIN32
+ {new TestDrawTargetD2D(), "DrawTarget (D2D)"},
+#endif
+ {new TestPoint(), "Point Tests"},
+ {new TestScaling(), "Scaling Tests"} {new TestBugs(), "Bug Tests"}};
+
+ int totalFailures = 0;
+ int totalTests = 0;
+ std::stringstream message;
+ printf("------ STARTING RUNNING TESTS ------\n");
+ for (int i = 0; i < sizeof(tests) / sizeof(TestObject); i++) {
+ message << "--- RUNNING TESTS: " << tests[i].name << " ---\n";
+ printf(message.str().c_str());
+ message.str("");
+ int failures = 0;
+ totalTests += tests[i].test->RunTests(&failures);
+ totalFailures += failures;
+ // Done with this test!
+ delete tests[i].test;
+ }
+ message << "------ FINISHED RUNNING TESTS ------\nTests run: " << totalTests
+ << " - Passes: " << totalTests - totalFailures
+ << " - Failures: " << totalFailures << "\n";
+ printf(message.str().c_str());
+ return totalFailures;
+}
diff --git a/gfx/2d/unittest/SanityChecks.cpp b/gfx/2d/unittest/SanityChecks.cpp
new file mode 100644
index 0000000000..c8db1dd69d
--- /dev/null
+++ b/gfx/2d/unittest/SanityChecks.cpp
@@ -0,0 +1,15 @@
+/* -*- 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 "SanityChecks.h"
+
+SanityChecks::SanityChecks() { REGISTER_TEST(SanityChecks, AlwaysPasses); }
+
+void SanityChecks::AlwaysPasses() {
+ bool testMustPass = true;
+
+ VERIFY(testMustPass);
+}
diff --git a/gfx/2d/unittest/SanityChecks.h b/gfx/2d/unittest/SanityChecks.h
new file mode 100644
index 0000000000..6161b783eb
--- /dev/null
+++ b/gfx/2d/unittest/SanityChecks.h
@@ -0,0 +1,16 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "TestBase.h"
+
+class SanityChecks : public TestBase {
+ public:
+ SanityChecks();
+
+ void AlwaysPasses();
+};
diff --git a/gfx/2d/unittest/TestBase.cpp b/gfx/2d/unittest/TestBase.cpp
new file mode 100644
index 0000000000..bf78008489
--- /dev/null
+++ b/gfx/2d/unittest/TestBase.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "TestBase.h"
+
+#include <sstream>
+
+int TestBase::RunTests(int* aFailures) {
+ int testsRun = 0;
+ *aFailures = 0;
+
+ for (unsigned int i = 0; i < mTests.size(); i++) {
+ std::stringstream stream;
+ stream << "Test (" << mTests[i].name << "): ";
+ LogMessage(stream.str());
+ stream.str("");
+
+ mTestFailed = false;
+
+ // Don't try this at home! We know these are actually pointers to members
+ // of child clases, so we reinterpret cast those child class pointers to
+ // TestBase and then call the functions. Because the compiler believes
+ // these function calls are members of TestBase.
+ ((*reinterpret_cast<TestBase*>((mTests[i].implPointer))).*
+ (mTests[i].funcCall))();
+
+ if (!mTestFailed) {
+ LogMessage("PASSED\n");
+ } else {
+ LogMessage("FAILED\n");
+ (*aFailures)++;
+ }
+ testsRun++;
+ }
+
+ return testsRun;
+}
+
+void TestBase::LogMessage(std::string aMessage) {
+ printf("%s", aMessage.c_str());
+}
diff --git a/gfx/2d/unittest/TestBase.h b/gfx/2d/unittest/TestBase.h
new file mode 100644
index 0000000000..fb9326e856
--- /dev/null
+++ b/gfx/2d/unittest/TestBase.h
@@ -0,0 +1,53 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#if defined(_MSC_VER) && !defined(__clang__)
+// On MSVC otherwise our generic member pointer trick doesn't work.
+// JYA: Do we still need this?
+# pragma pointers_to_members(full_generality, single_inheritance)
+#endif
+
+#define VERIFY(arg) \
+ if (!(arg)) { \
+ LogMessage("VERIFY FAILED: " #arg "\n"); \
+ mTestFailed = true; \
+ }
+
+#define REGISTER_TEST(className, testName) \
+ mTests.push_back( \
+ Test(static_cast<TestCall>(&className::testName), #testName, this))
+
+class TestBase {
+ public:
+ TestBase() = default;
+
+ typedef void (TestBase::*TestCall)();
+
+ int RunTests(int* aFailures);
+
+ protected:
+ static void LogMessage(std::string aMessage);
+
+ struct Test {
+ Test(TestCall aCall, std::string aName, void* aImplPointer)
+ : funcCall(aCall), name(aName), implPointer(aImplPointer) {}
+ TestCall funcCall;
+ std::string name;
+ void* implPointer;
+ };
+ std::vector<Test> mTests;
+
+ bool mTestFailed;
+
+ private:
+ // This doesn't really work with our generic member pointer trick.
+ TestBase(const TestBase& aOther);
+};
diff --git a/gfx/2d/unittest/TestBugs.cpp b/gfx/2d/unittest/TestBugs.cpp
new file mode 100644
index 0000000000..545166007a
--- /dev/null
+++ b/gfx/2d/unittest/TestBugs.cpp
@@ -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/. */
+
+#include "TestBugs.h"
+#include "2D.h"
+#include <string.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+TestBugs::TestBugs() {
+ REGISTER_TEST(TestBugs, CairoClip918671);
+ REGISTER_TEST(TestBugs, PushPopClip950550);
+}
+
+void TestBugs::CairoClip918671() {
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
+ BackendType::CAIRO, IntSize(100, 100), SurfaceFormat::B8G8R8A8);
+ RefPtr<DrawTarget> ref = Factory::CreateDrawTarget(
+ BackendType::CAIRO, IntSize(100, 100), SurfaceFormat::B8G8R8A8);
+ // Create a path that extends around the center rect but doesn't intersect it.
+ RefPtr<PathBuilder> pb1 = dt->CreatePathBuilder();
+ pb1->MoveTo(Point(10, 10));
+ pb1->LineTo(Point(90, 10));
+ pb1->LineTo(Point(90, 20));
+ pb1->LineTo(Point(10, 20));
+ pb1->Close();
+ pb1->MoveTo(Point(90, 90));
+ pb1->LineTo(Point(91, 90));
+ pb1->LineTo(Point(91, 91));
+ pb1->LineTo(Point(91, 90));
+ pb1->Close();
+
+ RefPtr<Path> path1 = pb1->Finish();
+ dt->PushClip(path1);
+
+ // This center rect must NOT be rectilinear!
+ RefPtr<PathBuilder> pb2 = dt->CreatePathBuilder();
+ pb2->MoveTo(Point(50, 50));
+ pb2->LineTo(Point(55, 51));
+ pb2->LineTo(Point(54, 55));
+ pb2->LineTo(Point(50, 56));
+ pb2->Close();
+
+ RefPtr<Path> path2 = pb2->Finish();
+ dt->PushClip(path2);
+
+ dt->FillRect(Rect(0, 0, 100, 100), ColorPattern(DeviceColor(1, 0, 0)));
+
+ RefPtr<SourceSurface> surf1 = dt->Snapshot();
+ RefPtr<SourceSurface> surf2 = ref->Snapshot();
+
+ RefPtr<DataSourceSurface> dataSurf1 = surf1->GetDataSurface();
+ RefPtr<DataSourceSurface> dataSurf2 = surf2->GetDataSurface();
+
+ DataSourceSurface::ScopedMap map1(dataSurf1, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap map2(dataSurf2, DataSourceSurface::READ);
+ for (int y = 0; y < dt->GetSize().height; y++) {
+ VERIFY(memcmp(map1.GetData() + y * map1.GetStride(),
+ map2.GetData() + y * map2.GetStride(),
+ dataSurf1->GetSize().width * 4) == 0);
+ }
+}
+
+void TestBugs::PushPopClip950550() {
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
+ BackendType::CAIRO, IntSize(500, 500), SurfaceFormat::B8G8R8A8);
+ dt->PushClipRect(Rect(0, 0, 100, 100));
+ Matrix m(1, 0, 0, 1, 45, -100);
+ dt->SetTransform(m);
+ dt->PopClip();
+
+ // We fail the test if we assert in this call because our draw target's
+ // transforms are out of sync.
+ dt->FillRect(Rect(50, 50, 50, 50),
+ ColorPattern(DeviceColor(0.5f, 0, 0, 1.0f)));
+}
diff --git a/gfx/2d/unittest/TestBugs.h b/gfx/2d/unittest/TestBugs.h
new file mode 100644
index 0000000000..c337b05cff
--- /dev/null
+++ b/gfx/2d/unittest/TestBugs.h
@@ -0,0 +1,17 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "TestBase.h"
+
+class TestBugs : public TestBase {
+ public:
+ TestBugs();
+
+ void CairoClip918671();
+ void PushPopClip950550();
+};
diff --git a/gfx/2d/unittest/TestCairo.cpp b/gfx/2d/unittest/TestCairo.cpp
new file mode 100644
index 0000000000..4ed7ad4d4a
--- /dev/null
+++ b/gfx/2d/unittest/TestCairo.cpp
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "cairo.h"
+
+#include "gtest/gtest.h"
+
+namespace mozilla {
+namespace layers {
+
+static void TryCircle(double centerX, double centerY, double radius) {
+ printf("TestCairo:TryArcs centerY %f, radius %f\n", centerY, radius);
+
+ cairo_surface_t* surf =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 8, 21);
+ ASSERT_TRUE(surf != nullptr);
+
+ cairo_t* cairo = cairo_create(surf);
+ ASSERT_TRUE(cairo != nullptr);
+
+ cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
+ cairo_arc(cairo, 0.0, centerY, radius, 0.0, 6.2831853071795862);
+ cairo_fill_preserve(cairo);
+
+ cairo_surface_destroy(surf);
+ cairo_destroy(cairo);
+}
+
+TEST(Cairo, Simple)
+{
+ TryCircle(0.0, 0.0, 14.0);
+ TryCircle(0.0, 1.0, 22.4);
+ TryCircle(1.0, 0.0, 1422.4);
+ TryCircle(1.0, 1.0, 3422.4);
+ TryCircle(-10.0, 1.0, -2);
+}
+
+TEST(Cairo, Bug825721)
+{
+ // OK:
+ TryCircle(0.0, 0.0, 8761126469220696064.0);
+ TryCircle(0.0, 1.0, 8761126469220696064.0);
+
+ // OK:
+ TryCircle(1.0, 0.0, 5761126469220696064.0);
+
+ // This was the crash in 825721. Note that centerY has to be non-zero,
+ // and radius has to be not only large, but in particular range.
+ // 825721 has a band-aid fix, where the crash is inevitable, but does
+ // not fix the cause. The same code crashes in cairo standalone.
+ TryCircle(0.0, 1.0, 5761126469220696064.0);
+}
+
+TEST(Cairo, Bug1063486)
+{
+ double x1, y1, x2, y2;
+ const double epsilon = .01;
+
+ cairo_surface_t* surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+ ASSERT_TRUE(surf != nullptr);
+
+ cairo_t* cairo = cairo_create(surf);
+ ASSERT_TRUE(cairo != nullptr);
+
+ printf("Path 1\n");
+ cairo_move_to(cairo, -20, -10);
+ cairo_line_to(cairo, 20, -10);
+ cairo_line_to(cairo, 20, 10);
+ cairo_curve_to(cairo, 10, 10, -10, 10, -20, 10);
+ cairo_curve_to(cairo, -30, 10, -30, -10, -20, -10);
+
+ cairo_path_extents(cairo, &x1, &y1, &x2, &y2);
+
+ ASSERT_LT(std::abs(-27.5 - x1), epsilon); // the failing coordinate
+ ASSERT_LT(std::abs(-10 - y1), epsilon);
+ ASSERT_LT(std::abs(20 - x2), epsilon);
+ ASSERT_LT(std::abs(10 - y2), epsilon);
+
+ printf("Path 2\n");
+ cairo_new_path(cairo);
+ cairo_move_to(cairo, 10, 30);
+ cairo_line_to(cairo, 90, 30);
+ cairo_curve_to(cairo, 30, 30, 30, 30, 10, 30);
+ cairo_curve_to(cairo, 0, 30, 0, 0, 30, 5);
+
+ cairo_path_extents(cairo, &x1, &y1, &x2, &y2);
+
+ ASSERT_LT(std::abs(4.019531 - x1), epsilon); // the failing coordinate
+ ASSERT_LT(std::abs(4.437500 - y1), epsilon);
+ ASSERT_LT(std::abs(90. - x2), epsilon);
+ ASSERT_LT(std::abs(30. - y2), epsilon);
+
+ cairo_surface_destroy(surf);
+ cairo_destroy(cairo);
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/2d/unittest/TestDrawTargetBase.cpp b/gfx/2d/unittest/TestDrawTargetBase.cpp
new file mode 100644
index 0000000000..2ef817cd86
--- /dev/null
+++ b/gfx/2d/unittest/TestDrawTargetBase.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "TestDrawTargetBase.h"
+#include <sstream>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+TestDrawTargetBase::TestDrawTargetBase() {
+ REGISTER_TEST(TestDrawTargetBase, Initialized);
+ REGISTER_TEST(TestDrawTargetBase, FillCompletely);
+ REGISTER_TEST(TestDrawTargetBase, FillRect);
+}
+
+void TestDrawTargetBase::Initialized() { VERIFY(mDT); }
+
+void TestDrawTargetBase::FillCompletely() {
+ mDT->FillRect(Rect(0, 0, DT_WIDTH, DT_HEIGHT),
+ ColorPattern(DeviceColor(0, 0.5f, 0, 1.0f)));
+
+ RefreshSnapshot();
+
+ VerifyAllPixels(DeviceColor(0, 0.5f, 0, 1.0f));
+}
+
+void TestDrawTargetBase::FillRect() {
+ mDT->FillRect(Rect(0, 0, DT_WIDTH, DT_HEIGHT),
+ ColorPattern(DeviceColor(0, 0.5f, 0, 1.0f)));
+ mDT->FillRect(Rect(50, 50, 50, 50),
+ ColorPattern(DeviceColor(0.5f, 0, 0, 1.0f)));
+
+ RefreshSnapshot();
+
+ VerifyPixel(IntPoint(49, 49), DeviceColor(0, 0.5f, 0, 1.0f));
+ VerifyPixel(IntPoint(50, 50), DeviceColor(0.5f, 0, 0, 1.0f));
+ VerifyPixel(IntPoint(99, 99), DeviceColor(0.5f, 0, 0, 1.0f));
+ VerifyPixel(IntPoint(100, 100), DeviceColor(0, 0.5f, 0, 1.0f));
+}
+
+void TestDrawTargetBase::RefreshSnapshot() {
+ RefPtr<SourceSurface> snapshot = mDT->Snapshot();
+ mDataSnapshot = snapshot->GetDataSurface();
+}
+
+void TestDrawTargetBase::VerifyAllPixels(const DeviceColor& aColor) {
+ uint32_t* colVal = (uint32_t*)mDataSnapshot->GetData();
+
+ uint32_t expected = RGBAPixelFromColor(aColor);
+
+ for (int y = 0; y < DT_HEIGHT; y++) {
+ for (int x = 0; x < DT_WIDTH; x++) {
+ if (colVal[y * (mDataSnapshot->Stride() / 4) + x] != expected) {
+ LogMessage("VerifyAllPixels Failed\n");
+ mTestFailed = true;
+ return;
+ }
+ }
+ }
+}
+
+void TestDrawTargetBase::VerifyPixel(const IntPoint& aPoint,
+ mozilla::gfx::DeviceColor& aColor) {
+ uint32_t* colVal = (uint32_t*)mDataSnapshot->GetData();
+
+ uint32_t expected = RGBAPixelFromColor(aColor);
+ uint32_t rawActual =
+ colVal[aPoint.y * (mDataSnapshot->Stride() / 4) + aPoint.x];
+
+ if (rawActual != expected) {
+ stringstream message;
+ uint32_t actb = rawActual & 0xFF;
+ uint32_t actg = (rawActual & 0xFF00) >> 8;
+ uint32_t actr = (rawActual & 0xFF0000) >> 16;
+ uint32_t acta = (rawActual & 0xFF000000) >> 24;
+ uint32_t expb = expected & 0xFF;
+ uint32_t expg = (expected & 0xFF00) >> 8;
+ uint32_t expr = (expected & 0xFF0000) >> 16;
+ uint32_t expa = (expected & 0xFF000000) >> 24;
+
+ message << "Verify Pixel (" << aPoint.x << "x" << aPoint.y
+ << ") Failed."
+ " Expected ("
+ << expr << "," << expg << "," << expb << "," << expa
+ << ") "
+ " Got ("
+ << actr << "," << actg << "," << actb << "," << acta << ")\n";
+
+ LogMessage(message.str());
+ mTestFailed = true;
+ return;
+ }
+}
+
+uint32_t TestDrawTargetBase::RGBAPixelFromColor(const DeviceColor& aColor) {
+ return uint8_t((aColor.b * 255) + 0.5f) |
+ uint8_t((aColor.g * 255) + 0.5f) << 8 |
+ uint8_t((aColor.r * 255) + 0.5f) << 16 |
+ uint8_t((aColor.a * 255) + 0.5f) << 24;
+}
diff --git a/gfx/2d/unittest/TestDrawTargetBase.h b/gfx/2d/unittest/TestDrawTargetBase.h
new file mode 100644
index 0000000000..3f13a63106
--- /dev/null
+++ b/gfx/2d/unittest/TestDrawTargetBase.h
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "2D.h"
+#include "TestBase.h"
+
+#define DT_WIDTH 500
+#define DT_HEIGHT 500
+
+/* This general DrawTarget test class can be reimplemented by a child class
+ * with optional additional drawtarget-specific tests. And is intended to run
+ * on a 500x500 32 BPP drawtarget.
+ */
+class TestDrawTargetBase : public TestBase {
+ public:
+ void Initialized();
+ void FillCompletely();
+ void FillRect();
+
+ protected:
+ TestDrawTargetBase();
+
+ void RefreshSnapshot();
+
+ void VerifyAllPixels(const mozilla::gfx::DeviceColor& aColor);
+ void VerifyPixel(const mozilla::gfx::IntPoint& aPoint,
+ mozilla::gfx::DeviceColor& aColor);
+
+ uint32_t RGBAPixelFromColor(const mozilla::gfx::DeviceColor& aColor);
+
+ RefPtr<mozilla::gfx::DrawTarget> mDT;
+ RefPtr<mozilla::gfx::DataSourceSurface> mDataSnapshot;
+};
diff --git a/gfx/2d/unittest/TestDrawTargetD2D.cpp b/gfx/2d/unittest/TestDrawTargetD2D.cpp
new file mode 100644
index 0000000000..cba63129fd
--- /dev/null
+++ b/gfx/2d/unittest/TestDrawTargetD2D.cpp
@@ -0,0 +1,22 @@
+/* -*- 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 "TestDrawTargetD2D.h"
+
+using namespace mozilla::gfx;
+TestDrawTargetD2D::TestDrawTargetD2D() {
+ ::D3D10CreateDevice1(
+ nullptr, D3D10_DRIVER_TYPE_HARDWARE, nullptr,
+ D3D10_CREATE_DEVICE_BGRA_SUPPORT |
+ D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS,
+ D3D10_FEATURE_LEVEL_10_0, D3D10_1_SDK_VERSION, getter_AddRefs(mDevice));
+
+ Factory::SetDirect3D10Device(mDevice);
+
+ mDT = Factory::CreateDrawTarget(BackendType::DIRECT2D,
+ IntSize(DT_WIDTH, DT_HEIGHT),
+ SurfaceFormat::B8G8R8A8);
+}
diff --git a/gfx/2d/unittest/TestDrawTargetD2D.h b/gfx/2d/unittest/TestDrawTargetD2D.h
new file mode 100644
index 0000000000..f4c88336a4
--- /dev/null
+++ b/gfx/2d/unittest/TestDrawTargetD2D.h
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "TestDrawTargetBase.h"
+
+#include <d3d10_1.h>
+
+class TestDrawTargetD2D : public TestDrawTargetBase {
+ public:
+ TestDrawTargetD2D();
+
+ private:
+ RefPtr<ID3D10Device1> mDevice;
+};
diff --git a/gfx/2d/unittest/TestPoint.cpp b/gfx/2d/unittest/TestPoint.cpp
new file mode 100644
index 0000000000..e79ff01ab3
--- /dev/null
+++ b/gfx/2d/unittest/TestPoint.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "TestPoint.h"
+
+#include "Point.h"
+
+using namespace mozilla::gfx;
+
+TestPoint::TestPoint() {
+ REGISTER_TEST(TestPoint, Addition);
+ REGISTER_TEST(TestPoint, Subtraction);
+ REGISTER_TEST(TestPoint, RoundToMultiple);
+}
+
+void TestPoint::Addition() {
+ Point a, b;
+ a.x = 2;
+ a.y = 2;
+ b.x = 5;
+ b.y = -5;
+
+ a += b;
+
+ VERIFY(a.x == 7.f);
+ VERIFY(a.y == -3.f);
+}
+
+void TestPoint::Subtraction() {
+ Point a, b;
+ a.x = 2;
+ a.y = 2;
+ b.x = 5;
+ b.y = -5;
+
+ a -= b;
+
+ VERIFY(a.x == -3.f);
+ VERIFY(a.y == 7.f);
+}
+
+void TestPoint::RoundToMultiple() {
+ const int32_t roundTo = 2;
+
+ IntPoint p(478, -394);
+ VERIFY(p.RoundedToMultiple(roundTo) == p);
+
+ IntPoint p2(478, 393);
+ VERIFY(p2.RoundedToMultiple(roundTo) != p2);
+}
diff --git a/gfx/2d/unittest/TestPoint.h b/gfx/2d/unittest/TestPoint.h
new file mode 100644
index 0000000000..cb5b6a3de3
--- /dev/null
+++ b/gfx/2d/unittest/TestPoint.h
@@ -0,0 +1,18 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "TestBase.h"
+
+class TestPoint : public TestBase {
+ public:
+ TestPoint();
+
+ void Addition();
+ void Subtraction();
+ void RoundToMultiple();
+};
diff --git a/gfx/2d/unittest/TestScaling.cpp b/gfx/2d/unittest/TestScaling.cpp
new file mode 100644
index 0000000000..fd15455f26
--- /dev/null
+++ b/gfx/2d/unittest/TestScaling.cpp
@@ -0,0 +1,235 @@
+/* -*- 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 "TestScaling.h"
+
+#include "ImageScaling.h"
+
+using namespace mozilla::gfx;
+
+TestScaling::TestScaling() {
+ REGISTER_TEST(TestScaling, BasicHalfScale);
+ REGISTER_TEST(TestScaling, DoubleHalfScale);
+ REGISTER_TEST(TestScaling, UnevenHalfScale);
+ REGISTER_TEST(TestScaling, OddStrideHalfScale);
+ REGISTER_TEST(TestScaling, VerticalHalfScale);
+ REGISTER_TEST(TestScaling, HorizontalHalfScale);
+ REGISTER_TEST(TestScaling, MixedHalfScale);
+}
+
+void TestScaling::BasicHalfScale() {
+ std::vector<uint8_t> data;
+ data.resize(500 * 500 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y += 2) {
+ for (int x = 0; x < 500; x += 2) {
+ pixels[y * 500 + x] = 0xff00ff00;
+ pixels[y * 500 + x + 1] = 0xff00ffff;
+ pixels[(y + 1) * 500 + x] = 0xff000000;
+ pixels[(y + 1) * 500 + x + 1] = 0xff0000ff;
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500));
+
+ scaler.ScaleForSize(IntSize(220, 240));
+
+ VERIFY(scaler.GetSize().width == 250);
+ VERIFY(scaler.GetSize().height == 250);
+
+ pixels = (uint32_t*)scaler.GetScaledData();
+
+ for (int y = 0; y < 250; y++) {
+ for (int x = 0; x < 250; x++) {
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f);
+ }
+ }
+}
+
+void TestScaling::DoubleHalfScale() {
+ std::vector<uint8_t> data;
+ data.resize(500 * 500 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y += 2) {
+ for (int x = 0; x < 500; x += 2) {
+ pixels[y * 500 + x] = 0xff00ff00;
+ pixels[y * 500 + x + 1] = 0xff00ffff;
+ pixels[(y + 1) * 500 + x] = 0xff000000;
+ pixels[(y + 1) * 500 + x + 1] = 0xff0000ff;
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500));
+
+ scaler.ScaleForSize(IntSize(120, 110));
+ VERIFY(scaler.GetSize().width == 125);
+ VERIFY(scaler.GetSize().height == 125);
+
+ pixels = (uint32_t*)scaler.GetScaledData();
+
+ for (int y = 0; y < 125; y++) {
+ for (int x = 0; x < 125; x++) {
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f);
+ }
+ }
+}
+
+void TestScaling::UnevenHalfScale() {
+ std::vector<uint8_t> data;
+ // Use a 16-byte aligned stride still, we test none-aligned strides
+ // separately.
+ data.resize(499 * 500 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y += 2) {
+ for (int x = 0; x < 500; x += 2) {
+ pixels[y * 500 + x] = 0xff00ff00;
+ if (x < 498) {
+ pixels[y * 500 + x + 1] = 0xff00ffff;
+ }
+ if (y < 498) {
+ pixels[(y + 1) * 500 + x] = 0xff000000;
+ if (x < 498) {
+ pixels[(y + 1) * 500 + x + 1] = 0xff0000ff;
+ }
+ }
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(499, 499));
+
+ scaler.ScaleForSize(IntSize(220, 220));
+ VERIFY(scaler.GetSize().width == 249);
+ VERIFY(scaler.GetSize().height == 249);
+
+ pixels = (uint32_t*)scaler.GetScaledData();
+
+ for (int y = 0; y < 249; y++) {
+ for (int x = 0; x < 249; x++) {
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f);
+ }
+ }
+}
+
+void TestScaling::OddStrideHalfScale() {
+ std::vector<uint8_t> data;
+ // Use a 4-byte aligned stride to test if that doesn't cause any issues.
+ data.resize(499 * 499 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y += 2) {
+ for (int x = 0; x < 500; x += 2) {
+ pixels[y * 499 + x] = 0xff00ff00;
+ if (x < 498) {
+ pixels[y * 499 + x + 1] = 0xff00ffff;
+ }
+ if (y < 498) {
+ pixels[(y + 1) * 499 + x] = 0xff000000;
+ if (x < 498) {
+ pixels[(y + 1) * 499 + x + 1] = 0xff0000ff;
+ }
+ }
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 499 * 4, IntSize(499, 499));
+
+ scaler.ScaleForSize(IntSize(220, 220));
+ VERIFY(scaler.GetSize().width == 249);
+ VERIFY(scaler.GetSize().height == 249);
+
+ pixels = (uint32_t*)scaler.GetScaledData();
+
+ for (int y = 0; y < 249; y++) {
+ for (int x = 0; x < 249; x++) {
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f);
+ }
+ }
+}
+void TestScaling::VerticalHalfScale() {
+ std::vector<uint8_t> data;
+ data.resize(500 * 500 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y += 2) {
+ for (int x = 0; x < 500; x += 2) {
+ pixels[y * 500 + x] = 0xff00ff00;
+ pixels[y * 500 + x + 1] = 0xff00ffff;
+ pixels[(y + 1) * 500 + x] = 0xff000000;
+ pixels[(y + 1) * 500 + x + 1] = 0xff0000ff;
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500));
+
+ scaler.ScaleForSize(IntSize(400, 240));
+ VERIFY(scaler.GetSize().width == 500);
+ VERIFY(scaler.GetSize().height == 250);
+
+ pixels = (uint32_t*)scaler.GetScaledData();
+
+ for (int y = 0; y < 250; y++) {
+ for (int x = 0; x < 500; x += 2) {
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f00);
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 1] == 0xff007fff);
+ }
+ }
+}
+
+void TestScaling::HorizontalHalfScale() {
+ std::vector<uint8_t> data;
+ data.resize(520 * 500 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y++) {
+ for (int x = 0; x < 520; x += 8) {
+ pixels[y * 520 + x] = 0xff00ff00;
+ pixels[y * 520 + x + 1] = 0xff00ffff;
+ pixels[y * 520 + x + 2] = 0xff000000;
+ pixels[y * 520 + x + 3] = 0xff0000ff;
+ pixels[y * 520 + x + 4] = 0xffff00ff;
+ pixels[y * 520 + x + 5] = 0xff0000ff;
+ pixels[y * 520 + x + 6] = 0xffffffff;
+ pixels[y * 520 + x + 7] = 0xff0000ff;
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 520 * 4, IntSize(520, 500));
+
+ scaler.ScaleForSize(IntSize(240, 400));
+ VERIFY(scaler.GetSize().width == 260);
+ VERIFY(scaler.GetSize().height == 500);
+
+ pixels = (uint32_t*)scaler.GetScaledData();
+
+ for (int y = 0; y < 500; y++) {
+ for (int x = 0; x < 260; x += 4) {
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff00ff7f);
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 1] == 0xff00007f);
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 2] == 0xff7f00ff);
+ VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 3] == 0xff7f7fff);
+ }
+ }
+}
+
+void TestScaling::MixedHalfScale() {
+ std::vector<uint8_t> data;
+ data.resize(500 * 500 * 4);
+
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front());
+ for (int y = 0; y < 500; y += 2) {
+ for (int x = 0; x < 500; x += 2) {
+ pixels[y * 500 + x] = 0xff00ff00;
+ pixels[y * 500 + x + 1] = 0xff00ffff;
+ pixels[(y + 1) * 500 + x] = 0xff000000;
+ pixels[(y + 1) * 500 + x + 1] = 0xff0000ff;
+ }
+ }
+ ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500));
+
+ scaler.ScaleForSize(IntSize(120, 240));
+ VERIFY(scaler.GetSize().width == 125);
+ VERIFY(scaler.GetSize().height == 250);
+ scaler.ScaleForSize(IntSize(240, 120));
+ VERIFY(scaler.GetSize().width == 250);
+ VERIFY(scaler.GetSize().height == 125);
+}
diff --git a/gfx/2d/unittest/TestScaling.h b/gfx/2d/unittest/TestScaling.h
new file mode 100644
index 0000000000..dbbcda91fa
--- /dev/null
+++ b/gfx/2d/unittest/TestScaling.h
@@ -0,0 +1,22 @@
+/* -*- 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/. */
+
+#pragma once
+
+#include "TestBase.h"
+
+class TestScaling : public TestBase {
+ public:
+ TestScaling();
+
+ void BasicHalfScale();
+ void DoubleHalfScale();
+ void UnevenHalfScale();
+ void OddStrideHalfScale();
+ void VerticalHalfScale();
+ void HorizontalHalfScale();
+ void MixedHalfScale();
+};
diff --git a/gfx/2d/unittest/unittest.vcxproj b/gfx/2d/unittest/unittest.vcxproj
new file mode 100644
index 0000000000..7ddf925303
--- /dev/null
+++ b/gfx/2d/unittest/unittest.vcxproj
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{CCF4BC8B-0CED-47CA-B621-ABF1832527D9}</ProjectGuid>
+ <RootNamespace>unittest</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LibraryPath>$(DXSDK_DIR)\Lib\x86;$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib</LibraryPath>
+ <IncludePath>$(ProjectDir)..\;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LibraryPath>$(DXSDK_DIR)\Lib\x86;$(VCInstallDir)lib;$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib</LibraryPath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>../$(Configuration)/gfx2d.lib;dxguid.lib;d3d10_1.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>../</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalDependencies>../$(Configuration)/gfx2d.lib;dxguid.lib;d3d10_1.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="SanityChecks.cpp" />
+ <ClCompile Include="TestBase.cpp" />
+ <ClCompile Include="TestDrawTargetBase.cpp" />
+ <ClCompile Include="TestDrawTargetD2D.cpp" />
+ <ClCompile Include="TestPoint.cpp" />
+ <ClCompile Include="TestScaling.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="TestDrawTargetBase.h" />
+ <ClInclude Include="SanityChecks.h" />
+ <ClInclude Include="TestBase.h" />
+ <ClInclude Include="TestDrawTargetD2D.h" />
+ <ClInclude Include="TestPoint.h" />
+ <ClInclude Include="TestScaling.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file