diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /gfx/2d | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'gfx/2d')
190 files changed, 73650 insertions, 0 deletions
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h new file mode 100644 index 000000000..c1fba3463 --- /dev/null +++ b/gfx/2d/2D.h @@ -0,0 +1,1526 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_2D_H +#define _MOZILLA_GFX_2D_H + +#include "Types.h" +#include "Point.h" +#include "Rect.h" +#include "Matrix.h" +#include "Quaternion.h" +#include "UserData.h" + +// 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" + +// 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/DebugOnly.h" + +#ifdef MOZ_ENABLE_FREETYPE +#include <string> +#endif + +#include "gfxPrefs.h" + +struct _cairo_surface; +typedef _cairo_surface cairo_surface_t; + +struct _cairo_scaled_font; +typedef _cairo_scaled_font cairo_scaled_font_t; + +struct _FcPattern; +typedef _FcPattern FcPattern; + +struct ID3D11Texture2D; +struct ID3D11Device; +struct ID2D1Device; +struct IDWriteFactory; +struct IDWriteRenderingParams; +struct IDWriteFontFace; + +class GrContext; +class SkCanvas; +struct gfxFontStyle; + +struct CGContext; +typedef struct CGContext *CGContextRef; + +namespace mozilla { + +namespace gfx { + +class SourceSurface; +class DataSourceSurface; +class DrawTarget; +class DrawEventRecorder; +class FilterNode; +class LogForwarder; + +struct NativeSurface { + NativeSurfaceType mType; + SurfaceFormat mFormat; + gfx::IntSize mSize; + void *mSurface; +}; + +struct NativeFont { + NativeFontType mType; + void *mFont; +}; + +/** + * 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. */ +}; + +/** + * 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. +}; + +/** + * 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. */ + +}; + +/** + * 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 RefCounted<GradientStops> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStops) + virtual ~GradientStops() {} + + virtual BackendType GetBackendType() const = 0; + virtual bool IsValid() const { return true; } + +protected: + GradientStops() {} +}; + +/** + * 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() {} + + virtual PatternType GetType() const = 0; + +protected: + Pattern() {} +}; + +class ColorPattern : public Pattern +{ +public: + // Explicit because consumers should generally use ToDeviceColor when + // creating a ColorPattern. + explicit ColorPattern(const Color &aColor) + : mColor(aColor) + {} + + virtual PatternType GetType() const override + { + return PatternType::COLOR; + } + + Color 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. + */ +class LinearGradientPattern : public Pattern +{ +public: + /// For constructor parameter description, see member data documentation. + LinearGradientPattern(const Point &aBegin, + const Point &aEnd, + GradientStops *aStops, + const Matrix &aMatrix = Matrix()) + : mBegin(aBegin) + , mEnd(aEnd) + , mStops(aStops) + , mMatrix(aMatrix) + { + } + + virtual PatternType GetType() const override + { + return PatternType::LINEAR_GRADIENT; + } + + 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. */ + RefPtr<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 */ +}; + +/** + * 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. + */ +class RadialGradientPattern : public Pattern +{ +public: + /// For constructor parameter description, see member data documentation. + RadialGradientPattern(const Point &aCenter1, + const Point &aCenter2, + Float aRadius1, + Float aRadius2, + GradientStops *aStops, + const Matrix &aMatrix = Matrix()) + : mCenter1(aCenter1) + , mCenter2(aCenter2) + , mRadius1(aRadius1) + , mRadius2(aRadius2) + , mStops(aStops) + , mMatrix(aMatrix) + { + } + + virtual PatternType GetType() const override + { + return PatternType::RADIAL_GRADIENT; + } + + 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. + RefPtr<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 +}; + +/** + * 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. + */ +class SurfacePattern : public Pattern +{ +public: + /// For constructor parameter description, see member data documentation. + SurfacePattern(SourceSurface *aSourceSurface, ExtendMode aExtendMode, + const Matrix &aMatrix = Matrix(), + SamplingFilter aSamplingFilter = SamplingFilter::GOOD, + const IntRect &aSamplingRect = IntRect()) + : mSurface(aSourceSurface) + , mExtendMode(aExtendMode) + , mSamplingFilter(aSamplingFilter) + , mMatrix(aMatrix) + , mSamplingRect(aSamplingRect) + {} + + virtual PatternType GetType() const override + { + return PatternType::SURFACE; + } + + RefPtr<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. */ +}; + +class StoredPattern; +class DrawTargetCaptureImpl; + +/** + * 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 external::AtomicRefCounted<SourceSurface> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurface) + virtual ~SourceSurface() {} + + virtual SurfaceType GetType() const = 0; + virtual IntSize GetSize() const = 0; + virtual SurfaceFormat GetFormat() const = 0; + + /** 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 function will get a DataSourceSurface for this surface, a + * DataSourceSurface's data can be accessed directly. + */ + virtual already_AddRefed<DataSourceSurface> GetDataSurface() = 0; + + /** 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) { + return mUserData.Get(key); + } + +protected: + friend class DrawTargetCaptureImpl; + friend class StoredPattern; + + // This is for internal use, it ensures the SourceSurface's data remains + // valid during the lifetime of the SourceSurface. + // @todo XXX - We need something better here :(. But we may be able to get rid + // of CreateWrappingDataSourceSurface in the future. + virtual void GuaranteePersistance() {} + + UserData mUserData; +}; + +class DataSourceSurface : public SourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurface, override) + DataSourceSurface() + : mIsMapped(false) + { + } + +#ifdef DEBUG + virtual ~DataSourceSurface() + { + MOZ_ASSERT(!mIsMapped, "Someone forgot to call Unmap()"); + } +#endif + + struct MappedSurface { + uint8_t *mData; + int32_t mStride; + }; + + 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 { + public: + explicit ScopedMap(DataSourceSurface* aSurface, MapType aType) + : mSurface(aSurface) + , mIsMapped(aSurface->Map(aType, &mMap)) {} + + virtual ~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; + } + + bool IsMapped() const { return mIsMapped; } + + private: + RefPtr<DataSourceSurface> mSurface; + MappedSurface mMap; + bool mIsMapped; + }; + + virtual 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. + */ + 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. + */ + virtual bool Map(MapType, MappedSurface *aMappedSurface) + { + aMappedSurface->mData = GetData(); + aMappedSurface->mStride = Stride(); + mIsMapped = !!aMappedSurface->mData; + return mIsMapped; + } + + virtual void Unmap() + { + MOZ_ASSERT(mIsMapped); + mIsMapped = false; + } + + /** + * Returns a DataSourceSurface with the same data as this one, but + * guaranteed to have surface->GetType() == SurfaceType::DATA. + */ + virtual already_AddRefed<DataSourceSurface> GetDataSurface() override; + +protected: + bool mIsMapped; +}; + +/** This is an abstract object that accepts path segments. */ +class PathSink : public RefCounted<PathSink> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathSink) + virtual ~PathSink() {} + + /** 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; + /** Point the current subpath is at - or where the next subpath will start + * if there is no active subpath. + */ + virtual Point CurrentPoint() const = 0; +}; + +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 RefCounted<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; + + /** 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 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) + /** 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; +}; + +/** 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. +}; + +struct GlyphMetrics +{ + // Horizontal distance from the origin to the leftmost side of the bounding + // box of the drawn glyph. This can be negative! + Float mXBearing; + // Horizontal distance from the origin of this glyph to the origin of the + // next glyph. + Float mXAdvance; + // Vertical distance from the origin to the topmost side of the bounding box + // of the drawn glyph. + Float mYBearing; + // Vertical distance from the origin of this glyph to the origin of the next + // glyph, this is used when drawing vertically and will typically be 0. + Float mYAdvance; + // Width of the glyph's black box. + Float mWidth; + // Height of the glyph's black box. + Float mHeight; +}; + +/** 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 RefCounted<ScaledFont> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFont) + virtual ~ScaledFont() {} + + typedef void (*FontFileDataOutput)(const uint8_t *aData, uint32_t aLength, uint32_t aIndex, Float aGlyphSize, void *aBaton); + typedef void (*FontInstanceDataOutput)(const uint8_t* aData, uint32_t aLength, void* aBaton); + typedef void (*FontDescriptorOutput)(const uint8_t *aData, uint32_t aLength, Float aFontSize, void *aBaton); + + virtual FontType GetType() const = 0; + virtual AntialiasMode GetDefaultAAMode() { + if (gfxPrefs::DisableAllTextAA()) { + return AntialiasMode::NONE; + } + + return AntialiasMode::DEFAULT; + } + + /** 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; + + /* This gets the metrics of a set of glyphs for the current font face. + */ + virtual void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) = 0; + + virtual bool GetFontFileData(FontFileDataOutput, void *) { return false; } + + virtual bool GetFontInstanceData(FontInstanceDataOutput, void *) { return false; } + + virtual bool GetFontDescriptor(FontDescriptorOutput, void *) { return false; } + + void AddUserData(UserDataKey *key, void *userData, void (*destroy)(void*)) { + mUserData.Add(key, userData, destroy); + } + void *GetUserData(UserDataKey *key) { + return mUserData.Get(key); + } + +protected: + ScaledFont() {} + + UserData mUserData; +}; + +/** + * Derived classes hold a native font resource from which to create + * ScaledFonts. + */ +class NativeFontResource : public RefCounted<NativeFontResource> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResource) + + /** + * Creates a ScaledFont using the font corresponding to the index and + * the given glyph size. + * + * @param aIndex index for the font within the resource. + * @param aGlyphSize the size of ScaledFont required. + * @param aInstanceData pointer to read-only buffer of any available instance data. + * @param aInstanceDataLength the size of the instance data. + * @return an already_addrefed ScaledFont, containing nullptr if failed. + */ + virtual already_AddRefed<ScaledFont> + CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) = 0; + + virtual ~NativeFontResource() {}; +}; + +/** This class is designed to allow passing additional glyph rendering + * parameters to the glyph drawing functions. This is an empty wrapper class + * merely used to allow holding on to and passing around platform specific + * parameters. This is because different platforms have unique rendering + * parameters. + */ +class GlyphRenderingOptions : public RefCounted<GlyphRenderingOptions> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GlyphRenderingOptions) + virtual ~GlyphRenderingOptions() {} + + virtual FontType GetType() const = 0; + +protected: + GlyphRenderingOptions() {} +}; + +class DrawTargetCapture; + +/** 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 RefCounted<DrawTarget> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTarget) + DrawTarget() : mTransformDirty(false), mPermitSubpixelAA(false) {} + virtual ~DrawTarget() {} + + virtual bool IsValid() const { return true; }; + virtual DrawTargetType GetType() const = 0; + + virtual BackendType GetBackendType() const = 0; + + virtual bool IsRecording() const { return false; } + + /** + * 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; + virtual IntSize GetSize() = 0; + + /** + * 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; + + /** + * Realize a DrawTargetCapture onto the draw target. + * + * @param aSource Capture DrawTarget to draw + * @param aTransform Transform to apply when replaying commands + */ + virtual void DrawCapturedDT(DrawTargetCapture *aCaptureDT, + const Matrix& aTransform); + + /** + * 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 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 aColor Color of the drawn shadow + * @param aOffset Offset of the shadow + * @param aSigma Sigma used for the guassian filter kernel + * @param aOperator Composition operator used + */ + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) = 0; + + /** + * 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; + + /** + * 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 clyphs on the draw target with a certain source pattern. + */ + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr) = 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 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"); } + + /** + * 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"); } + + /** + * 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 that captures the drawing commands and can be replayed + * onto a compatible DrawTarget afterwards. + * + * @param aSize Size of the area this DT will capture. + */ + virtual already_AddRefed<DrawTargetCapture> CreateCaptureDT(const IntSize& aSize); + + /** + * 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 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; } + + /* + * Get the metrics of a glyph, including any additional spacing that is taken + * during rasterization to this backends (for example because of antialiasing + * filters. + * + * aScaledFont The scaled font used when drawing. + * aGlyphIndices An array of indices for the glyphs whose the metrics are wanted + * aNumGlyphs The amount of elements in aGlyphIndices + * aGlyphMetrics The glyph metrics + */ + virtual void GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices, + uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) + { + aScaledFont->GetGlyphDesignMetrics(aGlyphIndices, aNumGlyphs, aGlyphMetrics); + } + + /** + * 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 IsDualDrawTarget() const { return false; } + 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; + } + + /** + * 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; + +#ifdef USE_SKIA_GPU + virtual bool InitWithGrContext(GrContext* aGrContext, + const IntSize &aSize, + SurfaceFormat aFormat) + { + MOZ_CRASH("GFX: InitWithGrContext"); + } +#endif + +protected: + UserData mUserData; + Matrix mTransform; + IntRect mOpaqueRect; + bool mTransformDirty : 1; + bool mPermitSubpixelAA : 1; + + SurfaceFormat mFormat; +}; + +class DrawTargetCapture : public DrawTarget +{ +}; + +class DrawEventRecorder : public RefCounted<DrawEventRecorder> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorder) + virtual ~DrawEventRecorder() { } +}; + +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(8192) + , mMaxAllocSize(52000000) + {} +}; + +class GFX2D_API Factory +{ +public: + static void Init(const Config& aConfig); + static void ShutDown(); + + static bool HasSSE2(); + + /** + * 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 that the given buffer size doesn't exceed the allocation limit. + */ + static bool CheckBufferSize(int32_t bufSize); + + /** 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<DrawTarget> + CreateRecordingDrawTarget(DrawEventRecorder *aRecorder, DrawTarget *aDT); + + static already_AddRefed<DrawTarget> + CreateDrawTargetForData(BackendType aBackend, unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat, bool aUninitialized = false); + + static already_AddRefed<ScaledFont> + CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize); + +#ifdef MOZ_WIDGET_GTK + static already_AddRefed<ScaledFont> + CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize); +#endif + + /** + * This creates a NativeFontResource from TrueType data. + * + * @param aData Pointer to the data + * @param aSize Size of the TrueType data + * @param aType Type of NativeFontResource that should be created. + * @return a NativeFontResource of nullptr if failed. + */ + static already_AddRefed<NativeFontResource> + CreateNativeFontResource(uint8_t *aData, uint32_t aSize, FontType aType); + + /** + * This creates a scaled font with an associated cairo_scaled_font_t, and + * must be used when using the Cairo backend. The NativeFont and + * cairo_scaled_font_t* parameters must correspond to the same font. + */ + static already_AddRefed<ScaledFont> + CreateScaledFontWithCairo(const NativeFont &aNativeFont, Float aSize, cairo_scaled_font_t* aScaledFont); + + /** + * 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 already_AddRefed<DrawEventRecorder> + CreateEventRecorderForFile(const char *aFilename); + + static void SetGlobalEventRecorder(DrawEventRecorder *aRecorder); + + static uint32_t GetMaxSurfaceSize(BackendType aType); + + static LogForwarder* GetLogForwarder() { return sConfig ? sConfig->mLogForwarder : nullptr; } + +private: + static Config* sConfig; +public: + +#ifdef USE_SKIA_GPU + static already_AddRefed<DrawTarget> + CreateDrawTargetSkiaWithGrContext(GrContext* aGrContext, + const IntSize &aSize, + SurfaceFormat aFormat); +#endif + + static void PurgeAllCaches(); + + static already_AddRefed<DrawTarget> + CreateDualDrawTarget(DrawTarget *targetA, DrawTarget *targetB); + + /* + * This creates a new tiled DrawTarget. When a tiled drawtarget is used the + * drawing is distributed over number of tiles which may each hold an + * individual offset. The tiles in the set must each have the same backend + * and format. + */ + static already_AddRefed<DrawTarget> CreateTiledDrawTarget(const TileSet& aTileSet); + + static bool DoesBackendSupportDataDrawtarget(BackendType aType); + +#ifdef USE_SKIA + static already_AddRefed<DrawTarget> CreateDrawTargetWithSkCanvas(SkCanvas* aCanvas); +#endif + +#ifdef XP_DARWIN + static already_AddRefed<GlyphRenderingOptions> + CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor); +#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 bool SetDWriteFactory(IDWriteFactory *aFactory); + static ID3D11Device *GetDirect3D11Device(); + static ID2D1Device *GetD2D1Device(); + static IDWriteFactory *GetDWriteFactory(); + static bool SupportsD2D1(); + + static already_AddRefed<GlyphRenderingOptions> + CreateDWriteGlyphRenderingOptions(IDWriteRenderingParams *aParams); + + static uint64_t GetD2DVRAMUsageDrawTarget(); + static uint64_t GetD2DVRAMUsageSourceSurface(); + static void D2DCleanup(); + + static already_AddRefed<ScaledFont> + CreateScaledFontForDWriteFont(IDWriteFontFace* aFontFace, + const gfxFontStyle* aStyle, + Float aSize, + bool aUseEmbeddedBitmap, + bool aForceGDIMode); + +private: + static ID2D1Device *mD2D1Device; + static ID3D11Device *mD3D11Device; + static IDWriteFactory *mDWriteFactory; +#endif + + static DrawEventRecorder *mRecorder; +}; + +} // 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 000000000..744d0d500 --- /dev/null +++ b/gfx/2d/AutoHelpersWin.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_AutoHelpersWin_h +#define mozilla_gfx_AutoHelpersWin_h + +#include <windows.h> + +namespace mozilla { +namespace gfx { + +// Get the global device context, and auto-release it on destruction. +class AutoDC +{ +public: + AutoDC() { + mDC = ::GetDC(nullptr); + } + + ~AutoDC() { + ::ReleaseDC(nullptr, mDC); + } + + HDC GetDC() { + return mDC; + } + +private: + HDC mDC; +}; + +// Select a font into the given DC, and auto-restore. +class AutoSelectFont +{ +public: + AutoSelectFont(HDC aDC, LOGFONTW *aLogFont) + : mOwnsFont(false) + { + mFont = ::CreateFontIndirectW(aLogFont); + if (mFont) { + mOwnsFont = true; + mDC = aDC; + mOldFont = (HFONT)::SelectObject(aDC, mFont); + } else { + mOldFont = nullptr; + } + } + + AutoSelectFont(HDC aDC, HFONT aFont) + : mOwnsFont(false) + { + mDC = aDC; + mFont = aFont; + mOldFont = (HFONT)::SelectObject(aDC, aFont); + } + + ~AutoSelectFont() { + if (mOldFont) { + ::SelectObject(mDC, mOldFont); + if (mOwnsFont) { + ::DeleteObject(mFont); + } + } + } + + bool IsValid() const { + return mFont != nullptr; + } + + HFONT GetFont() const { + return mFont; + } + +private: + HDC mDC; + HFONT mFont; + HFONT mOldFont; + bool mOwnsFont; +}; + +} // gfx +} // mozilla + +#endif // mozilla_gfx_AutoHelpersWin_h diff --git a/gfx/2d/BaseCoord.h b/gfx/2d/BaseCoord.h new file mode 100644 index 000000000..61caf9889 --- /dev/null +++ b/gfx/2d/BaseCoord.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASECOORD_H_ +#define MOZILLA_GFX_BASECOORD_H_ + +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BaseCoord { + T value; + + // Constructors + constexpr BaseCoord() : value(0) {} + explicit constexpr BaseCoord(T aValue) : value(aValue) {} + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + operator T() const { return value; } + + friend bool operator==(Sub aA, Sub aB) { + return aA.value == aB.value; + } + friend bool operator!=(Sub aA, Sub aB) { + return aA.value != aB.value; + } + + friend Sub operator+(Sub aA, Sub aB) { + return Sub(aA.value + aB.value); + } + friend Sub operator-(Sub aA, Sub aB) { + return Sub(aA.value - aB.value); + } + friend Sub operator*(Sub aCoord, T aScale) { + return Sub(aCoord.value * aScale); + } + friend Sub operator*(T aScale, Sub aCoord) { + return Sub(aScale * aCoord.value); + } + friend Sub operator/(Sub aCoord, T aScale) { + return Sub(aCoord.value / aScale); + } + // 'scale / coord' is intentionally omitted because it doesn't make sense. + + Sub& operator+=(Sub aCoord) { + value += aCoord.value; + return *static_cast<Sub*>(this); + } + Sub& operator-=(Sub aCoord) { + value -= aCoord.value; + return *static_cast<Sub*>(this); + } + Sub& operator*=(T aScale) { + value *= aScale; + return *static_cast<Sub*>(this); + } + Sub& operator/=(T aScale) { + value /= aScale; + return *static_cast<Sub*>(this); + } + + // Since BaseCoord is implicitly convertible to its value type T, we need + // mixed-type operator overloads to avoid ambiguities at mixed-type call + // sites. As we transition more of our code to strongly-typed classes, we + // may be able to remove some or all of these overloads. + friend bool operator==(Sub aA, T aB) { + return aA.value == aB; + } + friend bool operator==(T aA, Sub aB) { + return aA == aB.value; + } + friend bool operator!=(Sub aA, T aB) { + return aA.value != aB; + } + friend bool operator!=(T aA, Sub aB) { + return aA != aB.value; + } + friend T operator+(Sub aA, T aB) { + return aA.value + aB; + } + friend T operator+(T aA, Sub aB) { + return aA + aB.value; + } + friend T operator-(Sub aA, T aB) { + return aA.value - aB; + } + friend T operator-(T aA, Sub aB) { + return aA - aB.value; + } + + Sub operator-() const { + return Sub(-value); + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASECOORD_H_ */ diff --git a/gfx/2d/BaseMargin.h b/gfx/2d/BaseMargin.h new file mode 100644 index 000000000..76f0a5296 --- /dev/null +++ b/gfx/2d/BaseMargin.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASEMARGIN_H_ +#define MOZILLA_GFX_BASEMARGIN_H_ + +#include <ostream> + +#include "Types.h" + +namespace mozilla { + +/** + * Sides represents a set of physical sides. + */ +struct Sides final { + Sides() : mBits(0) {} + explicit Sides(SideBits aSideBits) + { + MOZ_ASSERT((aSideBits & ~eSideBitsAll) == 0, "illegal side bits"); + mBits = aSideBits; + } + bool IsEmpty() const { return mBits == 0; } + bool Top() const { return (mBits & eSideBitsTop) != 0; } + bool Right() const { return (mBits & eSideBitsRight) != 0; } + bool Bottom() const { return (mBits & eSideBitsBottom) != 0; } + bool Left() const { return (mBits & eSideBitsLeft) != 0; } + bool Contains(SideBits aSideBits) const + { + MOZ_ASSERT((aSideBits & ~eSideBitsAll) == 0, "illegal side bits"); + return (mBits & aSideBits) == aSideBits; + } + Sides operator|(Sides aOther) const + { + return Sides(SideBits(mBits | aOther.mBits)); + } + Sides operator|(SideBits aSideBits) const + { + return *this | Sides(aSideBits); + } + Sides& operator|=(Sides aOther) + { + mBits |= aOther.mBits; + return *this; + } + Sides& operator|=(SideBits aSideBits) + { + return *this |= Sides(aSideBits); + } + bool operator==(Sides aOther) const + { + return mBits == aOther.mBits; + } + bool operator!=(Sides aOther) const + { + return !(*this == aOther); + } + +private: + uint8_t mBits; +}; + +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. + */ +template <class T, class Sub> +struct BaseMargin { + typedef mozilla::Side SideT; // because we have a method named Side + + // Do not change the layout of these members; the Side() methods below + // depend on this order. + T top, right, bottom, left; + + // Constructors + BaseMargin() : top(0), right(0), bottom(0), left(0) {} + BaseMargin(T aTop, T aRight, T aBottom, T aLeft) : + top(aTop), right(aRight), bottom(aBottom), left(aLeft) {} + + void SizeTo(T aTop, T aRight, T aBottom, T aLeft) + { + top = aTop; right = aRight; bottom = aBottom; left = aLeft; + } + + T LeftRight() const { return left + right; } + T TopBottom() const { return top + bottom; } + + T& Side(SideT aSide) { + // This is ugly! + return *(&top + int(aSide)); + } + T Side(SideT aSide) const { + // This is ugly! + return *(&top + int(aSide)); + } + + void ApplySkipSides(Sides aSkipSides) + { + if (aSkipSides.Top()) { + top = 0; + } + if (aSkipSides.Right()) { + right = 0; + } + if (aSkipSides.Bottom()) { + bottom = 0; + } + if (aSkipSides.Left()) { + left = 0; + } + } + + // Overloaded operators. Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + bool operator==(const Sub& aMargin) const { + return top == aMargin.top && right == aMargin.right && + bottom == aMargin.bottom && left == aMargin.left; + } + bool operator!=(const Sub& aMargin) const { + return !(*this == aMargin); + } + Sub operator+(const Sub& aMargin) const { + return Sub(top + aMargin.top, right + aMargin.right, + bottom + aMargin.bottom, left + aMargin.left); + } + Sub operator-(const Sub& aMargin) const { + return Sub(top - aMargin.top, right - aMargin.right, + bottom - aMargin.bottom, left - aMargin.left); + } + Sub& operator+=(const Sub& aMargin) { + top += aMargin.top; + right += aMargin.right; + bottom += aMargin.bottom; + left += aMargin.left; + return *static_cast<Sub*>(this); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseMargin& aMargin) { + return aStream << '(' << aMargin.top << ',' << aMargin.right << ',' + << aMargin.bottom << ',' << aMargin.left << ')'; + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASEMARGIN_H_ */ diff --git a/gfx/2d/BasePoint.h b/gfx/2d/BasePoint.h new file mode 100644 index 000000000..8e4c5fc56 --- /dev/null +++ b/gfx/2d/BasePoint.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASEPOINT_H_ +#define MOZILLA_GFX_BASEPOINT_H_ + +#include <cmath> +#include <ostream> +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/TypeTraits.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub, class Coord = T> +struct BasePoint { + union { + struct { + T x, y; + }; + T components[2]; + }; + + // Constructors + constexpr BasePoint() : x(0), y(0) {} + constexpr BasePoint(Coord aX, Coord aY) : x(aX), y(aY) {} + + void MoveTo(T aX, T aY) { x = aX; y = aY; } + void MoveBy(T aDx, T aDy) { x += aDx; y += aDy; } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + bool operator==(const Sub& aPoint) const { + return x == aPoint.x && y == aPoint.y; + } + bool operator!=(const Sub& aPoint) const { + return x != aPoint.x || y != aPoint.y; + } + + Sub operator+(const Sub& aPoint) const { + return Sub(x + aPoint.x, y + aPoint.y); + } + Sub operator-(const Sub& aPoint) const { + return Sub(x - aPoint.x, y - aPoint.y); + } + Sub& operator+=(const Sub& aPoint) { + x += aPoint.x; + y += aPoint.y; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aPoint) { + x -= aPoint.x; + y -= aPoint.y; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { + return Sub(x * aScale, y * aScale); + } + Sub operator/(T aScale) const { + return Sub(x / aScale, y / aScale); + } + + Sub operator-() const { + return Sub(-x, -y); + } + + T DotProduct(const Sub& aPoint) const { + return x * aPoint.x + y * aPoint.y; + } + + Coord Length() const { + return hypot(x, y); + } + + T LengthSquare() const { + return x * x + y * y; + } + + // Round() is *not* rounding to nearest integer if the values are negative. + // They are always rounding as floor(n + 0.5). + // See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 + Sub& Round() { + x = Coord(floor(T(x) + T(0.5))); + y = Coord(floor(T(y) + T(0.5))); + return *static_cast<Sub*>(this); + } + + // "Finite" means not inf and not NaN + bool IsFinite() const + { + typedef typename mozilla::Conditional<mozilla::IsSame<T, float>::value, float, double>::Type FloatType; + return (mozilla::IsFinite(FloatType(x)) && mozilla::IsFinite(FloatType(y))); + return true; + } + + void Clamp(T aMaxAbsValue) + { + x = std::max(std::min(x, aMaxAbsValue), -aMaxAbsValue); + y = std::max(std::min(y, aMaxAbsValue), -aMaxAbsValue); + } + + friend std::ostream& operator<<(std::ostream& stream, const BasePoint<T, Sub, Coord>& aPoint) { + return stream << '(' << aPoint.x << ',' << aPoint.y << ')'; + } + +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASEPOINT_H_ */ diff --git a/gfx/2d/BasePoint3D.h b/gfx/2d/BasePoint3D.h new file mode 100644 index 000000000..41e387576 --- /dev/null +++ b/gfx/2d/BasePoint3D.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_BASEPOINT3D_H_ +#define MOZILLA_BASEPOINT3D_H_ + +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BasePoint3D { + union { + struct { + T x, y, z; + }; + T components[3]; + }; + + // Constructors + BasePoint3D() : x(0), y(0), z(0) {} + BasePoint3D(T aX, T aY, T aZ) : x(aX), y(aY), z(aZ) {} + + void MoveTo(T aX, T aY, T aZ) { x = aX; y = aY; z = aZ; } + void MoveBy(T aDx, T aDy, T aDz) { x += aDx; y += aDy; z += aDz; } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + T& operator[](int aIndex) { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 2); + return *((&x)+aIndex); + } + + const T& operator[](int aIndex) const { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 2); + return *((&x)+aIndex); + } + + bool operator==(const Sub& aPoint) const { + return x == aPoint.x && y == aPoint.y && z == aPoint.z; + } + bool operator!=(const Sub& aPoint) const { + return x != aPoint.x || y != aPoint.y || z != aPoint.z; + } + + Sub operator+(const Sub& aPoint) const { + return Sub(x + aPoint.x, y + aPoint.y, z + aPoint.z); + } + Sub operator-(const Sub& aPoint) const { + return Sub(x - aPoint.x, y - aPoint.y, z - aPoint.z); + } + Sub& operator+=(const Sub& aPoint) { + x += aPoint.x; + y += aPoint.y; + z += aPoint.z; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aPoint) { + x -= aPoint.x; + y -= aPoint.y; + z -= aPoint.z; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { + return Sub(x * aScale, y * aScale, z * aScale); + } + Sub operator/(T aScale) const { + return Sub(x / aScale, y / aScale, z / aScale); + } + + Sub& operator*=(T aScale) { + x *= aScale; + y *= aScale; + z *= aScale; + return *static_cast<Sub*>(this); + } + + Sub& operator/=(T aScale) { + x /= aScale; + y /= aScale; + z /= aScale; + return *static_cast<Sub*>(this); + } + + Sub operator-() const { + return Sub(-x, -y, -z); + } + + Sub CrossProduct(const Sub& aPoint) const { + return Sub(y * aPoint.z - aPoint.y * z, + z * aPoint.x - aPoint.z * x, + x * aPoint.y - aPoint.x * y); + } + + T DotProduct(const Sub& aPoint) const { + return x * aPoint.x + y * aPoint.y + z * aPoint.z; + } + + T Length() const { + return sqrt(x*x + y*y + z*z); + } + + // Invalid for points with distance from origin of 0. + void Normalize() { + *this /= Length(); + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_BASEPOINT3D_H_ */ diff --git a/gfx/2d/BasePoint4D.h b/gfx/2d/BasePoint4D.h new file mode 100644 index 000000000..3f4d71011 --- /dev/null +++ b/gfx/2d/BasePoint4D.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_BASEPOINT4D_H_ +#define MOZILLA_BASEPOINT4D_H_ + +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BasePoint4D { + union { + struct { + T x, y, z, w; + }; + T components[4]; + }; + + // Constructors + BasePoint4D() : x(0), y(0), z(0), w(0) {} + BasePoint4D(T aX, T aY, T aZ, T aW) : x(aX), y(aY), z(aZ), w(aW) {} + + void MoveTo(T aX, T aY, T aZ, T aW) { x = aX; y = aY; z = aZ; w = aW; } + void MoveBy(T aDx, T aDy, T aDz, T aDw) { x += aDx; y += aDy; z += aDz; w += aDw; } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + bool operator==(const Sub& aPoint) const { + return x == aPoint.x && y == aPoint.y && + z == aPoint.z && w == aPoint.w; + } + bool operator!=(const Sub& aPoint) const { + return x != aPoint.x || y != aPoint.y || + z != aPoint.z || w != aPoint.w; + } + + Sub operator+(const Sub& aPoint) const { + return Sub(x + aPoint.x, y + aPoint.y, z + aPoint.z, w + aPoint.w); + } + Sub operator-(const Sub& aPoint) const { + return Sub(x - aPoint.x, y - aPoint.y, z - aPoint.z, w - aPoint.w); + } + Sub& operator+=(const Sub& aPoint) { + x += aPoint.x; + y += aPoint.y; + z += aPoint.z; + w += aPoint.w; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aPoint) { + x -= aPoint.x; + y -= aPoint.y; + z -= aPoint.z; + w -= aPoint.w; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { + return Sub(x * aScale, y * aScale, z * aScale, w * aScale); + } + Sub operator/(T aScale) const { + return Sub(x / aScale, y / aScale, z / aScale, w / aScale); + } + + Sub& operator*=(T aScale) { + x *= aScale; + y *= aScale; + z *= aScale; + w *= aScale; + return *static_cast<Sub*>(this); + } + + Sub& operator/=(T aScale) { + x /= aScale; + y /= aScale; + z /= aScale; + w /= aScale; + return *static_cast<Sub*>(this); + } + + Sub operator-() const { + return Sub(-x, -y, -z, -w); + } + + T& operator[](int aIndex) { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index"); + return *((&x)+aIndex); + } + + const T& operator[](int aIndex) const { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index"); + return *((&x)+aIndex); + } + + T DotProduct(const Sub& aPoint) const { + return x * aPoint.x + y * aPoint.y + z * aPoint.z + w * aPoint.w; + } + + // Ignores the 4th component! + Sub CrossProduct(const Sub& aPoint) const { + return Sub(y * aPoint.z - aPoint.y * z, + z * aPoint.x - aPoint.z * x, + x * aPoint.y - aPoint.x * y, + 0); + } + + T Length() const { + return sqrt(x*x + y*y + z*z + w*w); + } + + void Normalize() { + *this /= Length(); + } + + bool HasPositiveWCoord() { return w > 0; } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_BASEPOINT4D_H_ */ diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h new file mode 100644 index 000000000..57d01ba09 --- /dev/null +++ b/gfx/2d/BaseRect.h @@ -0,0 +1,586 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASERECT_H_ +#define MOZILLA_GFX_BASERECT_H_ + +#include <algorithm> +#include <cmath> +#include <ostream> + +#include "mozilla/Assertions.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/TypeTraits.h" +#include "Types.h" + +namespace mozilla { +namespace gfx { + +/** + * Rectangles have two interpretations: a set of (zero-size) points, + * and a rectangular area of the plane. Most rectangle operations behave + * the same no matter what interpretation is being used, but some operations + * differ: + * -- Equality tests behave differently. When a rectangle represents an area, + * all zero-width and zero-height rectangles are equal to each other since they + * represent the empty area. But when a rectangle represents a set of + * mathematical points, zero-width and zero-height rectangles can be unequal. + * -- The union operation can behave differently. When rectangles represent + * areas, taking the union of a zero-width or zero-height rectangle with + * another rectangle can just ignore the empty rectangle. But when rectangles + * represent sets of mathematical points, we may need to extend the latter + * rectangle to include the points of a zero-width or zero-height rectangle. + * + * To ensure that these interpretations are explicitly disambiguated, we + * deny access to the == and != operators and require use of IsEqualEdges and + * IsEqualInterior instead. Similarly we provide separate Union and UnionEdges + * methods. + * + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. + */ +template <class T, class Sub, class Point, class SizeT, class MarginT> +struct BaseRect { + T x, y, width, height; + + // Constructors + BaseRect() : x(0), y(0), width(0), height(0) {} + BaseRect(const Point& aOrigin, const SizeT &aSize) : + x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height) + { + } + BaseRect(T aX, T aY, T aWidth, T aHeight) : + x(aX), y(aY), width(aWidth), height(aHeight) + { + } + + // Emptiness. An empty rect is one that has no area, i.e. its height or width + // is <= 0 + bool IsEmpty() const { return height <= 0 || width <= 0; } + void SetEmpty() { width = height = 0; } + + // "Finite" means not inf and not NaN + bool IsFinite() const + { + typedef typename mozilla::Conditional<mozilla::IsSame<T, float>::value, float, double>::Type FloatType; + return (mozilla::IsFinite(FloatType(x)) && + mozilla::IsFinite(FloatType(y)) && + mozilla::IsFinite(FloatType(width)) && + mozilla::IsFinite(FloatType(height))); + } + + // Returns true if this rectangle contains the interior of aRect. Always + // returns true if aRect is empty, and always returns false is aRect is + // nonempty but this rect is empty. + bool Contains(const Sub& aRect) const + { + return aRect.IsEmpty() || + (x <= aRect.x && aRect.XMost() <= XMost() && + y <= aRect.y && aRect.YMost() <= YMost()); + } + // Returns true if this rectangle contains the point. Points are considered + // in the rectangle if they are on the left or top edge, but outside if they + // are on the right or bottom edge. + bool Contains(T aX, T aY) const + { + return x <= aX && aX < XMost() && + y <= aY && aY < YMost(); + } + // Returns true if this rectangle contains the point. Points are considered + // in the rectangle if they are on the left or top edge, but outside if they + // are on the right or bottom edge. + bool Contains(const Point& aPoint) const { return Contains(aPoint.x, aPoint.y); } + + // Intersection. Returns TRUE if the receiver's area has non-empty + // intersection with aRect's area, and FALSE otherwise. + // Always returns false if aRect is empty or 'this' is empty. + bool Intersects(const Sub& aRect) const + { + return !IsEmpty() && !aRect.IsEmpty() && + x < aRect.XMost() && aRect.x < XMost() && + y < aRect.YMost() && aRect.y < YMost(); + } + // Returns the rectangle containing the intersection of the points + // (including edges) of *this and aRect. If there are no points in that + // intersection, returns an empty rectangle with x/y set to the std::max of the x/y + // of *this and aRect. + MOZ_MUST_USE Sub Intersect(const Sub& aRect) const + { + Sub result; + result.x = std::max<T>(x, aRect.x); + result.y = std::max<T>(y, aRect.y); + result.width = std::min<T>(x - result.x + width, aRect.x - result.x + aRect.width); + result.height = std::min<T>(y - result.y + height, aRect.y - result.y + aRect.height); + if (result.width < 0 || result.height < 0) { + result.SizeTo(0, 0); + } + return result; + } + // Sets *this to be the rectangle containing the intersection of the points + // (including edges) of *this and aRect. If there are no points in that + // intersection, sets *this to be an empty rectangle with x/y set to the std::max + // of the x/y of *this and aRect. + // + // 'this' can be the same object as either aRect1 or aRect2 + bool IntersectRect(const Sub& aRect1, const Sub& aRect2) + { + *static_cast<Sub*>(this) = aRect1.Intersect(aRect2); + return !IsEmpty(); + } + + // Returns the smallest rectangle that contains both the area of both + // this and aRect2. + // Thus, empty input rectangles are ignored. + // If both rectangles are empty, returns this. + // WARNING! This is not safe against overflow, prefer using SafeUnion instead + // when dealing with int-based rects. + MOZ_MUST_USE Sub Union(const Sub& aRect) const + { + if (IsEmpty()) { + return aRect; + } else if (aRect.IsEmpty()) { + return *static_cast<const Sub*>(this); + } else { + return UnionEdges(aRect); + } + } + // Returns the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // WARNING! This is not safe against overflow, prefer using SafeUnionEdges + // instead when dealing with int-based rects. + MOZ_MUST_USE Sub UnionEdges(const Sub& aRect) const + { + Sub result; + result.x = std::min(x, aRect.x); + result.y = std::min(y, aRect.y); + result.width = std::max(XMost(), aRect.XMost()) - result.x; + result.height = std::max(YMost(), aRect.YMost()) - result.y; + return result; + } + // Computes the smallest rectangle that contains both the area of both + // aRect1 and aRect2, and fills 'this' with the result. + // Thus, empty input rectangles are ignored. + // If both rectangles are empty, sets 'this' to aRect2. + // + // 'this' can be the same object as either aRect1 or aRect2 + void UnionRect(const Sub& aRect1, const Sub& aRect2) + { + *static_cast<Sub*>(this) = aRect1.Union(aRect2); + } + + // Computes the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // + // 'this' can be the same object as either aRect1 or aRect2 + void UnionRectEdges(const Sub& aRect1, const Sub& aRect2) + { + *static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2); + } + + // Expands the rect to include the point + void ExpandToEnclose(const Point& aPoint) + { + if (aPoint.x < x) { + width = XMost() - aPoint.x; + x = aPoint.x; + } else if (aPoint.x > XMost()) { + width = aPoint.x - x; + } + if (aPoint.y < y) { + height = YMost() - aPoint.y; + y = aPoint.y; + } else if (aPoint.y > YMost()) { + height = aPoint.y - y; + } + } + + void SetRect(T aX, T aY, T aWidth, T aHeight) + { + x = aX; y = aY; width = aWidth; height = aHeight; + } + void SetRect(const Point& aPt, const SizeT& aSize) + { + SetRect(aPt.x, aPt.y, aSize.width, aSize.height); + } + void MoveTo(T aX, T aY) { x = aX; y = aY; } + void MoveTo(const Point& aPoint) { x = aPoint.x; y = aPoint.y; } + void MoveBy(T aDx, T aDy) { x += aDx; y += aDy; } + void MoveBy(const Point& aPoint) { x += aPoint.x; y += aPoint.y; } + void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; } + void SizeTo(const SizeT& aSize) { width = aSize.width; height = aSize.height; } + + void Inflate(T aD) { Inflate(aD, aD); } + void Inflate(T aDx, T aDy) + { + x -= aDx; + y -= aDy; + width += 2 * aDx; + height += 2 * aDy; + } + void Inflate(const MarginT& aMargin) + { + x -= aMargin.left; + y -= aMargin.top; + width += aMargin.LeftRight(); + height += aMargin.TopBottom(); + } + void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); } + + void Deflate(T aD) { Deflate(aD, aD); } + void Deflate(T aDx, T aDy) + { + x += aDx; + y += aDy; + width = std::max(T(0), width - 2 * aDx); + height = std::max(T(0), height - 2 * aDy); + } + void Deflate(const MarginT& aMargin) + { + x += aMargin.left; + y += aMargin.top; + width = std::max(T(0), width - aMargin.LeftRight()); + height = std::max(T(0), height - aMargin.TopBottom()); + } + void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); } + + // Return true if the rectangles contain the same set of points, including + // points on the edges. + // Use when we care about the exact x/y/width/height values being + // equal (i.e. we care about differences in empty rectangles). + bool IsEqualEdges(const Sub& aRect) const + { + return x == aRect.x && y == aRect.y && + width == aRect.width && height == aRect.height; + } + // Return true if the rectangles contain the same area of the plane. + // Use when we do not care about differences in empty rectangles. + bool IsEqualInterior(const Sub& aRect) const + { + return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty()); + } + + friend Sub operator+(Sub aSub, const Point& aPoint) + { + aSub += aPoint; + return aSub; + } + friend Sub operator-(Sub aSub, const Point& aPoint) + { + aSub -= aPoint; + return aSub; + } + friend Sub operator+(Sub aSub, const SizeT& aSize) + { + aSub += aSize; + return aSub; + } + friend Sub operator-(Sub aSub, const SizeT& aSize) + { + aSub -= aSize; + return aSub; + } + Sub& operator+=(const Point& aPoint) + { + MoveBy(aPoint); + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Point& aPoint) + { + MoveBy(-aPoint); + return *static_cast<Sub*>(this); + } + Sub& operator+=(const SizeT& aSize) + { + width += aSize.width; + height += aSize.height; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const SizeT& aSize) + { + width -= aSize.width; + height -= aSize.height; + return *static_cast<Sub*>(this); + } + // Find difference as a Margin + MarginT operator-(const Sub& aRect) const + { + return MarginT(aRect.y - y, + XMost() - aRect.XMost(), + YMost() - aRect.YMost(), + aRect.x - x); + } + + // Helpers for accessing the vertices + Point TopLeft() const { return Point(x, y); } + Point TopRight() const { return Point(XMost(), y); } + Point BottomLeft() const { return Point(x, YMost()); } + Point BottomRight() const { return Point(XMost(), YMost()); } + Point AtCorner(int aCorner) const { + switch (aCorner) { + case RectCorner::TopLeft: return TopLeft(); + case RectCorner::TopRight: return TopRight(); + case RectCorner::BottomRight: return BottomRight(); + case RectCorner::BottomLeft: return BottomLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point CCWCorner(mozilla::Side side) const { + switch (side) { + case NS_SIDE_TOP: return TopLeft(); + case NS_SIDE_RIGHT: return TopRight(); + case NS_SIDE_BOTTOM: return BottomRight(); + case NS_SIDE_LEFT: return BottomLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point CWCorner(mozilla::Side side) const { + switch (side) { + case NS_SIDE_TOP: return TopRight(); + case NS_SIDE_RIGHT: return BottomRight(); + case NS_SIDE_BOTTOM: return BottomLeft(); + case NS_SIDE_LEFT: return TopLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point Center() const { return Point(x, y) + Point(width, height)/2; } + SizeT Size() const { return SizeT(width, height); } + + T Area() const { return width * height; } + + // Helper methods for computing the extents + T X() const { return x; } + T Y() const { return y; } + T Width() const { return width; } + T Height() const { return height; } + T XMost() const { return x + width; } + T YMost() const { return y + height; } + + // Get the coordinate of the edge on the given side. + T Edge(mozilla::Side aSide) const + { + switch (aSide) { + case NS_SIDE_TOP: return Y(); + case NS_SIDE_RIGHT: return XMost(); + case NS_SIDE_BOTTOM: return YMost(); + case NS_SIDE_LEFT: return X(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + + // Moves one edge of the rect without moving the opposite edge. + void SetLeftEdge(T aX) { + MOZ_ASSERT(aX <= XMost()); + width = XMost() - aX; + x = aX; + } + void SetRightEdge(T aXMost) { + MOZ_ASSERT(aXMost >= x); + width = aXMost - x; + } + void SetTopEdge(T aY) { + MOZ_ASSERT(aY <= YMost()); + height = YMost() - aY; + y = aY; + } + void SetBottomEdge(T aYMost) { + MOZ_ASSERT(aYMost >= y); + height = aYMost - y; + } + + // Round the rectangle edges to integer coordinates, such that the rounded + // rectangle has the same set of pixel centers as the original rectangle. + // Edges at offset 0.5 round up. + // Suitable for most places where integral device coordinates + // are needed, but note that any translation should be applied first to + // avoid pixel rounding errors. + // Note that this is *not* rounding to nearest integer if the values are negative. + // They are always rounding as floor(n + 0.5). + // See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 + // If you need similar method which is using NS_round(), you should create + // new |RoundAwayFromZero()| method. + void Round() + { + T x0 = static_cast<T>(floor(T(X()) + 0.5)); + T y0 = static_cast<T>(floor(T(Y()) + 0.5)); + T x1 = static_cast<T>(floor(T(XMost()) + 0.5)); + T y1 = static_cast<T>(floor(T(YMost()) + 0.5)); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Snap the rectangle edges to integer coordinates, such that the + // original rectangle contains the resulting rectangle. + void RoundIn() + { + T x0 = static_cast<T>(ceil(T(X()))); + T y0 = static_cast<T>(ceil(T(Y()))); + T x1 = static_cast<T>(floor(T(XMost()))); + T y1 = static_cast<T>(floor(T(YMost()))); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Snap the rectangle edges to integer coordinates, such that the + // resulting rectangle contains the original rectangle. + void RoundOut() + { + T x0 = static_cast<T>(floor(T(X()))); + T y0 = static_cast<T>(floor(T(Y()))); + T x1 = static_cast<T>(ceil(T(XMost()))); + T y1 = static_cast<T>(ceil(T(YMost()))); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Scale 'this' by aScale without doing any rounding. + void Scale(T aScale) { Scale(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, without doing any rounding. + void Scale(T aXScale, T aYScale) + { + T right = XMost() * aXScale; + T bottom = YMost() * aYScale; + x = x * aXScale; + y = y * aYScale; + width = right - x; + height = bottom - y; + } + // Scale 'this' by aScale, converting coordinates to integers so that the result is + // the smallest integer-coordinate rectangle containing the unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the smallest integer-coordinate rectangle containing the + // unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleRoundOut(double aXScale, double aYScale) + { + T right = static_cast<T>(ceil(double(XMost()) * aXScale)); + T bottom = static_cast<T>(ceil(double(YMost()) * aYScale)); + x = static_cast<T>(floor(double(x) * aXScale)); + y = static_cast<T>(floor(double(y) * aYScale)); + width = right - x; + height = bottom - y; + } + // Scale 'this' by aScale, converting coordinates to integers so that the result is + // the largest integer-coordinate rectangle contained by the unrounded result. + void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleRoundIn(double aXScale, double aYScale) + { + T right = static_cast<T>(floor(double(XMost()) * aXScale)); + T bottom = static_cast<T>(floor(double(YMost()) * aYScale)); + x = static_cast<T>(ceil(double(x) * aXScale)); + y = static_cast<T>(ceil(double(y) * aYScale)); + width = std::max<T>(0, right - x); + height = std::max<T>(0, bottom - y); + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is + // the smallest integer-coordinate rectangle containing the unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleInverseRoundOut(double aScale) { ScaleInverseRoundOut(aScale, aScale); } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so + // that the result is the smallest integer-coordinate rectangle containing the + // unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleInverseRoundOut(double aXScale, double aYScale) + { + T right = static_cast<T>(ceil(double(XMost()) / aXScale)); + T bottom = static_cast<T>(ceil(double(YMost()) / aYScale)); + x = static_cast<T>(floor(double(x) / aXScale)); + y = static_cast<T>(floor(double(y) / aYScale)); + width = right - x; + height = bottom - y; + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is + // the largest integer-coordinate rectangle contained by the unrounded result. + void ScaleInverseRoundIn(double aScale) { ScaleInverseRoundIn(aScale, aScale); } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so + // that the result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleInverseRoundIn(double aXScale, double aYScale) + { + T right = static_cast<T>(floor(double(XMost()) / aXScale)); + T bottom = static_cast<T>(floor(double(YMost()) / aYScale)); + x = static_cast<T>(ceil(double(x) / aXScale)); + y = static_cast<T>(ceil(double(y) / aYScale)); + width = std::max<T>(0, right - x); + height = std::max<T>(0, bottom - y); + } + + /** + * Clamp aPoint to this rectangle. It is allowed to end up on any + * edge of the rectangle. + */ + MOZ_MUST_USE Point ClampPoint(const Point& aPoint) const + { + return Point(std::max(x, std::min(XMost(), aPoint.x)), + std::max(y, std::min(YMost(), aPoint.y))); + } + + /** + * Translate this rectangle to be inside aRect. If it doesn't fit inside + * aRect then the dimensions that don't fit will be shrunk so that they + * do fit. The resulting rect is returned. + */ + MOZ_MUST_USE Sub MoveInsideAndClamp(const Sub& aRect) const + { + Sub rect(std::max(aRect.x, x), + std::max(aRect.y, y), + std::min(aRect.width, width), + std::min(aRect.height, height)); + rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width; + rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height; + return rect; + } + + // Returns the largest rectangle that can be represented with 32-bit + // signed integers, centered around a point at 0,0. As BaseRect's represent + // the dimensions as a top-left point with a width and height, the width + // and height will be the largest positive 32-bit value. The top-left + // position coordinate is divided by two to center the rectangle around a + // point at 0,0. + static Sub MaxIntRect() + { + return Sub( + static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5), + static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5), + static_cast<T>(std::numeric_limits<int32_t>::max()), + static_cast<T>(std::numeric_limits<int32_t>::max()) + ); + }; + + friend std::ostream& operator<<(std::ostream& stream, + const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) { + return stream << '(' << aRect.x << ',' << aRect.y << ',' + << aRect.width << ',' << aRect.height << ')'; + } + +private: + // Do not use the default operator== or operator!= ! + // Use IsEqualEdges or IsEqualInterior explicitly. + bool operator==(const Sub& aRect) const { return false; } + bool operator!=(const Sub& aRect) const { return false; } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASERECT_H_ */ diff --git a/gfx/2d/BaseSize.h b/gfx/2d/BaseSize.h new file mode 100644 index 000000000..7bcccc629 --- /dev/null +++ b/gfx/2d/BaseSize.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASESIZE_H_ +#define MOZILLA_GFX_BASESIZE_H_ + +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BaseSize { + union { + struct { + T width, height; + }; + T components[2]; + }; + + // Constructors + constexpr BaseSize() : width(0), height(0) {} + constexpr BaseSize(T aWidth, T aHeight) : width(aWidth), height(aHeight) {} + + void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; } + + bool IsEmpty() const { + return width <= 0 || height <= 0; + } + + bool IsSquare() const { + return width == height; + } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + bool operator==(const Sub& aSize) const { + return width == aSize.width && height == aSize.height; + } + bool operator!=(const Sub& aSize) const { + return width != aSize.width || height != aSize.height; + } + bool operator<=(const Sub& aSize) const { + return width <= aSize.width && height <= aSize.height; + } + bool operator<(const Sub& aSize) const { + return *this <= aSize && *this != aSize; + } + + Sub operator+(const Sub& aSize) const { + return Sub(width + aSize.width, height + aSize.height); + } + Sub operator-(const Sub& aSize) const { + return Sub(width - aSize.width, height - aSize.height); + } + Sub& operator+=(const Sub& aSize) { + width += aSize.width; + height += aSize.height; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aSize) { + width -= aSize.width; + height -= aSize.height; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { + return Sub(width * aScale, height * aScale); + } + Sub operator/(T aScale) const { + return Sub(width / aScale, height / aScale); + } + friend Sub operator*(T aScale, const Sub& aSize) { + return Sub(aScale * aSize.width, aScale * aSize.height); + } + void Scale(T aXScale, T aYScale) { + width *= aXScale; + height *= aYScale; + } + + Sub operator*(const Sub& aSize) const { + return Sub(width * aSize.width, height * aSize.height); + } + Sub operator/(const Sub& aSize) const { + return Sub(width / aSize.width, height / aSize.height); + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASESIZE_H_ */ diff --git a/gfx/2d/BezierUtils.cpp b/gfx/2d/BezierUtils.cpp new file mode 100644 index 000000000..292c6356b --- /dev/null +++ b/gfx/2d/BezierUtils.cpp @@ -0,0 +1,339 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=2:et:sw=2: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BezierUtils.h" + +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +Point +GetBezierPoint(const Bezier& aBezier, Float t) +{ + Float s = 1.0f - t; + + return Point( + aBezier.mPoints[0].x * s * s * s + + 3.0f * aBezier.mPoints[1].x * t * s * s + + 3.0f * aBezier.mPoints[2].x * t * t * s + + aBezier.mPoints[3].x * t * t * t, + aBezier.mPoints[0].y * s * s * s + + 3.0f * aBezier.mPoints[1].y * t * s * s + + 3.0f * aBezier.mPoints[2].y * t * t * s + + aBezier.mPoints[3].y * t * t * t + ); +} + +Point +GetBezierDifferential(const Bezier& aBezier, Float t) +{ + // Return P'(t). + + Float s = 1.0f - t; + + return Point( + -3.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s * s + + 2.0f * (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * t * s + + (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t * t), + -3.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s * s + + 2.0f * (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * t * s+ + (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t * t) + ); +} + +Point +GetBezierDifferential2(const Bezier& aBezier, Float t) +{ + // Return P''(t). + + Float s = 1.0f - t; + + return Point( + 6.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s - + (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * (s - t) - + (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t), + 6.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s - + (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * (s - t) - + (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t) + ); +} + +Float +GetBezierLength(const Bezier& aBezier, Float a, Float b) +{ + if (a < 0.5f && b > 0.5f) { + // To increase the accuracy, split into two parts. + return GetBezierLength(aBezier, a, 0.5f) + + GetBezierLength(aBezier, 0.5f, b); + } + + // Calculate length of simple bezier curve with Simpson's rule. + // _ + // / b + // length = | |P'(x)| dx + // _/ a + // + // b - a a + b + // = ----- [ |P'(a)| + 4 |P'(-----)| + |P'(b)| ] + // 6 2 + + Float fa = GetBezierDifferential(aBezier, a).Length(); + Float fab = GetBezierDifferential(aBezier, (a + b) / 2.0f).Length(); + Float fb = GetBezierDifferential(aBezier, b).Length(); + + return (b - a) / 6.0f * (fa + 4.0f * fab + fb); +} + +static void +SplitBezierA(Bezier* aSubBezier, const Bezier& aBezier, Float t) +{ + // Split bezier curve into [0,t] and [t,1] parts, and return [0,t] part. + + Float s = 1.0f - t; + + Point tmp1; + Point tmp2; + + aSubBezier->mPoints[0] = aBezier.mPoints[0]; + + aSubBezier->mPoints[1] = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t; + tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t; + tmp2 = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t; + + aSubBezier->mPoints[2] = aSubBezier->mPoints[1] * s + tmp1 * t; + tmp1 = tmp1 * s + tmp2 * t; + + aSubBezier->mPoints[3] = aSubBezier->mPoints[2] * s + tmp1 * t; +} + +static void +SplitBezierB(Bezier* aSubBezier, const Bezier& aBezier, Float t) +{ + // Split bezier curve into [0,t] and [t,1] parts, and return [t,1] part. + + Float s = 1.0f - t; + + Point tmp1; + Point tmp2; + + aSubBezier->mPoints[3] = aBezier.mPoints[3]; + + aSubBezier->mPoints[2] = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t; + tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t; + tmp2 = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t; + + aSubBezier->mPoints[1] = tmp1 * s + aSubBezier->mPoints[2] * t; + tmp1 = tmp2 * s + tmp1 * t; + + aSubBezier->mPoints[0] = tmp1 * s + aSubBezier->mPoints[1] * t; +} + +void +GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1, Float t2) +{ + Bezier tmp; + SplitBezierB(&tmp, aBezier, t1); + + Float range = 1.0f - t1; + if (range == 0.0f) { + *aSubBezier = tmp; + } else { + SplitBezierA(aSubBezier, tmp, (t2 - t1) / range); + } +} + +static Point +BisectBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, + Float* aT) +{ + // Find a nearest point on bezier curve with Binary search. + // Called from FindBezierNearestPoint. + + Float lower = 0.0f; + Float upper = 1.0f; + Float t; + + Point P, lastP; + const size_t MAX_LOOP = 32; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN; + const Float DIFF = 0.0001f; + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + P = GetBezierPoint(aBezier, t); + + // Check if it converged. + if (i > 0 && (lastP - P).LengthSquare() < DIST_MARGIN_SQUARE) { + break; + } + + Float distSquare = (P - aTarget).LengthSquare(); + if ((GetBezierPoint(aBezier, t + DIFF) - aTarget).LengthSquare() < + distSquare) { + lower = t; + } else if ((GetBezierPoint(aBezier, t - DIFF) - aTarget).LengthSquare() < + distSquare) { + upper = t; + } else { + break; + } + + lastP = P; + } + + if (aT) { + *aT = t; + } + + return P; +} + +Point +FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, + Float aInitialT, Float* aT) +{ + // Find a nearest point on bezier curve with Newton's method. + // It converges within 4 iterations in most cases. + // + // f(t_n) + // t_{n+1} = t_n - --------- + // f'(t_n) + // + // d 2 + // f(t) = ---- | P(t) - aTarget | + // dt + + Float t = aInitialT; + Point P; + Point lastP = GetBezierPoint(aBezier, t); + + const size_t MAX_LOOP = 4; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN; + for (size_t i = 0; i <= MAX_LOOP; i++) { + Point dP = GetBezierDifferential(aBezier, t); + Point ddP = GetBezierDifferential2(aBezier, t); + Float f = 2.0f * (lastP.DotProduct(dP) - aTarget.DotProduct(dP)); + Float df = 2.0f * (dP.DotProduct(dP) + lastP.DotProduct(ddP) - + aTarget.DotProduct(ddP)); + t = t - f / df; + P = GetBezierPoint(aBezier, t); + if ((P - lastP).LengthSquare() < DIST_MARGIN_SQUARE) { + break; + } + lastP = P; + + if (i == MAX_LOOP) { + // If aInitialT is too bad, it won't converge in a few iterations, + // fallback to binary search. + return BisectBezierNearestPoint(aBezier, aTarget, aT); + } + } + + if (aT) { + *aT = t; + } + + return P; +} + +void +GetBezierPointsForCorner(Bezier* aBezier, mozilla::css::Corner aCorner, + const Point& aCornerPoint, const Size& aCornerSize) +{ + // Calculate bezier control points for elliptic arc. + + const Float signsList[4][2] = { + { +1.0f, +1.0f }, + { -1.0f, +1.0f }, + { -1.0f, -1.0f }, + { +1.0f, -1.0f } + }; + const Float (& signs)[2] = signsList[aCorner]; + + aBezier->mPoints[0] = aCornerPoint; + aBezier->mPoints[0].x += signs[0] * aCornerSize.width; + + aBezier->mPoints[1] = aBezier->mPoints[0]; + aBezier->mPoints[1].x -= signs[0] * aCornerSize.width * kKappaFactor; + + aBezier->mPoints[3] = aCornerPoint; + aBezier->mPoints[3].y += signs[1] * aCornerSize.height; + + aBezier->mPoints[2] = aBezier->mPoints[3]; + aBezier->mPoints[2].y -= signs[1] * aCornerSize.height * kKappaFactor; +} + +Float +GetQuarterEllipticArcLength(Float a, Float b) +{ + // Calculate the approximate length of a quarter elliptic arc formed by radii + // (a, b), by Ramanujan's approximation of the perimeter p of an ellipse. + // _ _ + // | 2 | + // | 3 * (a - b) | + // p = PI | (a + b) + ------------------------------------------- | + // | 2 2 | + // |_ 10 * (a + b) + sqrt(a + 14 * a * b + b ) _| + // + // _ _ + // | 2 | + // | 3 * (a - b) | + // = PI | (a + b) + -------------------------------------------------- | + // | 2 2 | + // |_ 10 * (a + b) + sqrt(4 * (a + b) - 3 * (a - b) ) _| + // + // _ _ + // | 2 | + // | 3 * S | + // = PI | A + -------------------------------------- | + // | 2 2 | + // |_ 10 * A + sqrt(4 * A - 3 * S ) _| + // + // where A = a + b, S = a - b + + Float A = a + b, S = a - b; + Float A2 = A * A, S2 = S * S; + Float p = M_PI * (A + 3.0f * S2 / (10.0f * A + sqrt(4.0f * A2 - 3.0f * S2))); + return p / 4.0f; +} + +Float +CalculateDistanceToEllipticArc(const Point& P, const Point& normal, + const Point& origin, Float width, Float height) +{ + // Solve following equations with n and return smaller n. + // + // / (x, y) = P + n * normal + // | + // < _ _ 2 _ _ 2 + // | | x - origin.x | | y - origin.y | + // | | ------------ | + | ------------ | = 1 + // \ |_ width _| |_ height _| + + Float a = (P.x - origin.x) / width; + Float b = normal.x / width; + Float c = (P.y - origin.y) / height; + Float d = normal.y / height; + + Float A = b * b + d * d; + Float B = a * b + c * d; + Float C = a * a + c * c - 1; + + Float S = sqrt(B * B - A * C); + + Float n1 = (- B + S) / A; + Float n2 = (- B - S) / A; + + MOZ_ASSERT(n1 >= 0); + MOZ_ASSERT(n2 >= 0); + + return n1 < n2 ? n1 : n2; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/BezierUtils.h b/gfx/2d/BezierUtils.h new file mode 100644 index 000000000..7871f0284 --- /dev/null +++ b/gfx/2d/BezierUtils.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BezierUtils_h_ +#define mozilla_BezierUtils_h_ + +#include "mozilla/gfx/2D.h" +#include "gfxRect.h" + +namespace mozilla { +namespace gfx { + +// Control points for bezier curve +// +// mPoints[2] +// +-----___---+ mPoints[3] +// __-- +// _-- +// / +// / +// mPoints[1] + | +// | | +// || +// || +// | +// | +// | +// | +// mPoints[0] + +struct Bezier { + Point mPoints[4]; +}; + +// Calculate a point or it's differential of a bezier curve formed by +// aBezier and parameter t. +// +// GetBezierPoint = P(t) +// GetBezierDifferential = P'(t) +// GetBezierDifferential2 = P''(t) +// +// mPoints[2] +// +-----___---+ mPoints[3] +// __-- P(1) +// _-- +// + +// / P(t) +// mPoints[1] + | +// | | +// || +// || +// | +// | +// | +// | +// mPoints[0] + P(0) +Point GetBezierPoint(const Bezier& aBezier, Float t); +Point GetBezierDifferential(const Bezier& aBezier, Float t); +Point GetBezierDifferential2(const Bezier& aBezier, Float t); + +// Calculate length of a simple bezier curve formed by aBezier and range [a, b]. +Float GetBezierLength(const Bezier& aBezier, Float a, Float b); + +// Split bezier curve formed by aBezier into [0,t1], [t1,t2], [t2,1] parts, and +// stores control points for [t1,t2] to aSubBezier. +// +// ___---+ +// __+- P(1) +// _-- P(t2) +// - +// / <-- aSubBezier +// | +// | +// + +// | P(t1) +// | +// | +// | +// | +// + P(0) +void GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, + Float t1, Float t2); + +// Find a nearest point on bezier curve formed by aBezier to a point aTarget. +// aInitialT is a hint to find the parameter t for the nearest point. +// If aT is non-null, parameter for the nearest point is stored to *aT. +// This function expects a bezier curve to be an approximation of elliptic arc. +// Otherwise it will return wrong point. +// +// aTarget +// + ___---+ +// __-- +// _-- +// + +// / nearest point = P(t = *aT) +// | +// | +// | +// + P(aInitialT) +// | +// | +// | +// | +// + +Point FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, + Float aInitialT, Float* aT=nullptr); + +// Calculate control points for a bezier curve that is an approximation of +// an elliptic arc. +// +// aCornerSize.width +// |<----------------->| +// | | +// aCornerPoint| mPoints[2] | +// -------------+-------+-----___---+ mPoints[3] +// ^ | __-- +// | | _-- +// | | - +// | | / +// aCornerSize.height | mPoints[1] + | +// | | | +// | || +// | || +// | | +// | | +// | | +// v mPoints[0] | +// -------------+ +void GetBezierPointsForCorner(Bezier* aBezier, mozilla::css::Corner aCorner, + const Point& aCornerPoint, + const Size& aCornerSize); + +// Calculate the approximate length of a quarter elliptic arc formed by radii +// (a, b). +// +// a +// |<----------------->| +// | | +// ---+-------------___---+ +// ^ | __-- +// | | _-- +// | | - +// | | / +// b | | | +// | | | +// | || +// | || +// | | +// | | +// | | +// v | +// ---+ +Float GetQuarterEllipticArcLength(Float a, Float b); + +// Calculate the distance between an elliptic arc formed by (origin, width, +// height), and a point P, along a line formed by |P + n * normal|. +// P should be outside of the ellipse, and the line should cross with the +// ellipse twice at n > 0 points. +// +// width +// |<----------------->| +// origin | | +// -----------+-------------___---+ +// ^ normal | __-- +// | P +->__ | _-- +// | --__ - +// | | --+ +// height | | | +// | | | +// | || +// | || +// | | +// | | +// | | +// v | +// -----------+ +Float CalculateDistanceToEllipticArc(const Point& P, const Point& normal, + const Point& origin, + Float width, Float height); + +} // namespace gfx +} // namespace mozilla + +#endif /* mozilla_BezierUtils_h_ */ diff --git a/gfx/2d/BigEndianInts.h b/gfx/2d/BigEndianInts.h new file mode 100644 index 000000000..aa4f0dfb2 --- /dev/null +++ b/gfx/2d/BigEndianInts.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_BigEndianInts_h +#define mozilla_BigEndianInts_h + +#include "mozilla/EndianUtils.h" + +namespace mozilla { + +#pragma pack(push, 1) + +struct BigEndianUint16 +{ +#ifdef __SUNPRO_CC + BigEndianUint16& operator=(const uint16_t aValue) + { + value = NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT BigEndianUint16(const uint16_t aValue) + { + value = NativeEndian::swapToBigEndian(aValue); + } +#endif + + operator uint16_t() const + { + return NativeEndian::swapFromBigEndian(value); + } + + friend inline bool + operator==(const BigEndianUint16& lhs, const BigEndianUint16& rhs) + { + return lhs.value == rhs.value; + } + + friend inline bool + operator!=(const BigEndianUint16& lhs, const BigEndianUint16& rhs) + { + return !(lhs == rhs); + } + +private: + uint16_t value; +}; + +struct BigEndianUint32 +{ +#ifdef __SUNPRO_CC + BigEndianUint32& operator=(const uint32_t aValue) + { + value = NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT BigEndianUint32(const uint32_t aValue) + { + value = NativeEndian::swapToBigEndian(aValue); + } +#endif + + operator uint32_t() const + { + return NativeEndian::swapFromBigEndian(value); + } + +private: + uint32_t value; +}; + +#pragma pack(pop) + +} // mozilla + +#endif // mozilla_BigEndianInts_h diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp new file mode 100644 index 000000000..f3f41c3af --- /dev/null +++ b/gfx/2d/Blur.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 "Blur.h" + +#include <algorithm> +#include <math.h> +#include <string.h> + +#include "mozilla/CheckedInt.h" + +#include "2D.h" +#include "DataSurfaceHelpers.h" +#include "Tools.h" + +#ifdef BUILD_ARM_NEON +#include "mozilla/arm.h" +#endif + +using namespace std; + +namespace mozilla { +namespace gfx { + +/** + * Box blur involves looking at one pixel, and setting its value to the average + * of its neighbouring pixels. + * @param aInput The input buffer. + * @param aOutput The output buffer. + * @param aLeftLobe The number of pixels to blend on the left. + * @param aRightLobe The number of pixels to blend on the right. + * @param aWidth The number of columns in the buffers. + * @param aRows The number of rows in the buffers. + * @param aSkipRect An area to skip blurring in. + * XXX shouldn't we pass stride in separately here? + */ +static void +BoxBlurHorizontal(unsigned char* aInput, + unsigned char* aOutput, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aWidth, + int32_t aRows, + const IntRect& aSkipRect) +{ + MOZ_ASSERT(aWidth > 0); + + int32_t boxSize = aLeftLobe + aRightLobe + 1; + bool skipRectCoversWholeRow = 0 >= aSkipRect.x && + aWidth <= aSkipRect.XMost(); + if (boxSize == 1) { + memcpy(aOutput, aInput, aWidth*aRows); + return; + } + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + for (int32_t y = 0; y < aRows; y++) { + // Check whether the skip rect intersects this row. If the skip + // rect covers the whole surface in this row, we can avoid + // this row entirely (and any others along the skip rect). + bool inSkipRectY = y >= aSkipRect.y && + y < aSkipRect.YMost(); + if (inSkipRectY && skipRectCoversWholeRow) { + y = aSkipRect.YMost() - 1; + continue; + } + + uint32_t alphaSum = 0; + for (int32_t i = 0; i < boxSize; i++) { + int32_t pos = i - aLeftLobe; + // See assertion above; if aWidth is zero, then we would have no + // valid position to clamp to. + pos = max(pos, 0); + pos = min(pos, aWidth - 1); + alphaSum += aInput[aWidth * y + pos]; + } + for (int32_t x = 0; x < aWidth; x++) { + // Check whether we are within the skip rect. If so, go + // to the next point outside the skip rect. + if (inSkipRectY && x >= aSkipRect.x && + x < aSkipRect.XMost()) { + x = aSkipRect.XMost(); + if (x >= aWidth) + break; + + // Recalculate the neighbouring alpha values for + // our new point on the surface. + alphaSum = 0; + for (int32_t i = 0; i < boxSize; i++) { + int32_t pos = x + i - aLeftLobe; + // See assertion above; if aWidth is zero, then we would have no + // valid position to clamp to. + pos = max(pos, 0); + pos = min(pos, aWidth - 1); + alphaSum += aInput[aWidth * y + pos]; + } + } + int32_t tmp = x - aLeftLobe; + int32_t last = max(tmp, 0); + int32_t next = min(tmp + boxSize, aWidth - 1); + + aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32; + + alphaSum += aInput[aWidth * y + next] - + aInput[aWidth * y + last]; + } + } +} + +/** + * Identical to BoxBlurHorizontal, except it blurs top and bottom instead of + * left and right. + * XXX shouldn't we pass stride in separately here? + */ +static void +BoxBlurVertical(unsigned char* aInput, + unsigned char* aOutput, + int32_t aTopLobe, + int32_t aBottomLobe, + int32_t aWidth, + int32_t aRows, + const IntRect& aSkipRect) +{ + MOZ_ASSERT(aRows > 0); + + int32_t boxSize = aTopLobe + aBottomLobe + 1; + bool skipRectCoversWholeColumn = 0 >= aSkipRect.y && + aRows <= aSkipRect.YMost(); + if (boxSize == 1) { + memcpy(aOutput, aInput, aWidth*aRows); + return; + } + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + for (int32_t x = 0; x < aWidth; x++) { + bool inSkipRectX = x >= aSkipRect.x && + x < aSkipRect.XMost(); + if (inSkipRectX && skipRectCoversWholeColumn) { + x = aSkipRect.XMost() - 1; + continue; + } + + uint32_t alphaSum = 0; + for (int32_t i = 0; i < boxSize; i++) { + int32_t pos = i - aTopLobe; + // See assertion above; if aRows is zero, then we would have no + // valid position to clamp to. + pos = max(pos, 0); + pos = min(pos, aRows - 1); + alphaSum += aInput[aWidth * pos + x]; + } + for (int32_t y = 0; y < aRows; y++) { + if (inSkipRectX && y >= aSkipRect.y && + y < aSkipRect.YMost()) { + y = aSkipRect.YMost(); + if (y >= aRows) + break; + + alphaSum = 0; + for (int32_t i = 0; i < boxSize; i++) { + int32_t pos = y + i - aTopLobe; + // See assertion above; if aRows is zero, then we would have no + // valid position to clamp to. + pos = max(pos, 0); + pos = min(pos, aRows - 1); + alphaSum += aInput[aWidth * pos + x]; + } + } + int32_t tmp = y - aTopLobe; + int32_t last = max(tmp, 0); + int32_t next = min(tmp + boxSize, aRows - 1); + + aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32; + + alphaSum += aInput[aWidth * next + x] - + aInput[aWidth * last + x]; + } + } +} + +static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2]) +{ + int32_t major, minor, final; + + /* See http://www.w3.org/TR/SVG/filters.html#feGaussianBlur for + * some notes about approximating the Gaussian blur with box-blurs. + * The comments below are in the terminology of that page. + */ + int32_t z = aRadius / 3; + switch (aRadius % 3) { + case 0: + // aRadius = z*3; choose d = 2*z + 1 + major = minor = final = z; + break; + case 1: + // aRadius = z*3 + 1 + // This is a tricky case since there is no value of d which will + // yield a radius of exactly aRadius. If d is odd, i.e. d=2*k + 1 + // for some integer k, then the radius will be 3*k. If d is even, + // i.e. d=2*k, then the radius will be 3*k - 1. + // So we have to choose values that don't match the standard + // algorithm. + major = z + 1; + minor = final = z; + break; + case 2: + // aRadius = z*3 + 2; choose d = 2*z + 2 + major = final = z + 1; + minor = z; + break; + default: + // Mathematical impossibility! + MOZ_ASSERT(false); + major = minor = final = 0; + } + MOZ_ASSERT(major + minor + final == aRadius); + + aLobes[0][0] = major; + aLobes[0][1] = minor; + aLobes[1][0] = minor; + aLobes[1][1] = major; + aLobes[2][0] = final; + aLobes[2][1] = final; +} + +static void +SpreadHorizontal(unsigned char* aInput, + unsigned char* aOutput, + int32_t aRadius, + int32_t aWidth, + int32_t aRows, + int32_t aStride, + const IntRect& aSkipRect) +{ + if (aRadius == 0) { + memcpy(aOutput, aInput, aStride * aRows); + return; + } + + bool skipRectCoversWholeRow = 0 >= aSkipRect.x && + aWidth <= aSkipRect.XMost(); + for (int32_t y = 0; y < aRows; y++) { + // Check whether the skip rect intersects this row. If the skip + // rect covers the whole surface in this row, we can avoid + // this row entirely (and any others along the skip rect). + bool inSkipRectY = y >= aSkipRect.y && + y < aSkipRect.YMost(); + if (inSkipRectY && skipRectCoversWholeRow) { + y = aSkipRect.YMost() - 1; + continue; + } + + for (int32_t x = 0; x < aWidth; x++) { + // Check whether we are within the skip rect. If so, go + // to the next point outside the skip rect. + if (inSkipRectY && x >= aSkipRect.x && + x < aSkipRect.XMost()) { + x = aSkipRect.XMost(); + if (x >= aWidth) + break; + } + + int32_t sMin = max(x - aRadius, 0); + int32_t sMax = min(x + aRadius, aWidth - 1); + int32_t v = 0; + for (int32_t s = sMin; s <= sMax; ++s) { + v = max<int32_t>(v, aInput[aStride * y + s]); + } + aOutput[aStride * y + x] = v; + } + } +} + +static void +SpreadVertical(unsigned char* aInput, + unsigned char* aOutput, + int32_t aRadius, + int32_t aWidth, + int32_t aRows, + int32_t aStride, + const IntRect& aSkipRect) +{ + if (aRadius == 0) { + memcpy(aOutput, aInput, aStride * aRows); + return; + } + + bool skipRectCoversWholeColumn = 0 >= aSkipRect.y && + aRows <= aSkipRect.YMost(); + for (int32_t x = 0; x < aWidth; x++) { + bool inSkipRectX = x >= aSkipRect.x && + x < aSkipRect.XMost(); + if (inSkipRectX && skipRectCoversWholeColumn) { + x = aSkipRect.XMost() - 1; + continue; + } + + for (int32_t y = 0; y < aRows; y++) { + // Check whether we are within the skip rect. If so, go + // to the next point outside the skip rect. + if (inSkipRectX && y >= aSkipRect.y && + y < aSkipRect.YMost()) { + y = aSkipRect.YMost(); + if (y >= aRows) + break; + } + + int32_t sMin = max(y - aRadius, 0); + int32_t sMax = min(y + aRadius, aRows - 1); + int32_t v = 0; + for (int32_t s = sMin; s <= sMax; ++s) { + v = max<int32_t>(v, aInput[aStride * s + x]); + } + aOutput[aStride * y + x] = v; + } + } +} + +CheckedInt<int32_t> +AlphaBoxBlur::RoundUpToMultipleOf4(int32_t aVal) +{ + CheckedInt<int32_t> val(aVal); + + val += 3; + val /= 4; + val *= 4; + + return val; +} + +AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect) + : mSpreadRadius(aSpreadRadius), + mBlurRadius(aBlurRadius), + mSurfaceAllocationSize(0) +{ + Rect rect(aRect); + rect.Inflate(Size(aBlurRadius + aSpreadRadius)); + rect.RoundOut(); + + if (aDirtyRect) { + // If we get passed a dirty rect from layout, we can minimize the + // shadow size and make painting faster. + mHasDirtyRect = true; + mDirtyRect = *aDirtyRect; + Rect requiredBlurArea = mDirtyRect.Intersect(rect); + requiredBlurArea.Inflate(Size(aBlurRadius + aSpreadRadius)); + rect = requiredBlurArea.Intersect(rect); + } else { + mHasDirtyRect = false; + } + + mRect = IntRect(int32_t(rect.x), int32_t(rect.y), + int32_t(rect.width), int32_t(rect.height)); + if (mRect.IsEmpty()) { + return; + } + + if (aSkipRect) { + // If we get passed a skip rect, we can lower the amount of + // blurring/spreading we need to do. We convert it to IntRect to avoid + // expensive int<->float conversions if we were to use Rect instead. + Rect skipRect = *aSkipRect; + skipRect.RoundIn(); + skipRect.Deflate(Size(aBlurRadius + aSpreadRadius)); + mSkipRect = IntRect(int32_t(skipRect.x), int32_t(skipRect.y), + int32_t(skipRect.width), int32_t(skipRect.height)); + + mSkipRect = mSkipRect.Intersect(mRect); + if (mSkipRect.IsEqualInterior(mRect)) + return; + + mSkipRect -= mRect.TopLeft(); + } else { + mSkipRect = IntRect(0, 0, 0, 0); + } + + CheckedInt<int32_t> stride = RoundUpToMultipleOf4(mRect.width); + if (stride.isValid()) { + mStride = stride.value(); + + // We need to leave room for an additional 3 bytes for a potential overrun + // in our blurring code. + size_t size = BufferSizeFromStrideAndHeight(mStride, mRect.height, 3); + if (size != 0) { + mSurfaceAllocationSize = size; + } + } +} + +AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, + int32_t aStride, + float aSigmaX, + float aSigmaY) + : mRect(int32_t(aRect.x), int32_t(aRect.y), + int32_t(aRect.width), int32_t(aRect.height)), + mSpreadRadius(), + mBlurRadius(CalculateBlurRadius(Point(aSigmaX, aSigmaY))), + mStride(aStride), + mSurfaceAllocationSize(0) +{ + IntRect intRect; + if (aRect.ToIntRect(&intRect)) { + size_t minDataSize = BufferSizeFromStrideAndHeight(intRect.width, intRect.height); + if (minDataSize != 0) { + mSurfaceAllocationSize = minDataSize; + } + } +} + + +AlphaBoxBlur::~AlphaBoxBlur() +{ +} + +IntSize +AlphaBoxBlur::GetSize() +{ + IntSize size(mRect.width, mRect.height); + return size; +} + +int32_t +AlphaBoxBlur::GetStride() +{ + return mStride; +} + +IntRect +AlphaBoxBlur::GetRect() +{ + return mRect; +} + +Rect* +AlphaBoxBlur::GetDirtyRect() +{ + if (mHasDirtyRect) { + return &mDirtyRect; + } + + return nullptr; +} + +size_t +AlphaBoxBlur::GetSurfaceAllocationSize() const +{ + return mSurfaceAllocationSize; +} + +void +AlphaBoxBlur::Blur(uint8_t* aData) +{ + if (!aData) { + return; + } + + // no need to do all this if not blurring or spreading + if (mBlurRadius != IntSize(0,0) || mSpreadRadius != IntSize(0,0)) { + int32_t stride = GetStride(); + + IntSize size = GetSize(); + + if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * size.height; + unsigned char* tmpData = new (std::nothrow) uint8_t[szB]; + + if (!tmpData) { + return; + } + + memset(tmpData, 0, szB); + + SpreadHorizontal(aData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect); + SpreadVertical(tmpData, aData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect); + + delete [] tmpData; + } + + int32_t horizontalLobes[3][2]; + ComputeLobes(mBlurRadius.width, horizontalLobes); + int32_t verticalLobes[3][2]; + ComputeLobes(mBlurRadius.height, verticalLobes); + + // We want to allow for some extra space on the left for alignment reasons. + int32_t maxLeftLobe = RoundUpToMultipleOf4(horizontalLobes[0][0] + 1).value(); + + IntSize integralImageSize(size.width + maxLeftLobe + horizontalLobes[1][1], + size.height + verticalLobes[0][0] + verticalLobes[1][1] + 1); + + if ((integralImageSize.width * integralImageSize.height) > (1 << 24)) { + // Fallback to old blurring code when the surface is so large it may + // overflow our integral image! + + // No need to use CheckedInt here - we have validated it in the constructor. + size_t szB = stride * size.height; + uint8_t* tmpData = new (std::nothrow) uint8_t[szB]; + if (!tmpData) { + return; + } + + memset(tmpData, 0, szB); + + uint8_t* a = aData; + uint8_t* b = tmpData; + if (mBlurRadius.width > 0) { + BoxBlurHorizontal(a, b, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(b, a, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurHorizontal(a, b, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect); + } else { + a = tmpData; + b = aData; + } + // The result is in 'b' here. + if (mBlurRadius.height > 0) { + BoxBlurVertical(b, a, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(a, b, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect); + BoxBlurVertical(b, a, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect); + } else { + a = b; + } + // The result is in 'a' here. + if (a == tmpData) { + memcpy(aData, tmpData, szB); + } + delete [] tmpData; + } else { + size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width, 4); + if (integralImageStride == 0) { + return; + } + + // We need to leave room for an additional 12 bytes for a maximum overrun + // of 3 pixels in the blurring code. + size_t bufLen = BufferSizeFromStrideAndHeight(integralImageStride, integralImageSize.height, 12); + if (bufLen == 0) { + return; + } + // bufLen is a byte count, but here we want a multiple of 32-bit ints, so + // we divide by 4. + AlignedArray<uint32_t> integralImage((bufLen / 4) + ((bufLen % 4) ? 1 : 0)); + + if (!integralImage) { + return; + } + +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + BoxBlur_SSE2(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_SSE2(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_SSE2(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } else +#endif +#ifdef BUILD_ARM_NEON + if (mozilla::supports_neon()) { + BoxBlur_NEON(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_NEON(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_NEON(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); + } else +#endif + { +#ifdef _MIPS_ARCH_LOONGSON3A + BoxBlur_LS3(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_LS3(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_LS3(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); +#else + BoxBlur_C(aData, horizontalLobes[0][0], horizontalLobes[0][1], verticalLobes[0][0], + verticalLobes[0][1], integralImage, integralImageStride); + BoxBlur_C(aData, horizontalLobes[1][0], horizontalLobes[1][1], verticalLobes[1][0], + verticalLobes[1][1], integralImage, integralImageStride); + BoxBlur_C(aData, horizontalLobes[2][0], horizontalLobes[2][1], verticalLobes[2][0], + verticalLobes[2][1], integralImage, integralImageStride); +#endif + } + } + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralRow(uint32_t *aDest, const uint8_t *aSource, uint32_t *aPreviousRow, + const uint32_t &aSourceWidth, const uint32_t &aLeftInflation, const uint32_t &aRightInflation) +{ + uint32_t currentRowSum = 0; + uint32_t pixel = aSource[0]; + for (uint32_t x = 0; x < aLeftInflation; x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } + for (uint32_t x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x += 4) { + uint32_t alphaValues = *(uint32_t*)(aSource + (x - aLeftInflation)); +#if defined WORDS_BIGENDIAN || defined IS_BIG_ENDIAN || defined __BIG_ENDIAN__ + currentRowSum += (alphaValues >> 24) & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + currentRowSum += (alphaValues >> 16) & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + currentRowSum += (alphaValues >> 8) & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; +#else + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; +#endif + } + pixel = aSource[aSourceWidth - 1]; + for (uint32_t x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_C(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + memset(aIntegralImage, 0, aIntegralImageStride); + + GenerateIntegralRow(aIntegralImage, aSource, aIntegralImage, + aSize.width, aLeftInflation, aRightInflation); + for (int y = 1; y < aTopInflation + 1; y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource, aIntegralImage + (y - 1) * stride32bit, + aSize.width, aLeftInflation, aRightInflation); + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + aSourceStride * (y - aTopInflation), + aIntegralImage + (y - 1) * stride32bit, aSize.width, aLeftInflation, aRightInflation); + } + + if (aBottomInflation) { + for (int y = (aSize.height + aTopInflation); y < integralImageSize.height; y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource + ((aSize.height - 1) * aSourceStride), + aIntegralImage + (y - 1) * stride32bit, + aSize.width, aLeftInflation, aRightInflation); + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_C(uint8_t* aData, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.width > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + int32_t stride32bit = aIntegralImageStride / 4; + + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_C(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + // Storing these locally makes this about 30% faster! Presumably the compiler + // can't be sure we're not altering the member variables in this loop. + IntRect skipRect = mSkipRect; + uint8_t *data = aData; + int32_t stride = mStride; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * stride32bit - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * stride32bit + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * stride32bit + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * stride32bit - aLeftLobe); + + for (int32_t x = 0; x < size.width; x++) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 1; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + int32_t topLeft = topLeftBase[x]; + int32_t topRight = topRightBase[x]; + int32_t bottomRight = bottomRightBase[x]; + int32_t bottomLeft = bottomLeftBase[x]; + + uint32_t value = bottomRight - topRight - bottomLeft; + value += topLeft; + + data[stride * y + x] = (uint64_t(reciprocal) * value + (uint64_t(1) << 31)) >> 32; + } + } +} + +/** + * Compute the box blur size (which we're calling the blur radius) from + * the standard deviation. + * + * Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for + * approximating a Gaussian using box blurs. This yields quite a good + * approximation for a Gaussian. Then we multiply this by 1.5 since our + * code wants the radius of the entire triple-box-blur kernel instead of + * the diameter of an individual box blur. For more details, see: + * http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement + * https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19 + */ +static const Float GAUSSIAN_SCALE_FACTOR = Float((3 * sqrt(2 * M_PI) / 4) * 1.5); + +IntSize +AlphaBoxBlur::CalculateBlurRadius(const Point& aStd) +{ + IntSize size(static_cast<int32_t>(floor(aStd.x * GAUSSIAN_SCALE_FACTOR + 0.5f)), + static_cast<int32_t>(floor(aStd.y * GAUSSIAN_SCALE_FACTOR + 0.5f))); + + return size; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h new file mode 100644 index 000000000..8464a57fe --- /dev/null +++ b/gfx/2d/Blur.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_GFX_BLUR_H_ +#define MOZILLA_GFX_BLUR_H_ + +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/CheckedInt.h" + +namespace mozilla { +namespace gfx { + +#ifdef _MSC_VER +#pragma warning( disable : 4251 ) +#endif + +/** + * Implementation of a triple box blur approximation of a Gaussian blur. + * + * A Gaussian blur is good for blurring because, when done independently + * in the horizontal and vertical directions, it matches the result that + * would be obtained using a different (rotated) set of axes. A triple + * box blur is a very close approximation of a Gaussian. + * + * This is a "service" class; the constructors set up all the information + * based on the values and compute the minimum size for an 8-bit alpha + * channel context. + * The callers are responsible for creating and managing the backing surface + * and passing the pointer to the data to the Blur() method. This class does + * not retain the pointer to the data outside of the Blur() call. + * + * A spread N makes each output pixel the maximum value of all source + * pixels within a square of side length 2N+1 centered on the output pixel. + */ +class GFX2D_API AlphaBoxBlur +{ +public: + + /** Constructs a box blur and computes the backing surface size. + * + * @param aRect The coordinates of the surface to create in device units. + * + * @param aBlurRadius The blur radius in pixels. This is the radius of the + * entire (triple) kernel function. Each individual box blur has radius + * approximately 1/3 this value, or diameter approximately 2/3 this value. + * This parameter should nearly always be computed using CalculateBlurRadius, + * below. + * + * @param aDirtyRect A pointer to a dirty rect, measured in device units, if + * available. This will be used for optimizing the blur operation. It is + * safe to pass nullptr here. + * + * @param aSkipRect A pointer to a rect, measured in device units, that + * represents an area where blurring is unnecessary and shouldn't be done for + * speed reasons. It is safe to pass nullptr here. + */ + AlphaBoxBlur(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect); + + AlphaBoxBlur(const Rect& aRect, + int32_t aStride, + float aSigmaX, + float aSigmaY); + + ~AlphaBoxBlur(); + + /** + * Return the size, in pixels, of the 8-bit alpha surface we'd use. + */ + IntSize GetSize(); + + /** + * Return the stride, in bytes, of the 8-bit alpha surface we'd use. + */ + int32_t GetStride(); + + /** + * Returns the device-space rectangle the 8-bit alpha surface covers. + */ + IntRect GetRect(); + + /** + * Return a pointer to a dirty rect, as passed in to the constructor, or nullptr + * if none was passed in. + */ + Rect* GetDirtyRect(); + + /** + * Return the minimum buffer size that should be given to Blur() method. If + * zero, the class is not properly setup for blurring. Note that this + * includes the extra three bytes on top of the stride*width, where something + * like gfxImageSurface::GetDataSize() would report without it, even if it + * happens to have the extra bytes. + */ + size_t GetSurfaceAllocationSize() const; + + /** + * Perform the blur in-place on the surface backed by specified 8-bit + * alpha surface data. The size must be at least that returned by + * GetSurfaceAllocationSize() or bad things will happen. + */ + void Blur(uint8_t* aData); + + /** + * Calculates a blur radius that, when used with box blur, approximates a + * Gaussian blur with the given standard deviation. The result of this + * function should be used as the aBlurRadius parameter to AlphaBoxBlur's + * constructor, above. + */ + static IntSize CalculateBlurRadius(const Point& aStandardDeviation); + +private: + + void BoxBlur_C(uint8_t* aData, + int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); + void BoxBlur_SSE2(uint8_t* aData, + int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); +#ifdef BUILD_ARM_NEON + void BoxBlur_NEON(uint8_t* aData, + int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); +#endif +#ifdef _MIPS_ARCH_LOONGSON3A + void BoxBlur_LS3(uint8_t* aData, + int32_t aLeftLobe, int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t *aIntegralImage, size_t aIntegralImageStride); +#endif + + static CheckedInt<int32_t> RoundUpToMultipleOf4(int32_t aVal); + + /** + * A rect indicating the area where blurring is unnecessary, and the blur + * algorithm should skip over it. + */ + IntRect mSkipRect; + + /** + * The device-space rectangle the the backing 8-bit alpha surface covers. + */ + IntRect mRect; + + /** + * A copy of the dirty rect passed to the constructor. This will only be valid if + * mHasDirtyRect is true. + */ + Rect mDirtyRect; + + /** + * The spread radius, in pixels. + */ + IntSize mSpreadRadius; + + /** + * The blur radius, in pixels. + */ + IntSize mBlurRadius; + + /** + * The stride of the data passed to Blur() + */ + int32_t mStride; + + /** + * The minimum size of the buffer needed for the Blur() operation. + */ + size_t mSurfaceAllocationSize; + + /** + * Whether mDirtyRect contains valid data. + */ + bool mHasDirtyRect; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BLUR_H_ */ diff --git a/gfx/2d/BlurLS3.cpp b/gfx/2d/BlurLS3.cpp new file mode 100644 index 000000000..20c28b37e --- /dev/null +++ b/gfx/2d/BlurLS3.cpp @@ -0,0 +1,588 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include <string.h> + +#ifdef _MIPS_ARCH_LOONGSON3A + +#include "MMIHelpers.h" + +namespace mozilla { +namespace gfx { + +typedef struct { double l; double h; } __m128i; + +MOZ_ALWAYS_INLINE +__m128i loadUnaligned128(__m128i *p) +{ + __m128i v; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gsldlc1 %[vh], 0xf(%[p]) \n\t" + "gsldrc1 %[vh], 0x8(%[p]) \n\t" + "gsldlc1 %[vl], 0x7(%[p]) \n\t" + "gsldrc1 %[vl], 0x0(%[p]) \n\t" + ".set pop \n\t" + :[vh]"=f"(v.h), [vl]"=f"(v.l) + :[p]"r"(p) + :"memory" + ); + + return v; +} + +MOZ_ALWAYS_INLINE +__m128i Divide(__m128i aValues, __m128i aDivisor) +{ + uint64_t tmp; + double srl32; + __m128i mask, ra, p4321, t1, t2; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 0x80000000 \n\t" + "mtc1 %[tmp], %[ral] \n\t" + "xor %[maskl], %[maskl], %[maskl] \n\t" + "mov.d %[rah], %[ral] \n\t" + "li %[tmp], 0xffffffff \n\t" + "mthc1 %[tmp], %[maskl] \n\t" + "mov.d %[maskh], %[maskl] \n\t" + ".set pop \n\t" + :[rah]"=f"(ra.h), [ral]"=f"(ra.l), + [maskh]"=f"(mask.h), [maskl]"=f"(mask.l), + [tmp]"=&r"(tmp) + ); + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "ori %[tmp], $0, 32 \n\t" + "mtc1 %[tmp], %[srl32] \n\t" + _mm_pmuluw(t1, av, ad) + _mm_psrld(t2, av, srl32) + _mm_pmuluw(t2, t2, ad) + // Add 1 << 31 before shifting or masking the lower 32 bits away, so that the + // result is rounded. + _mm_paddd(t1, t1, ra) + _mm_psrld(t1, t1, srl32) + _mm_paddd(t2, t2, ra) + _mm_and(t2, t2, mask) + _mm_or(p4321, t1, t2) + ".set pop \n\t" + :[p4321h]"=&f"(p4321.h), [p4321l]"=&f"(p4321.l), + [t1h]"=&f"(t1.h), [t1l]"=&f"(t1.l), + [t2h]"=&f"(t2.h), [t2l]"=&f"(t2.l), + [srl32]"=&f"(srl32), [tmp]"=&r"(tmp) + :[rah]"f"(ra.h), [ral]"f"(ra.l), + [maskh]"f"(mask.h), [maskl]"f"(mask.l), + [avh]"f"(aValues.h), [avl]"f"(aValues.l), + [adh]"f"(aDivisor.h), [adl]"f"(aDivisor.l) + ); + + return p4321; +} + +MOZ_ALWAYS_INLINE +__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight, + const __m128i& aBottomRight, const __m128i& aBottomLeft, + const __m128i& aDivisor) +{ + __m128i values; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_psubw(val, abr, atr) + _mm_psubw(val, val, abl) + _mm_paddw(val, val, atl) + ".set pop \n\t" + :[valh]"=&f"(values.h), [vall]"=&f"(values.l) + :[abrh]"f"(aBottomRight.h), [abrl]"f"(aBottomRight.l), + [atrh]"f"(aTopRight.h), [atrl]"f"(aTopRight.l), + [ablh]"f"(aBottomLeft.h), [abll]"f"(aBottomLeft.l), + [atlh]"f"(aTopLeft.h), [atll]"f"(aTopLeft.l) + ); + + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) +{ + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) +{ + uint64_t tr; + double tmp, s4, s64; + __m128i sumPixels, currentPixels, zero; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(z, z, z) + "li %[tr], 64 \n\t" + "mtc1 %[tr], %[s64] \n\t" + "li %[tr], 32 \n\t" + "mtc1 %[tr], %[s4] \n\t" + _mm_psllq(cp, ap, s4, s64, t) + _mm_paddw(sp, ap, cp) + _mm_punpckldq(cp, z, sp) + _mm_paddw(sp, sp, cp) + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l), + [cph]"=&f"(currentPixels.h), [cpl]"=&f"(currentPixels.l), + [zh]"=&f"(zero.h), [zl]"=&f"(zero.l), + [s4]"=&f"(s4), [s64]"=&f"(s64), [t]"=&f"(tmp), [tr]"=&r"(tr) + :[aph]"f"(aPixels.h), [apl]"f"(aPixels.l) + ); + + return sumPixels; +} + +MOZ_ALWAYS_INLINE +void GenerateIntegralImage_LS3(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow, previousRow; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gslqc1 %[frh], %[frl], (%[fr]) \n\t" + "gslqc1 %[prh], %[prl], (%[pr]) \n\t" + _mm_paddw(fr, fr, pr) + "gssqc1 %[frh], %[frl], (%[r]) \n\t" + ".set pop \n\t" + :[frh]"=&f"(firstRow.h), [frl]"=&f"(firstRow.l), + [prh]"=&f"(previousRow.h), [prl]"=&f"(previousRow.l) + :[fr]"r"(intFirstRow + x), [pr]"r"(intPrevRow + x), + [r]"r"(intRow + x) + :"memory" + ); + } + } + + uint64_t tmp; + double s44, see; + __m128i zero; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 0xee \n\t" + "mtc1 %[tmp], %[see] \n\t" + "li %[tmp], 0x44 \n\t" + "mtc1 %[tmp], %[s44] \n\t" + _mm_xor(zero, zero, zero) + ".set pop \n\t" + :[tmp]"=&r"(tmp), [s44]"=f"(s44), [see]"=f"(see), + [zeroh]"=f"(zero.h), [zerol]"=f"(zero.l) + ); + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum; + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); + uint32_t pixel = sourceRow[0]; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(cr, cr, cr) + ".set pop \n\t" + :[crh]"=f"(currentRowSum.h), [crl]"=f"(currentRowSum.l) + ); + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels, t; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + "pshufh %[sph], %[spl], %[s44] \n\t" + "pshufh %[spl], %[spl], %[s44] \n\t" + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l) + :[pix]"r"(pixel), [s44]"f"(s44) + ); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "pshufh %[crh], %[sph], %[see] \n\t" + "pshufh %[crl], %[sph], %[see] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see) + :"memory" + ); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + __m128i sumPixels, t; + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "pshufh %[crl], %[crh], %[see] \n\t" + "pshufh %[crh], %[crh], %[see] \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + _mm_punpcklbh(sp, sp, zero) + _mm_punpcklhw(sp, sp, zero) + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[pix]"r"(pixels), [see]"f"(see), + [zeroh]"f"(zero.h), [zerol]"f"(zero.l) + ); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "mov.d %[crh], %[sph] \n\t" + "mov.d %[crl], %[spl] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x) + :"memory" + ); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; + for (; x < integralImageSize.width; x++) { + // We could be unaligned here! + if (!(x & 3)) { + // aligned! + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[cr], %[crl] \n\t" + "punpcklwd %[crl], %[crl], %[crl] \n\t" + "mov.d %[crh], %[crl] \n\t" + ".set pop \n\t" + :[crh]"=f"(currentRowSum.h), [crl]"=f"(currentRowSum.l) + :[cr]"r"(intCurrentRowSum) + ); + break; + } + intCurrentRowSum += pixel; + intRow[x] = intPrevRow[x] + intCurrentRowSum; + } + } else { + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "pshufh %[crl], %[crh], %[see] \n\t" + "pshufh %[crh], %[crh], %[see] \n\t" + ".set pop \n\t" + :[crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[see]"f"(see) + ); + } + for (; x < integralImageSize.width; x += 4) { + __m128i sumPixels, t; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + ".set pop \n\t" + :[sph]"=f"(sumPixels.h), [spl]"=f"(sumPixels.l) + :[pix]"r"(pixel) + ); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "pshufh %[crh], %[sph], %[see] \n\t" + "pshufh %[crl], %[sph], %[see] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see) + :"memory" + ); + } + } + + if (aBottomInflation) { + // Store the last valid row of our source image in the last row of + // our integral image. This will be overwritten with the correct values + // in the upcoming loop. + LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, + aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); + + + for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { + __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); + __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); + __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i t1, t2; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gslqc1 %[t1h], %[t1l], (%[lr]) \n\t" + "gslqc1 %[t2h], %[t2l], (%[pr]) \n\t" + _mm_paddw(t1, t1, t2) + "gssqc1 %[t1h], %[t1l], (%[r]) \n\t" + ".set pop \n\t" + :[t1h]"=&f"(t1.h), [t1l]"=&f"(t1.l), + [t2h]"=&f"(t2.h), [t2l]"=&f"(t2.l) + :[r]"r"(intRow + (x / 4)), + [lr]"r"(intLastRow + (x / 4)), + [pr]"r"(intPrevRow + (x / 4)) + :"memory" + ); + } + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_LS3(uint8_t* aData, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_LS3(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + __m128i divisor, zero; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[rec], %[divl] \n\t" + "punpcklwd %[divl], %[divl], %[divl] \n\t" + "mov.d %[divh], %[divl] \n\t" + _mm_xor(zero, zero, zero) + ".set pop \n\t" + :[divh]"=f"(divisor.h), [divl]"=f"(divisor.l), + [zeroh]"=f"(zero.h), [zerol]"=f"(zero.l) + :[rec]"r"(reciprocal) + ); + + // This points to the start of the rectangle within the IntegralImage that overlaps + // the surface being blurred. + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t *data = aData; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + __m128i topLeft; + __m128i topRight; + __m128i bottomRight; + __m128i bottomLeft; + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + __m128i result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4)); + __m128i result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8)); + __m128i result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12)); + __m128i result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + double t; + __m128i final; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_packsswh(r3, r3, r4, t) + _mm_packsswh(f, r1, r2, t) + _mm_packushb(f, f, r3, t) + "gssdlc1 %[fh], 0xf(%[d]) \n\t" + "gssdrc1 %[fh], 0x8(%[d]) \n\t" + "gssdlc1 %[fl], 0x7(%[d]) \n\t" + "gssdrc1 %[fl], 0x0(%[d]) \n\t" + ".set pop \n\t" + :[fh]"=&f"(final.h), [fl]"=&f"(final.l), + [r3h]"+f"(result3.h), [r3l]"+f"(result3.l), + [t]"=&f"(t) + :[r1h]"f"(result1.h), [r1l]"f"(result1.l), + [r2h]"f"(result2.h), [r2l]"f"(result2.l), + [r4h]"f"(result4.h), [r4l]"f"(result4.l), + [d]"r"(data + stride * y + x) + :"memory" + ); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + double t; + __m128i final; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_packsswh(f, r, zero, t) + _mm_packushb(f, f, zero, t) + "swc1 %[fl], (%[d]) \n\t" + ".set pop \n\t" + :[fh]"=&f"(final.h), [fl]"=&f"(final.l), + [t]"=&f"(t) + :[d]"r"(data + stride * y + x), + [rh]"f"(result.h), [rl]"f"(result.l), + [zeroh]"f"(zero.h), [zerol]"f"(zero.l) + :"memory" + ); + } + } + +} + +} +} + +#endif /* _MIPS_ARCH_LOONGSON3A */ diff --git a/gfx/2d/BlurNEON.cpp b/gfx/2d/BlurNEON.cpp new file mode 100644 index 000000000..978b3cdc0 --- /dev/null +++ b/gfx/2d/BlurNEON.cpp @@ -0,0 +1,288 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" +#include <arm_neon.h> + +namespace mozilla { +namespace gfx { + +MOZ_ALWAYS_INLINE +uint16x4_t Divide(uint32x4_t aValues, uint32x2_t aDivisor) +{ + uint64x2_t roundingAddition = vdupq_n_u64(int64_t(1) << 31); + uint64x2_t multiplied21 = vmull_u32(vget_low_u32(aValues), aDivisor); + uint64x2_t multiplied43 = vmull_u32(vget_high_u32(aValues), aDivisor); + return vqmovn_u32(vcombine_u32(vshrn_n_u64(vaddq_u64(multiplied21, roundingAddition), 32), + vshrn_n_u64(vaddq_u64(multiplied43, roundingAddition), 32))); +} + +MOZ_ALWAYS_INLINE +uint16x4_t BlurFourPixels(const uint32x4_t& aTopLeft, const uint32x4_t& aTopRight, + const uint32x4_t& aBottomRight, const uint32x4_t& aBottomLeft, + const uint32x2_t& aDivisor) +{ + uint32x4_t values = vaddq_u32(vsubq_u32(vsubq_u32(aBottomRight, aTopRight), aBottomLeft), aTopLeft); + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) +{ + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_NEON(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + uint32x4_t firstRow = vld1q_u32(intFirstRow + x); + uint32x4_t previousRow = vld1q_u32(intPrevRow + x); + vst1q_u32(intRow + x, vaddq_u32(firstRow, previousRow)); + } + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + uint32x4_t currentRowSum = vdupq_n_u32(0); + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); + + uint32_t pixel = sourceRow[0]; + for (int x = 0; x < aLeftInflation; x += 4) { + uint32_t temp[4]; + temp[0] = pixel; + temp[1] = temp[0] + pixel; + temp[2] = temp[1] + pixel; + temp[3] = temp[2] + pixel; + uint32x4_t sumPixels = vld1q_u32(temp); + sumPixels = vaddq_u32(sumPixels, currentRowSum); + currentRowSum = vdupq_n_u32(vgetq_lane_u32(sumPixels, 3)); + vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x))); + } + + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + currentRowSum = vdupq_n_u32(vgetq_lane_u32(currentRowSum, 3)); + + uint32_t temp[4]; + temp[0] = *(sourceRow + (x - aLeftInflation)); + temp[1] = temp[0] + *(sourceRow + (x - aLeftInflation) + 1); + temp[2] = temp[1] + *(sourceRow + (x - aLeftInflation) + 2); + temp[3] = temp[2] + *(sourceRow + (x - aLeftInflation) + 3); + uint32x4_t sumPixels = vld1q_u32(temp); + sumPixels = vaddq_u32(sumPixels, currentRowSum); + currentRowSum = sumPixels; + vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x))); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; + for (; x < integralImageSize.width; x++) { + // We could be unaligned here! + if (!(x & 3)) { + // aligned! + currentRowSum = vdupq_n_u32(intCurrentRowSum); + break; + } + intCurrentRowSum += pixel; + intRow[x] = intPrevRow[x] + intCurrentRowSum; + } + } else { + currentRowSum = vdupq_n_u32(vgetq_lane_u32(currentRowSum, 3)); + } + + for (; x < integralImageSize.width; x += 4) { + uint32_t temp[4]; + temp[0] = pixel; + temp[1] = temp[0] + pixel; + temp[2] = temp[1] + pixel; + temp[3] = temp[2] + pixel; + uint32x4_t sumPixels = vld1q_u32(temp); + sumPixels = vaddq_u32(sumPixels, currentRowSum); + currentRowSum = vdupq_n_u32(vgetq_lane_u32(sumPixels, 3)); + vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x))); + } + } + + if (aBottomInflation) { + // Store the last valid row of our source image in the last row of + // our integral image. This will be overwritten with the correct values + // in the upcoming loop. + LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, + aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); + + for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intLastRow = aIntegralImage + (integralImageSize.height - 1) * stride32bit; + for (int x = 0; x < integralImageSize.width; x += 4) { + vst1q_u32(intRow + x, + vaddq_u32(vld1q_u32(intLastRow + x), + vld1q_u32(intPrevRow + x))); + } + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_NEON(uint8_t* aData, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_NEON(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + uint32x2_t divisor = vdup_n_u32(reciprocal); + + // This points to the start of the rectangle within the IntegralImage that overlaps + // the surface being blurred. + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t *data = aData; + + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + uint32x4_t topLeft; + uint32x4_t topRight; + uint32x4_t bottomRight; + uint32x4_t bottomLeft; + topLeft = vld1q_u32(topLeftBase + x); + topRight = vld1q_u32(topRightBase + x); + bottomRight = vld1q_u32(bottomRightBase + x); + bottomLeft = vld1q_u32(bottomLeftBase + x); + uint16x4_t result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = vld1q_u32(topLeftBase + x + 4); + topRight = vld1q_u32(topRightBase + x + 4); + bottomRight = vld1q_u32(bottomRightBase + x + 4); + bottomLeft = vld1q_u32(bottomLeftBase + x + 4); + uint16x4_t result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = vld1q_u32(topLeftBase + x + 8); + topRight = vld1q_u32(topRightBase + x + 8); + bottomRight = vld1q_u32(bottomRightBase + x + 8); + bottomLeft = vld1q_u32(bottomLeftBase + x + 8); + uint16x4_t result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = vld1q_u32(topLeftBase + x + 12); + topRight = vld1q_u32(topRightBase + x + 12); + bottomRight = vld1q_u32(bottomRightBase + x + 12); + bottomLeft = vld1q_u32(bottomLeftBase + x + 12); + uint16x4_t result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + uint8x8_t combine1 = vqmovn_u16(vcombine_u16(result1, result2)); + uint8x8_t combine2 = vqmovn_u16(vcombine_u16(result3, result4)); + uint8x16_t final = vcombine_u8(combine1, combine2); + vst1q_u8(data + stride * y + x, final); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + uint32x4_t topLeft = vld1q_u32(topLeftBase + x); + uint32x4_t topRight = vld1q_u32(topRightBase + x); + uint32x4_t bottomRight = vld1q_u32(bottomRightBase + x); + uint32x4_t bottomLeft = vld1q_u32(bottomLeftBase + x); + uint16x4_t result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + uint32x2_t final = vreinterpret_u32_u8(vmovn_u16(vcombine_u16(result, vdup_n_u16(0)))); + *(uint32_t*)(data + stride * y + x) = vget_lane_u32(final, 0); + } + } +} + +} +} + diff --git a/gfx/2d/BlurSSE2.cpp b/gfx/2d/BlurSSE2.cpp new file mode 100644 index 000000000..d652325b9 --- /dev/null +++ b/gfx/2d/BlurSSE2.cpp @@ -0,0 +1,315 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include "SSEHelpers.h" + +#include <string.h> + +namespace mozilla { +namespace gfx { + +MOZ_ALWAYS_INLINE +__m128i Divide(__m128i aValues, __m128i aDivisor) +{ + const __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff); + static const union { + int64_t i64[2]; + __m128i m; + } roundingAddition = { { int64_t(1) << 31, int64_t(1) << 31 } }; + + __m128i multiplied31 = _mm_mul_epu32(aValues, aDivisor); + __m128i multiplied42 = _mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor); + + // Add 1 << 31 before shifting or masking the lower 32 bits away, so that the + // result is rounded. + __m128i p_3_1 = _mm_srli_epi64(_mm_add_epi64(multiplied31, roundingAddition.m), 32); + __m128i p4_2_ = _mm_and_si128(_mm_add_epi64(multiplied42, roundingAddition.m), mask); + __m128i p4321 = _mm_or_si128(p_3_1, p4_2_); + return p4321; +} + +MOZ_ALWAYS_INLINE +__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight, + const __m128i& aBottomRight, const __m128i& aBottomLeft, + const __m128i& aDivisor) +{ + __m128i values = _mm_add_epi32(_mm_sub_epi32(_mm_sub_epi32(aBottomRight, aTopRight), aBottomLeft), aTopLeft); + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) +{ + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) +{ + __m128i sumPixels = aPixels; + __m128i currentPixels = _mm_slli_si128(aPixels, 4); + sumPixels = _mm_add_epi32(sumPixels, currentPixels); + currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels); + + return _mm_add_epi32(sumPixels, currentPixels); +} + +MOZ_ALWAYS_INLINE void +GenerateIntegralImage_SSE2(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t *aIntegralImage, size_t aIntegralImageStride, + uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) +{ + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t *intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x)); + __m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x)); + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(firstRow, previousRow)); + } + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum = _mm_setzero_si128(); + uint32_t *intRow = aIntegralImage + (y * stride32bit); + uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); + + uint32_t pixel = sourceRow[0]; + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels = AccumulatePixelSums(_mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0))); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + + __m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16(_mm_unpacklo_epi8( _mm_set1_epi32(pixels), _mm_setzero_si128()), _mm_setzero_si128())); + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = sumPixels; + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; + for (; x < integralImageSize.width; x++) { + // We could be unaligned here! + if (!(x & 3)) { + // aligned! + currentRowSum = _mm_set1_epi32(intCurrentRowSum); + break; + } + intCurrentRowSum += pixel; + intRow[x] = intPrevRow[x] + intCurrentRowSum; + } + } else { + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + } + for (; x < integralImageSize.width; x += 4) { + __m128i sumPixels = AccumulatePixelSums(_mm_set1_epi32(pixel)); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + } + + if (aBottomInflation) { + // Store the last valid row of our source image in the last row of + // our integral image. This will be overwritten with the correct values + // in the upcoming loop. + LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, + aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); + + + for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { + __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); + __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); + __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); + + for (int x = 0; x < integralImageSize.width; x += 4) { + _mm_store_si128(intRow + (x / 4), + _mm_add_epi32(_mm_load_si128(intLastRow + (x / 4)), + _mm_load_si128(intPrevRow + (x / 4)))); + } + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void +AlphaBoxBlur::BoxBlur_SSE2(uint8_t* aData, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aTopLobe, + int32_t aBottomLobe, + uint32_t *aIntegralImage, + size_t aIntegralImageStride) +{ + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + __m128i divisor = _mm_set1_epi32(reciprocal); + + // This points to the start of the rectangle within the IntegralImage that overlaps + // the surface being blurred. + uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t *data = aData; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + __m128i topLeft; + __m128i topRight; + __m128i bottomRight; + __m128i bottomLeft; + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + __m128i result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4)); + __m128i result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8)); + __m128i result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12)); + __m128i result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + __m128i final = _mm_packus_epi16(_mm_packs_epi32(result1, result2), _mm_packs_epi32(result3, result4)); + + _mm_storeu_si128((__m128i*)(data + stride * y + x), final); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + __m128i final = _mm_packus_epi16(_mm_packs_epi32(result, _mm_setzero_si128()), _mm_setzero_si128()); + + *(uint32_t*)(data + stride * y + x) = _mm_cvtsi128_si32(final); + } + } + +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/BorrowedContext.h b/gfx/2d/BorrowedContext.h new file mode 100644 index 000000000..edb923b1e --- /dev/null +++ b/gfx/2d/BorrowedContext.h @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_BORROWED_CONTEXT_H +#define _MOZILLA_GFX_BORROWED_CONTEXT_H + +#include "2D.h" + +#ifdef MOZ_X11 +#include <X11/extensions/Xrender.h> +#include <X11/Xlib.h> +#include "X11UndefineNone.h" +#endif + +struct _cairo; +typedef struct _cairo cairo_t; + +namespace mozilla { + +namespace gfx { + +/* This is a helper class that let's you borrow a cairo_t from a + * DrawTargetCairo. This is used for drawing themed widgets. + * + * Callers should check the cr member after constructing the object + * to see if it succeeded. The DrawTarget should not be used while + * the context is borrowed. */ +class BorrowedCairoContext +{ +public: + BorrowedCairoContext() + : mCairo(nullptr) + , mDT(nullptr) + { } + + explicit BorrowedCairoContext(DrawTarget *aDT) + : mDT(aDT) + { + mCairo = BorrowCairoContextFromDrawTarget(aDT); + } + + // We can optionally Init after construction in + // case we don't know what the DT will be at construction + // time. + cairo_t *Init(DrawTarget *aDT) + { + MOZ_ASSERT(!mDT, "Can't initialize twice!"); + mDT = aDT; + return mCairo = BorrowCairoContextFromDrawTarget(aDT); + } + + // The caller needs to call Finish if cr is non-null when + // they are done with the context. This is currently explicit + // instead of happening implicitly in the destructor to make + // what's happening in the caller more clear. It also + // let's you resume using the DrawTarget in the same scope. + void Finish() + { + if (mCairo) { + ReturnCairoContextToDrawTarget(mDT, mCairo); + mCairo = nullptr; + } + } + + ~BorrowedCairoContext() { + MOZ_ASSERT(!mCairo); + } + + cairo_t *mCairo; +private: + static cairo_t* BorrowCairoContextFromDrawTarget(DrawTarget *aDT); + static void ReturnCairoContextToDrawTarget(DrawTarget *aDT, cairo_t *aCairo); + DrawTarget *mDT; +}; + +#ifdef MOZ_X11 +/* This is a helper class that let's you borrow an Xlib drawable from + * a DrawTarget. This is used for drawing themed widgets. + * + * Callers should check the Xlib drawable after constructing the object + * to see if it succeeded. The DrawTarget should not be used while + * the drawable is borrowed. */ +class BorrowedXlibDrawable +{ +public: + BorrowedXlibDrawable() + : mDT(nullptr), + mDisplay(nullptr), + mDrawable(X11None), + mScreen(nullptr), + mVisual(nullptr), + mXRenderFormat(nullptr) + {} + + explicit BorrowedXlibDrawable(DrawTarget *aDT) + : mDT(nullptr), + mDisplay(nullptr), + mDrawable(X11None), + mScreen(nullptr), + mVisual(nullptr), + mXRenderFormat(nullptr) + { + Init(aDT); + } + + // We can optionally Init after construction in + // case we don't know what the DT will be at construction + // time. + bool Init(DrawTarget *aDT); + + // The caller needs to call Finish if drawable is non-zero when + // they are done with the context. This is currently explicit + // instead of happening implicitly in the destructor to make + // what's happening in the caller more clear. It also + // let's you resume using the DrawTarget in the same scope. + void Finish(); + + ~BorrowedXlibDrawable() { + MOZ_ASSERT(!mDrawable); + } + + Display *GetDisplay() const { return mDisplay; } + Drawable GetDrawable() const { return mDrawable; } + Screen *GetScreen() const { return mScreen; } + Visual *GetVisual() const { return mVisual; } + IntSize GetSize() const { return mSize; } + Point GetOffset() const { return mOffset; } + + XRenderPictFormat* GetXRenderFormat() const { return mXRenderFormat; } + +private: + DrawTarget *mDT; + Display *mDisplay; + Drawable mDrawable; + Screen *mScreen; + Visual *mVisual; + XRenderPictFormat *mXRenderFormat; + IntSize mSize; + Point mOffset; +}; +#endif + +#ifdef XP_DARWIN +/* This is a helper class that let's you borrow a CGContextRef from a + * DrawTargetCG. This is used for drawing themed widgets. + * + * Callers should check the cg member after constructing the object + * to see if it succeeded. The DrawTarget should not be used while + * the context is borrowed. */ +class BorrowedCGContext +{ +public: + BorrowedCGContext() + : cg(nullptr) + , mDT(nullptr) + { } + + explicit BorrowedCGContext(DrawTarget *aDT) + : mDT(aDT) + { + MOZ_ASSERT(aDT, "Caller should check for nullptr"); + cg = BorrowCGContextFromDrawTarget(aDT); + } + + // We can optionally Init after construction in + // case we don't know what the DT will be at construction + // time. + CGContextRef Init(DrawTarget *aDT) + { + MOZ_ASSERT(aDT, "Caller should check for nullptr"); + MOZ_ASSERT(!mDT, "Can't initialize twice!"); + mDT = aDT; + cg = BorrowCGContextFromDrawTarget(aDT); + return cg; + } + + // The caller needs to call Finish if cg is non-null when + // they are done with the context. This is currently explicit + // instead of happening implicitly in the destructor to make + // what's happening in the caller more clear. It also + // let's you resume using the DrawTarget in the same scope. + void Finish() + { + if (cg) { + ReturnCGContextToDrawTarget(mDT, cg); + cg = nullptr; + } + } + + ~BorrowedCGContext() { + MOZ_ASSERT(!cg); + } + + CGContextRef cg; +private: +#ifdef USE_SKIA + static CGContextRef BorrowCGContextFromDrawTarget(DrawTarget *aDT); + static void ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg); +#else + static CGContextRef BorrowCGContextFromDrawTarget(DrawTarget *aDT) { + MOZ_CRASH("Not supported without Skia"); + } + + static void ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg) { + MOZ_CRASH("not supported without Skia"); + } +#endif + DrawTarget *mDT; +}; +#endif + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_BORROWED_CONTEXT_H diff --git a/gfx/2d/CGTextDrawing.h b/gfx/2d/CGTextDrawing.h new file mode 100644 index 000000000..b9e3b374a --- /dev/null +++ b/gfx/2d/CGTextDrawing.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _MOZILLA_GFX_SKIACGPOPUPDRAWER_H +#define _MOZILLA_GFX_SKIACGPOPUPDRAWER_H + +#include <ApplicationServices/ApplicationServices.h> +#include "nsDebug.h" +#include "mozilla/Vector.h" +#include "ScaledFontMac.h" +#include "PathCG.h" +#include <dlfcn.h> + +// This is used when we explicitly need CG to draw text to support things such +// as vibrancy and subpixel AA on transparent backgrounds. The current use cases +// are really only to enable Skia to support drawing text in those situations. + +namespace mozilla { +namespace gfx { + +typedef void (*CGContextSetFontSmoothingBackgroundColorFunc) (CGContextRef cgContext, CGColorRef color); + +static CGContextSetFontSmoothingBackgroundColorFunc +GetCGContextSetFontSmoothingBackgroundColorFunc() +{ + static CGContextSetFontSmoothingBackgroundColorFunc func = nullptr; + static bool lookedUpFunc = false; + if (!lookedUpFunc) { + func = (CGContextSetFontSmoothingBackgroundColorFunc)dlsym( + RTLD_DEFAULT, "CGContextSetFontSmoothingBackgroundColor"); + lookedUpFunc = true; + } + return func; +} + +static CGColorRef +ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor) +{ + CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a}; + return CGColorCreate(aColorSpace, components); +} + +static bool +SetFontSmoothingBackgroundColor(CGContextRef aCGContext, CGColorSpaceRef aColorSpace, + const GlyphRenderingOptions* aRenderingOptions) +{ + if (aRenderingOptions) { + Color fontSmoothingBackgroundColor = + static_cast<const GlyphRenderingOptionsCG*>(aRenderingOptions)->FontSmoothingBackgroundColor(); + if (fontSmoothingBackgroundColor.a > 0) { + CGContextSetFontSmoothingBackgroundColorFunc setFontSmoothingBGColorFunc = + GetCGContextSetFontSmoothingBackgroundColorFunc(); + if (setFontSmoothingBGColorFunc) { + CGColorRef color = ColorToCGColor(aColorSpace, fontSmoothingBackgroundColor); + setFontSmoothingBGColorFunc(aCGContext, color); + CGColorRelease(color); + return true; + } + } + } + + return false; +} + +// Font rendering with a non-transparent font smoothing background color +// can leave pixels in our buffer where the rgb components exceed the alpha +// component. When this happens we need to clean up the data afterwards. +// The purpose of this is probably the following: Correct compositing of +// subpixel anti-aliased fonts on transparent backgrounds requires +// different alpha values per RGB component. Usually, premultiplied color +// values are derived by multiplying all components with the same per-pixel +// alpha value. However, if you multiply each component with a *different* +// alpha, and set the alpha component of the pixel to, say, the average +// of the alpha values that you used during the premultiplication of the +// RGB components, you can trick OVER compositing into doing a simplified +// form of component alpha compositing. (You just need to make sure to +// clamp the components of the result pixel to [0,255] afterwards.) +static void +EnsureValidPremultipliedData(CGContextRef aContext, + CGRect aTextBounds = CGRectInfinite) +{ + if (CGBitmapContextGetBitsPerPixel(aContext) != 32 || + CGBitmapContextGetAlphaInfo(aContext) != kCGImageAlphaPremultipliedFirst) { + return; + } + + uint8_t* bitmapData = (uint8_t*)CGBitmapContextGetData(aContext); + CGRect bitmapBounds = CGRectMake(0, 0, CGBitmapContextGetWidth(aContext), CGBitmapContextGetHeight(aContext)); + int stride = CGBitmapContextGetBytesPerRow(aContext); + + CGRect bounds = CGRectIntersection(bitmapBounds, aTextBounds); + int startX = bounds.origin.x; + int endX = startX + bounds.size.width; + MOZ_ASSERT(endX <= bitmapBounds.size.width); + + + // CGRect assume that our origin is the bottom left. + // The data assumes that the origin is the top left. + // Have to switch the Y axis so that our coordinates are correct + int startY = bitmapBounds.size.height - (bounds.origin.y + bounds.size.height); + int endY = startY + bounds.size.height; + MOZ_ASSERT(endY <= (int)CGBitmapContextGetHeight(aContext)); + + for (int y = startY; y < endY; y++) { + for (int x = startX; x < endX; x++) { + int i = y * stride + x * 4; + uint8_t a = bitmapData[i + 3]; + + bitmapData[i + 0] = std::min(a, bitmapData[i+0]); + bitmapData[i + 1] = std::min(a, bitmapData[i+1]); + bitmapData[i + 2] = std::min(a, bitmapData[i+2]); + } + } +} + +static CGRect +ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale) +{ + CGFloat x1, x2, y1, y2; + if (count < 1) + return CGRectZero; + + x1 = bboxes[0].origin.x + positions[0].x; + x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width; + y1 = bboxes[0].origin.y + positions[0].y; + y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height; + + // accumulate max and minimum coordinates + for (int i = 1; i < count; i++) { + x1 = std::min(x1, bboxes[i].origin.x + positions[i].x); + y1 = std::min(y1, bboxes[i].origin.y + positions[i].y); + x2 = std::max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width); + y2 = std::max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height); + } + + CGRect extents = {{x1, y1}, {x2-x1, y2-y1}}; + return extents; +} + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/Coord.h b/gfx/2d/Coord.h new file mode 100644 index 000000000..0d2cdd6a6 --- /dev/null +++ b/gfx/2d/Coord.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COORD_H_ +#define MOZILLA_GFX_COORD_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/TypeTraits.h" // For IsSame +#include "Types.h" +#include "BaseCoord.h" + +#include <cmath> + +namespace mozilla { + +template <typename> struct IsPixel; + +namespace gfx { + +template <class units> struct IntCoordTyped; +template <class units, class F = Float> struct CoordTyped; + +// CommonType<coord, primitive> is a metafunction that returns the type of the +// result of an arithmetic operation on the underlying type of a strongly-typed +// coordinate type 'coord', and a primitive type 'primitive'. C++ rules for +// arithmetic conversions are designed to avoid losing information - for +// example, the result of adding an int and a float is a float - and we want +// the same behaviour when mixing our coordinate types with primitive types. +// We get C++ to compute the desired result type using 'decltype'. + +template <class coord, class primitive> +struct CommonType; + +template <class units, class primitive> +struct CommonType<IntCoordTyped<units>, primitive> { + typedef decltype(int32_t() + primitive()) type; +}; + +template <class units, class F, class primitive> +struct CommonType<CoordTyped<units, F>, primitive> { + typedef decltype(F() + primitive()) type; +}; + +// This is a base class that provides mixed-type operator overloads between +// a strongly-typed Coord and a primitive value. It is needed to avoid +// ambiguities at mixed-type call sites, because Coord classes are implicitly +// convertible to their underlying value type. As we transition more of our code +// to strongly-typed classes, we may be able to remove some or all of these +// overloads. + +template <bool B, class coord, class primitive> +struct CoordOperatorsHelper { + // Using SFINAE (Substitution Failure Is Not An Error) to suppress redundant + // operators +}; + +template <class coord, class primitive> +struct CoordOperatorsHelper<true, coord, primitive> { + friend bool operator==(coord aA, primitive aB) { + return aA.value == aB; + } + friend bool operator==(primitive aA, coord aB) { + return aA == aB.value; + } + friend bool operator!=(coord aA, primitive aB) { + return aA.value != aB; + } + friend bool operator!=(primitive aA, coord aB) { + return aA != aB.value; + } + + typedef typename CommonType<coord, primitive>::type result_type; + + friend result_type operator+(coord aA, primitive aB) { + return aA.value + aB; + } + friend result_type operator+(primitive aA, coord aB) { + return aA + aB.value; + } + friend result_type operator-(coord aA, primitive aB) { + return aA.value - aB; + } + friend result_type operator-(primitive aA, coord aB) { + return aA - aB.value; + } + friend result_type operator*(coord aCoord, primitive aScale) { + return aCoord.value * aScale; + } + friend result_type operator*(primitive aScale, coord aCoord) { + return aScale * aCoord.value; + } + friend result_type operator/(coord aCoord, primitive aScale) { + return aCoord.value / aScale; + } + // 'scale / coord' is intentionally omitted because it doesn't make sense. +}; + +// Note: 'IntCoordTyped<units>' and 'CoordTyped<units>' do not derive from +// 'units' to work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61959. + +template<class units> +struct IntCoordTyped : + public BaseCoord< int32_t, IntCoordTyped<units> >, + public CoordOperatorsHelper< true, IntCoordTyped<units>, float >, + public CoordOperatorsHelper< true, IntCoordTyped<units>, double > { + static_assert(IsPixel<units>::value, + "'units' must be a coordinate system tag"); + + typedef BaseCoord< int32_t, IntCoordTyped<units> > Super; + + constexpr IntCoordTyped() : Super() {} + constexpr MOZ_IMPLICIT IntCoordTyped(int32_t aValue) : Super(aValue) {} +}; + +template<class units, class F> +struct CoordTyped : + public BaseCoord< F, CoordTyped<units, F> >, + public CoordOperatorsHelper< !IsSame<F, int32_t>::value, CoordTyped<units, F>, int32_t >, + public CoordOperatorsHelper< !IsSame<F, uint32_t>::value, CoordTyped<units, F>, uint32_t >, + public CoordOperatorsHelper< !IsSame<F, double>::value, CoordTyped<units, F>, double >, + public CoordOperatorsHelper< !IsSame<F, float>::value, CoordTyped<units, F>, float > { + static_assert(IsPixel<units>::value, + "'units' must be a coordinate system tag"); + + typedef BaseCoord< F, CoordTyped<units, F> > Super; + + constexpr CoordTyped() : Super() {} + constexpr MOZ_IMPLICIT CoordTyped(F aValue) : Super(aValue) {} + explicit constexpr CoordTyped(const IntCoordTyped<units>& aCoord) : Super(F(aCoord.value)) {} + + void Round() { + this->value = floor(this->value + 0.5); + } + void Truncate() { + this->value = int32_t(this->value); + } + + IntCoordTyped<units> Rounded() const { + return IntCoordTyped<units>(int32_t(floor(this->value + 0.5))); + } + IntCoordTyped<units> Truncated() const { + return IntCoordTyped<units>(int32_t(this->value)); + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_COORD_H_ */ diff --git a/gfx/2d/CriticalSection.h b/gfx/2d/CriticalSection.h new file mode 100644 index 000000000..d1eb69abc --- /dev/null +++ b/gfx/2d/CriticalSection.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_CRITICALSECTION_H_ +#define MOZILLA_GFX_CRITICALSECTION_H_ + +#ifdef WIN32 +#include <windows.h> +#else +#include <pthread.h> +#include "mozilla/DebugOnly.h" +#endif + +namespace mozilla { +namespace gfx { + +#ifdef WIN32 + +class CriticalSection { +public: + CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); } + + ~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); } + + void Enter() { ::EnterCriticalSection(&mCriticalSection); } + + void Leave() { ::LeaveCriticalSection(&mCriticalSection); } + +protected: + CRITICAL_SECTION mCriticalSection; +}; + +#else +// posix + +class PosixCondvar; +class CriticalSection { +public: + CriticalSection() { + DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr); + MOZ_ASSERT(!err); + } + + ~CriticalSection() { + DebugOnly<int> err = pthread_mutex_destroy(&mMutex); + MOZ_ASSERT(!err); + } + + void Enter() { + DebugOnly<int> err = pthread_mutex_lock(&mMutex); + MOZ_ASSERT(!err); + } + + void Leave() { + DebugOnly<int> err = pthread_mutex_unlock(&mMutex); + MOZ_ASSERT(!err); + } + +protected: + pthread_mutex_t mMutex; + friend class PosixCondVar; +}; + +#endif + +/// RAII helper. +struct CriticalSectionAutoEnter { + explicit CriticalSectionAutoEnter(CriticalSection* aSection) : mSection(aSection) { mSection->Enter(); } + ~CriticalSectionAutoEnter() { mSection->Leave(); } +protected: + CriticalSection* mSection; +}; + + +} // namespace +} // namespace + +#endif diff --git a/gfx/2d/DataSourceSurface.cpp b/gfx/2d/DataSourceSurface.cpp new file mode 100644 index 000000000..75d843506 --- /dev/null +++ b/gfx/2d/DataSourceSurface.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "DataSourceSurfaceWrapper.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<DataSourceSurface> +DataSourceSurface::GetDataSurface() +{ + RefPtr<DataSourceSurface> surface = + (GetType() == SurfaceType::DATA) ? this : new DataSourceSurfaceWrapper(this); + return surface.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DataSourceSurfaceWrapper.h b/gfx/2d/DataSourceSurfaceWrapper.h new file mode 100644 index 000000000..d1112b57c --- /dev/null +++ b/gfx/2d/DataSourceSurfaceWrapper.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ +#define MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +// Wraps a DataSourceSurface and forwards all methods except for GetType(), +// from which it always returns SurfaceType::DATA. +class DataSourceSurfaceWrapper : public DataSourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceWrapper, override) + explicit DataSourceSurfaceWrapper(DataSourceSurface *aSurface) + : mSurface(aSurface) + {} + + virtual SurfaceType GetType() const override { return SurfaceType::DATA; } + + virtual uint8_t *GetData() override { return mSurface->GetData(); } + virtual int32_t Stride() override { return mSurface->Stride(); } + virtual IntSize GetSize() const override { return mSurface->GetSize(); } + virtual SurfaceFormat GetFormat() const override { return mSurface->GetFormat(); } + virtual bool IsValid() const override { return mSurface->IsValid(); } + +private: + RefPtr<DataSourceSurface> mSurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ */ diff --git a/gfx/2d/DataSurfaceHelpers.cpp b/gfx/2d/DataSurfaceHelpers.cpp new file mode 100644 index 000000000..87ef00fcd --- /dev/null +++ b/gfx/2d/DataSurfaceHelpers.cpp @@ -0,0 +1,374 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <cstring> + +#include "2D.h" +#include "DataSurfaceHelpers.h" +#include "Logging.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/PodOperations.h" +#include "Tools.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<DataSourceSurface> +CreateDataSourceSurfaceFromData(const IntSize& aSize, + SurfaceFormat aFormat, + const uint8_t* aData, + int32_t aDataStride) +{ + RefPtr<DataSourceSurface> srcSurface = + Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData), + aDataStride, + aSize, + aFormat); + RefPtr<DataSourceSurface> destSurface = + Factory::CreateDataSourceSurface(aSize, aFormat, false); + + if (!srcSurface || !destSurface) { + return nullptr; + } + + if (CopyRect(srcSurface, + destSurface, + IntRect(IntPoint(), srcSurface->GetSize()), + IntPoint())) { + return destSurface.forget(); + } + + return nullptr; +} + +already_AddRefed<DataSourceSurface> +CreateDataSourceSurfaceWithStrideFromData(const IntSize &aSize, + SurfaceFormat aFormat, + int32_t aStride, + const uint8_t* aData, + int32_t aDataStride) +{ + RefPtr<DataSourceSurface> srcSurface = + Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData), + aDataStride, + aSize, + aFormat); + RefPtr<DataSourceSurface> destSurface = + Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, aStride, false); + + if (!srcSurface || !destSurface) { + return nullptr; + } + + if (CopyRect(srcSurface, + destSurface, + IntRect(IntPoint(), srcSurface->GetSize()), + IntPoint())) { + return destSurface.forget(); + } + + return nullptr; +} + +uint8_t* +DataAtOffset(DataSourceSurface* aSurface, + const DataSourceSurface::MappedSurface* aMap, + IntPoint aPoint) +{ + if (!SurfaceContainsPoint(aSurface, aPoint)) { + MOZ_CRASH("GFX: sample position needs to be inside surface!"); + } + + MOZ_ASSERT(Factory::CheckSurfaceSize(aSurface->GetSize()), + "surface size overflows - this should have been prevented when the surface was created"); + + uint8_t* data = aMap->mData + aPoint.y * aMap->mStride + + aPoint.x * BytesPerPixel(aSurface->GetFormat()); + + if (data < aMap->mData) { + MOZ_CRASH("GFX: out-of-range data access"); + } + + return data; +} + +// This check is safe against integer overflow. +bool +SurfaceContainsPoint(SourceSurface* aSurface, const IntPoint& aPoint) +{ + IntSize size = aSurface->GetSize(); + return aPoint.x >= 0 && aPoint.x < size.width && + aPoint.y >= 0 && aPoint.y < size.height; +} + +void +ConvertBGRXToBGRA(uint8_t* aData, const IntSize &aSize, const int32_t aStride) +{ + int height = aSize.height, width = aSize.width * 4; + + for (int row = 0; row < height; ++row) { + for (int column = 0; column < width; column += 4) { +#ifdef IS_BIG_ENDIAN + aData[column] = 0xFF; +#else + aData[column + 3] = 0xFF; +#endif + } + aData += aStride; + } +} + +void +CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst, IntSize aSrcSize, + int32_t aSrcStride, int32_t aBytesPerPixel) +{ + MOZ_ASSERT(aBytesPerPixel > 0, + "Negative stride for aDst not currently supported"); + MOZ_ASSERT(BufferSizeFromStrideAndHeight(aSrcStride, aSrcSize.height) > 0, + "How did we end up with a surface with such a big buffer?"); + + int packedStride = aSrcSize.width * aBytesPerPixel; + + if (aSrcStride == packedStride) { + // aSrc is already packed, so we can copy with a single memcpy. + memcpy(aDst, aSrc, packedStride * aSrcSize.height); + } else { + // memcpy one row at a time. + for (int row = 0; row < aSrcSize.height; ++row) { + memcpy(aDst, aSrc, packedStride); + aSrc += aSrcStride; + aDst += packedStride; + } + } +} + +void +CopyBGRXSurfaceDataToPackedBGRArray(uint8_t* aSrc, uint8_t* aDst, + IntSize aSrcSize, int32_t aSrcStride) +{ + int packedStride = aSrcSize.width * 3; + + uint8_t* srcPx = aSrc; + uint8_t* dstPx = aDst; + + for (int row = 0; row < aSrcSize.height; ++row) { + for (int col = 0; col < aSrcSize.width; ++col) { + dstPx[0] = srcPx[0]; + dstPx[1] = srcPx[1]; + dstPx[2] = srcPx[2]; + // srcPx[3] (unused or alpha component) dropped on floor + srcPx += 4; + dstPx += 3; + } + srcPx = aSrc += aSrcStride; + dstPx = aDst += packedStride; + } +} + +UniquePtr<uint8_t[]> +SurfaceToPackedBGRA(DataSourceSurface *aSurface) +{ + SurfaceFormat format = aSurface->GetFormat(); + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + return nullptr; + } + + IntSize size = aSurface->GetSize(); + + UniquePtr<uint8_t[]> imageBuffer( + new (std::nothrow) uint8_t[size.width * size.height * sizeof(uint32_t)]); + if (!imageBuffer) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return nullptr; + } + + CopySurfaceDataToPackedArray(map.mData, imageBuffer.get(), size, + map.mStride, 4 * sizeof(uint8_t)); + + aSurface->Unmap(); + + if (format == SurfaceFormat::B8G8R8X8) { + // Convert BGRX to BGRA by setting a to 255. + ConvertBGRXToBGRA(imageBuffer.get(), size, size.width * sizeof(uint32_t)); + } + + return imageBuffer; +} + +uint8_t* +SurfaceToPackedBGR(DataSourceSurface *aSurface) +{ + SurfaceFormat format = aSurface->GetFormat(); + MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8, "Format not supported"); + + if (format != SurfaceFormat::B8G8R8X8) { + // To support B8G8R8A8 we'd need to un-pre-multiply alpha + return nullptr; + } + + IntSize size = aSurface->GetSize(); + + uint8_t* imageBuffer = new (std::nothrow) uint8_t[size.width * size.height * 3 * sizeof(uint8_t)]; + if (!imageBuffer) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { + delete [] imageBuffer; + return nullptr; + } + + CopyBGRXSurfaceDataToPackedBGRArray(map.mData, imageBuffer, size, + map.mStride); + + aSurface->Unmap(); + + return imageBuffer; +} + +void +ClearDataSourceSurface(DataSourceSurface *aSurface) +{ + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { + MOZ_ASSERT(false, "Failed to map DataSourceSurface"); + return; + } + + // We avoid writing into the gaps between the rows here since we can't be + // sure that some drivers don't use those bytes. + + uint32_t width = aSurface->GetSize().width; + uint32_t bytesPerRow = width * BytesPerPixel(aSurface->GetFormat()); + uint8_t* row = map.mData; + // converting to size_t here because otherwise the temporaries can overflow + // and we can end up with |end| being a bad address! + uint8_t* end = row + size_t(map.mStride) * size_t(aSurface->GetSize().height); + + while (row != end) { + memset(row, 0, bytesPerRow); + row += map.mStride; + } + + aSurface->Unmap(); +} + +size_t +BufferSizeFromStrideAndHeight(int32_t aStride, + int32_t aHeight, + int32_t aExtraBytes) +{ + if (MOZ_UNLIKELY(aHeight <= 0) || MOZ_UNLIKELY(aStride <= 0)) { + return 0; + } + + // We limit the length returned to values that can be represented by int32_t + // because we don't want to allocate buffers any bigger than that. This + // allows for a buffer size of over 2 GiB which is already rediculously + // large and will make the process janky. (Note the choice of the signed type + // is deliberate because we specifically don't want the returned value to + // overflow if someone stores the buffer length in an int32_t variable.) + + CheckedInt32 requiredBytes = + CheckedInt32(aStride) * CheckedInt32(aHeight) + CheckedInt32(aExtraBytes); + if (MOZ_UNLIKELY(!requiredBytes.isValid())) { + gfxWarning() << "Buffer size too big; returning zero " << aStride << ", " << aHeight << ", " << aExtraBytes; + return 0; + } + return requiredBytes.value(); +} + +size_t +BufferSizeFromDimensions(int32_t aWidth, + int32_t aHeight, + int32_t aDepth, + int32_t aExtraBytes) +{ + if (MOZ_UNLIKELY(aHeight <= 0) || + MOZ_UNLIKELY(aWidth <= 0) || + MOZ_UNLIKELY(aDepth <= 0)) { + return 0; + } + + // Similar to BufferSizeFromStrideAndHeight, but with an extra parameter. + + CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * CheckedInt32(aDepth) + CheckedInt32(aExtraBytes); + if (MOZ_UNLIKELY(!requiredBytes.isValid())) { + gfxWarning() << "Buffer size too big; returning zero " << aWidth << ", " << aHeight << ", " << aDepth << ", " << aExtraBytes; + return 0; + } + return requiredBytes.value(); +} + +/** + * aSrcRect: Rect relative to the aSrc surface + * aDestPoint: Point inside aDest surface + */ +bool +CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest, + IntRect aSrcRect, IntPoint aDestPoint) +{ + if (aSrcRect.Overflows() || + IntRect(aDestPoint, aSrcRect.Size()).Overflows()) { + MOZ_CRASH("GFX: we should never be getting invalid rects at this point"); + } + + MOZ_RELEASE_ASSERT(aSrc->GetFormat() == aDest->GetFormat(), + "GFX: different surface formats"); + MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aSrc->GetSize()).Contains(aSrcRect), + "GFX: source rect too big for source surface"); + MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aDest->GetSize()).Contains(IntRect(aDestPoint, aSrcRect.Size())), + "GFX: dest surface too small"); + + if (aSrcRect.IsEmpty()) { + return false; + } + + DataSourceSurface::ScopedMap srcMap(aSrc, DataSourceSurface::READ); + DataSourceSurface::ScopedMap destMap(aDest, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!srcMap.IsMapped() || !destMap.IsMapped())) { + return false; + } + + uint8_t* sourceData = DataAtOffset(aSrc, srcMap.GetMappedSurface(), aSrcRect.TopLeft()); + uint32_t sourceStride = srcMap.GetStride(); + uint8_t* destData = DataAtOffset(aDest, destMap.GetMappedSurface(), aDestPoint); + uint32_t destStride = destMap.GetStride(); + + if (BytesPerPixel(aSrc->GetFormat()) == 4) { + for (int32_t y = 0; y < aSrcRect.height; y++) { + PodCopy((int32_t*)destData, (int32_t*)sourceData, aSrcRect.width); + sourceData += sourceStride; + destData += destStride; + } + } else if (BytesPerPixel(aSrc->GetFormat()) == 1) { + for (int32_t y = 0; y < aSrcRect.height; y++) { + PodCopy(destData, sourceData, aSrcRect.width); + sourceData += sourceStride; + destData += destStride; + } + } + + return true; +} + +already_AddRefed<DataSourceSurface> +CreateDataSourceSurfaceByCloning(DataSourceSurface* aSource) +{ + RefPtr<DataSourceSurface> copy = + Factory::CreateDataSourceSurface(aSource->GetSize(), aSource->GetFormat(), true); + if (copy) { + CopyRect(aSource, copy, IntRect(IntPoint(), aSource->GetSize()), IntPoint()); + } + return copy.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DataSurfaceHelpers.h b/gfx/2d/DataSurfaceHelpers.h new file mode 100644 index 000000000..b0fdf2983 --- /dev/null +++ b/gfx/2d/DataSurfaceHelpers.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_DATASURFACEHELPERS_H +#define _MOZILLA_GFX_DATASURFACEHELPERS_H + +#include "2D.h" + +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace gfx { + +/** + * Create a DataSourceSurface and init the surface with the |aData|. The stride + * of this source surface might be different from the input data's |aDataStride|. + * System will try to use the optimal one. + */ +already_AddRefed<DataSourceSurface> +CreateDataSourceSurfaceFromData(const IntSize& aSize, + SurfaceFormat aFormat, + const uint8_t* aData, + int32_t aDataStride); + +/** + * Similar to CreateDataSourceSurfaceFromData(), but could setup the stride for + * this surface. + */ +already_AddRefed<DataSourceSurface> +CreateDataSourceSurfaceWithStrideFromData(const IntSize &aSize, + SurfaceFormat aFormat, + int32_t aStride, + const uint8_t* aData, + int32_t aDataStride); + +void +ConvertBGRXToBGRA(uint8_t* aData, const IntSize &aSize, const int32_t aStride); + +/** + * Copy the pixel data from aSrc and pack it into aDst. aSrcSize, aSrcStride + * and aBytesPerPixel give the size, stride and bytes per pixel for aSrc's + * surface. Callers are responsible for making sure that aDst is big enough to + * contain |aSrcSize.width * aSrcSize.height * aBytesPerPixel| bytes. + */ +void +CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst, IntSize aSrcSize, + int32_t aSrcStride, int32_t aBytesPerPixel); + +/** + * Convert aSurface to a packed buffer in BGRA format. + */ +UniquePtr<uint8_t[]> +SurfaceToPackedBGRA(DataSourceSurface *aSurface); + +/** + * Convert aSurface to a packed buffer in BGR format. The pixel data is + * returned in a buffer allocated with new uint8_t[]. The caller then has + * ownership of the buffer and is responsible for delete[]'ing it. + * + * This function is currently only intended for use with surfaces of format + * SurfaceFormat::B8G8R8X8 since the X components of the pixel data (if any) + * are simply dropped (no attempt is made to un-pre-multiply alpha from the + * color components). + */ +uint8_t* +SurfaceToPackedBGR(DataSourceSurface *aSurface); + +/** + * Clears all the bytes in a DataSourceSurface's data array to zero (so to + * transparent black for SurfaceFormat::B8G8R8A8, for example). + * Note that DataSourceSurfaces can be initialized to zero, which is + * more efficient than zeroing the surface after initialization. + */ +void +ClearDataSourceSurface(DataSourceSurface *aSurface); + +/** + * Multiplies aStride and aHeight and makes sure the result is limited to + * something sane. To keep things consistent, this should always be used + * wherever we allocate a buffer based on surface stride and height. + * + * @param aExtra Optional argument to specify an additional number of trailing + * bytes (useful for creating intermediate surfaces for filters, for + * example). + * + * @return The result of the multiplication if it is acceptable, or else zero. + */ +size_t +BufferSizeFromStrideAndHeight(int32_t aStride, + int32_t aHeight, + int32_t aExtraBytes = 0); + +/** + * Multiplies aWidth, aHeight, aDepth and makes sure the result is limited to + * something sane. To keep things consistent, this should always be used + * wherever we allocate a buffer based on surface dimensions. + * + * @param aExtra Optional argument to specify an additional number of trailing + * bytes (useful for creating intermediate surfaces for filters, for + * example). + * + * @return The result of the multiplication if it is acceptable, or else zero. + */ +size_t +BufferSizeFromDimensions(int32_t aWidth, + int32_t aHeight, + int32_t aDepth, + int32_t aExtraBytes = 0); +/** + * Copy aSrcRect from aSrc to aDest starting at aDestPoint. + * @returns false if the copy is not successful or the aSrc's size is empty. + */ +bool +CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest, + IntRect aSrcRect, IntPoint aDestPoint); + +/** + * Create a non aliasing copy of aSource. This creates a new DataSourceSurface + * using the factory and copies the bits. + * + * @return a dss allocated by Factory that contains a copy a aSource. + */ +already_AddRefed<DataSourceSurface> +CreateDataSourceSurfaceByCloning(DataSourceSurface* aSource); + +/** + * Return the byte at aPoint. + */ +uint8_t* +DataAtOffset(DataSourceSurface* aSurface, + const DataSourceSurface::MappedSurface* aMap, + IntPoint aPoint); + +/** + * Check if aPoint is contained by the surface. + * + * @returns true if and only if aPoint is inside the surface. + */ +bool +SurfaceContainsPoint(SourceSurface* aSurface, const IntPoint& aPoint); + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_DATASURFACEHELPERS_H diff --git a/gfx/2d/DrawCommand.h b/gfx/2d/DrawCommand.h new file mode 100644 index 000000000..eb415c70a --- /dev/null +++ b/gfx/2d/DrawCommand.h @@ -0,0 +1,594 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWCOMMAND_H_ +#define MOZILLA_GFX_DRAWCOMMAND_H_ + +#include <math.h> + +#include "2D.h" +#include "Filters.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +enum class CommandType : int8_t { + DRAWSURFACE = 0, + DRAWFILTER, + DRAWSURFACEWITHSHADOW, + CLEARRECT, + COPYSURFACE, + COPYRECT, + FILLRECT, + STROKERECT, + STROKELINE, + STROKE, + FILL, + FILLGLYPHS, + MASK, + MASKSURFACE, + PUSHCLIP, + PUSHCLIPRECT, + POPCLIP, + SETTRANSFORM, + FLUSH +}; + +class DrawingCommand +{ +public: + virtual ~DrawingCommand() {} + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform = nullptr) const = 0; + + virtual bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const { return false; } + +protected: + explicit DrawingCommand(CommandType aType) + : mType(aType) + { + } + + CommandType GetType() { return mType; } + +private: + CommandType mType; +}; + +class StoredPattern +{ +public: + explicit StoredPattern(const Pattern& aPattern) + { + Assign(aPattern); + } + + void Assign(const Pattern& aPattern) + { + switch (aPattern.GetType()) { + case PatternType::COLOR: + new (mColor)ColorPattern(*static_cast<const ColorPattern*>(&aPattern)); + return; + case PatternType::SURFACE: + { + SurfacePattern* surfPat = new (mSurface)SurfacePattern(*static_cast<const SurfacePattern*>(&aPattern)); + surfPat->mSurface->GuaranteePersistance(); + return; + } + case PatternType::LINEAR_GRADIENT: + new (mLinear)LinearGradientPattern(*static_cast<const LinearGradientPattern*>(&aPattern)); + return; + case PatternType::RADIAL_GRADIENT: + new (mRadial)RadialGradientPattern(*static_cast<const RadialGradientPattern*>(&aPattern)); + return; + } + } + + ~StoredPattern() + { + reinterpret_cast<Pattern*>(mPattern)->~Pattern(); + } + + operator Pattern&() + { + return *reinterpret_cast<Pattern*>(mPattern); + } + + operator const Pattern&() const + { + return *reinterpret_cast<const Pattern*>(mPattern); + } + + StoredPattern(const StoredPattern& aPattern) + { + Assign(aPattern); + } + +private: + StoredPattern operator=(const StoredPattern& aOther) + { + // Block this so that we notice if someone's doing excessive assigning. + return *this; + } + + union { + char mPattern[sizeof(Pattern)]; + char mColor[sizeof(ColorPattern)]; + char mLinear[sizeof(LinearGradientPattern)]; + char mRadial[sizeof(RadialGradientPattern)]; + char mSurface[sizeof(SurfacePattern)]; + }; +}; + +class DrawSurfaceCommand : public DrawingCommand +{ +public: + DrawSurfaceCommand(SourceSurface *aSurface, const Rect& aDest, + const Rect& aSource, const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::DRAWSURFACE) + , mSurface(aSurface), mDest(aDest) + , mSource(aSource), mSurfOptions(aSurfOptions) + , mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->DrawSurface(mSurface, mDest, mSource, mSurfOptions, mOptions); + } + +private: + RefPtr<SourceSurface> mSurface; + Rect mDest; + Rect mSource; + DrawSurfaceOptions mSurfOptions; + DrawOptions mOptions; +}; + +class DrawFilterCommand : public DrawingCommand +{ +public: + DrawFilterCommand(FilterNode* aFilter, const Rect& aSourceRect, + const Point& aDestPoint, const DrawOptions& aOptions) + : DrawingCommand(CommandType::DRAWSURFACE) + , mFilter(aFilter), mSourceRect(aSourceRect) + , mDestPoint(aDestPoint), mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions); + } + +private: + RefPtr<FilterNode> mFilter; + Rect mSourceRect; + Point mDestPoint; + DrawOptions mOptions; +}; + +class ClearRectCommand : public DrawingCommand +{ +public: + explicit ClearRectCommand(const Rect& aRect) + : DrawingCommand(CommandType::CLEARRECT) + , mRect(aRect) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->ClearRect(mRect); + } + +private: + Rect mRect; +}; + +class CopySurfaceCommand : public DrawingCommand +{ +public: + CopySurfaceCommand(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) + : DrawingCommand(CommandType::COPYSURFACE) + , mSurface(aSurface) + , mSourceRect(aSourceRect) + , mDestination(aDestination) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform) const + { + MOZ_ASSERT(!aTransform || !aTransform->HasNonIntegerTranslation()); + Point dest(Float(mDestination.x), Float(mDestination.y)); + if (aTransform) { + dest = aTransform->TransformPoint(dest); + } + aDT->CopySurface(mSurface, mSourceRect, IntPoint(uint32_t(dest.x), uint32_t(dest.y))); + } + +private: + RefPtr<SourceSurface> mSurface; + IntRect mSourceRect; + IntPoint mDestination; +}; + +class FillRectCommand : public DrawingCommand +{ +public: + FillRectCommand(const Rect& aRect, + const Pattern& aPattern, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::FILLRECT) + , mRect(aRect) + , mPattern(aPattern) + , mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->FillRect(mRect, mPattern, mOptions); + } + + bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const + { + aDeviceRect = aTransform.TransformBounds(mRect); + return true; + } + +private: + Rect mRect; + StoredPattern mPattern; + DrawOptions mOptions; +}; + +class StrokeRectCommand : public DrawingCommand +{ +public: + StrokeRectCommand(const Rect& aRect, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::STROKERECT) + , mRect(aRect) + , mPattern(aPattern) + , mStrokeOptions(aStrokeOptions) + , mOptions(aOptions) + { + if (aStrokeOptions.mDashLength) { + mDashes.resize(aStrokeOptions.mDashLength); + mStrokeOptions.mDashPattern = &mDashes.front(); + memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float)); + } + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->StrokeRect(mRect, mPattern, mStrokeOptions, mOptions); + } + +private: + Rect mRect; + StoredPattern mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; + std::vector<Float> mDashes; +}; + +class StrokeLineCommand : public DrawingCommand +{ +public: + StrokeLineCommand(const Point& aStart, + const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::STROKELINE) + , mStart(aStart) + , mEnd(aEnd) + , mPattern(aPattern) + , mStrokeOptions(aStrokeOptions) + , mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->StrokeLine(mStart, mEnd, mPattern, mStrokeOptions, mOptions); + } + +private: + Point mStart; + Point mEnd; + StoredPattern mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class FillCommand : public DrawingCommand +{ +public: + FillCommand(const Path* aPath, + const Pattern& aPattern, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::FILL) + , mPath(const_cast<Path*>(aPath)) + , mPattern(aPattern) + , mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->Fill(mPath, mPattern, mOptions); + } + + bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const + { + aDeviceRect = mPath->GetBounds(aTransform); + return true; + } + +private: + RefPtr<Path> mPath; + StoredPattern mPattern; + DrawOptions mOptions; +}; + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 +#endif + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.707106781186547524400844362104849039 +#endif + +// The logic for this comes from _cairo_stroke_style_max_distance_from_path +static Rect +PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions, + const Rect &aRect, + const Matrix &aTransform) +{ + double styleExpansionFactor = 0.5f; + + if (aStrokeOptions.mLineCap == CapStyle::SQUARE) { + styleExpansionFactor = M_SQRT1_2; + } + + if (aStrokeOptions.mLineJoin == JoinStyle::MITER && + styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) { + styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit; + } + + styleExpansionFactor *= aStrokeOptions.mLineWidth; + + double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21); + double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12); + + Rect result = aRect; + result.Inflate(dx, dy); + return result; +} + +class StrokeCommand : public DrawingCommand +{ +public: + StrokeCommand(const Path* aPath, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::STROKE) + , mPath(const_cast<Path*>(aPath)) + , mPattern(aPattern) + , mStrokeOptions(aStrokeOptions) + , mOptions(aOptions) + { + if (aStrokeOptions.mDashLength) { + mDashes.resize(aStrokeOptions.mDashLength); + mStrokeOptions.mDashPattern = &mDashes.front(); + memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float)); + } + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->Stroke(mPath, mPattern, mStrokeOptions, mOptions); + } + + bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const + { + aDeviceRect = PathExtentsToMaxStrokeExtents(mStrokeOptions, mPath->GetBounds(aTransform), aTransform); + return true; + } + +private: + RefPtr<Path> mPath; + StoredPattern mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; + std::vector<Float> mDashes; +}; + +class FillGlyphsCommand : public DrawingCommand +{ +public: + FillGlyphsCommand(ScaledFont* aFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions, + const GlyphRenderingOptions* aRenderingOptions) + : DrawingCommand(CommandType::FILLGLYPHS) + , mFont(aFont) + , mPattern(aPattern) + , mOptions(aOptions) + , mRenderingOptions(const_cast<GlyphRenderingOptions*>(aRenderingOptions)) + { + mGlyphs.resize(aBuffer.mNumGlyphs); + memcpy(&mGlyphs.front(), aBuffer.mGlyphs, sizeof(Glyph) * aBuffer.mNumGlyphs); + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + GlyphBuffer buf; + buf.mNumGlyphs = mGlyphs.size(); + buf.mGlyphs = &mGlyphs.front(); + aDT->FillGlyphs(mFont, buf, mPattern, mOptions, mRenderingOptions); + } + +private: + RefPtr<ScaledFont> mFont; + std::vector<Glyph> mGlyphs; + StoredPattern mPattern; + DrawOptions mOptions; + RefPtr<GlyphRenderingOptions> mRenderingOptions; +}; + +class MaskCommand : public DrawingCommand +{ +public: + MaskCommand(const Pattern& aSource, + const Pattern& aMask, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::MASK) + , mSource(aSource) + , mMask(aMask) + , mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->Mask(mSource, mMask, mOptions); + } + +private: + StoredPattern mSource; + StoredPattern mMask; + DrawOptions mOptions; +}; + +class MaskSurfaceCommand : public DrawingCommand +{ +public: + MaskSurfaceCommand(const Pattern& aSource, + const SourceSurface* aMask, + const Point& aOffset, + const DrawOptions& aOptions) + : DrawingCommand(CommandType::MASKSURFACE) + , mSource(aSource) + , mMask(const_cast<SourceSurface*>(aMask)) + , mOffset(aOffset) + , mOptions(aOptions) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->MaskSurface(mSource, mMask, mOffset, mOptions); + } + +private: + StoredPattern mSource; + RefPtr<SourceSurface> mMask; + Point mOffset; + DrawOptions mOptions; +}; + +class PushClipCommand : public DrawingCommand +{ +public: + explicit PushClipCommand(const Path* aPath) + : DrawingCommand(CommandType::PUSHCLIP) + , mPath(const_cast<Path*>(aPath)) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->PushClip(mPath); + } + +private: + RefPtr<Path> mPath; +}; + +class PushClipRectCommand : public DrawingCommand +{ +public: + explicit PushClipRectCommand(const Rect& aRect) + : DrawingCommand(CommandType::PUSHCLIPRECT) + , mRect(aRect) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->PushClipRect(mRect); + } + +private: + Rect mRect; +}; + +class PopClipCommand : public DrawingCommand +{ +public: + PopClipCommand() + : DrawingCommand(CommandType::POPCLIP) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->PopClip(); + } +}; + +class SetTransformCommand : public DrawingCommand +{ +public: + explicit SetTransformCommand(const Matrix& aTransform) + : DrawingCommand(CommandType::SETTRANSFORM) + , mTransform(aTransform) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aMatrix) const + { + if (aMatrix) { + aDT->SetTransform(mTransform * (*aMatrix)); + } else { + aDT->SetTransform(mTransform); + } + } + +private: + Matrix mTransform; +}; + +class FlushCommand : public DrawingCommand +{ +public: + explicit FlushCommand() + : DrawingCommand(CommandType::FLUSH) + { + } + + virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const + { + aDT->Flush(); + } +}; + +} // namespace gfx + +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWCOMMAND_H_ */ diff --git a/gfx/2d/DrawEventRecorder.cpp b/gfx/2d/DrawEventRecorder.cpp new file mode 100644 index 000000000..0e20b8b5a --- /dev/null +++ b/gfx/2d/DrawEventRecorder.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawEventRecorder.h" +#include "PathRecording.h" +#include "RecordingTypes.h" + +namespace mozilla { +namespace gfx { + +using namespace std; + +DrawEventRecorderPrivate::DrawEventRecorderPrivate(std::ostream *aStream) + : mOutputStream(aStream) +{ +} + +void +DrawEventRecorderPrivate::WriteHeader() +{ + WriteElement(*mOutputStream, kMagicInt); + WriteElement(*mOutputStream, kMajorRevision); + WriteElement(*mOutputStream, kMinorRevision); +} + +void +DrawEventRecorderPrivate::RecordEvent(const RecordedEvent &aEvent) +{ + WriteElement(*mOutputStream, aEvent.mType); + + aEvent.RecordToStream(*mOutputStream); + + Flush(); +} + +DrawEventRecorderFile::DrawEventRecorderFile(const char *aFilename) + : DrawEventRecorderPrivate(nullptr) + , mOutputFile(aFilename, ofstream::binary) +{ + mOutputStream = &mOutputFile; + + WriteHeader(); +} + +DrawEventRecorderFile::~DrawEventRecorderFile() +{ + mOutputFile.close(); +} + +void +DrawEventRecorderFile::Flush() +{ + mOutputFile.flush(); +} + +bool +DrawEventRecorderFile::IsOpen() +{ + return mOutputFile.is_open(); +} + +void +DrawEventRecorderFile::OpenNew(const char *aFilename) +{ + MOZ_ASSERT(!mOutputFile.is_open()); + + mOutputFile.open(aFilename, ofstream::binary); + WriteHeader(); +} + +void +DrawEventRecorderFile::Close() +{ + MOZ_ASSERT(mOutputFile.is_open()); + + mOutputFile.close(); +} + +DrawEventRecorderMemory::DrawEventRecorderMemory() + : DrawEventRecorderPrivate(nullptr) +{ + mOutputStream = &mMemoryStream; + + WriteHeader(); +} + +void +DrawEventRecorderMemory::Flush() +{ + mOutputStream->flush(); +} + +size_t +DrawEventRecorderMemory::RecordingSize() +{ + return mMemoryStream.tellp(); +} + +bool +DrawEventRecorderMemory::CopyRecording(char* aBuffer, size_t aBufferLen) +{ + return !!mMemoryStream.read(aBuffer, aBufferLen); +} + +void +DrawEventRecorderMemory::WipeRecording() +{ + mMemoryStream.str(std::string()); + mMemoryStream.clear(); + + WriteHeader(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawEventRecorder.h b/gfx/2d/DrawEventRecorder.h new file mode 100644 index 000000000..a789379d2 --- /dev/null +++ b/gfx/2d/DrawEventRecorder.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWEVENTRECORDER_H_ +#define MOZILLA_GFX_DRAWEVENTRECORDER_H_ + +#include "2D.h" +#include "RecordedEvent.h" +#include <ostream> +#include <fstream> + +#if defined(_MSC_VER) +#include <unordered_set> +#else +#include <set> +#endif + +namespace mozilla { +namespace gfx { + +class PathRecording; + +class DrawEventRecorderPrivate : public DrawEventRecorder +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPrivate) + explicit DrawEventRecorderPrivate(std::ostream *aStream); + virtual ~DrawEventRecorderPrivate() { } + + void WriteHeader(); + + void RecordEvent(const RecordedEvent &aEvent); + void WritePath(const PathRecording *aPath); + + void AddStoredObject(const ReferencePtr aObject) { + mStoredObjects.insert(aObject); + } + + void RemoveStoredObject(const ReferencePtr aObject) { + mStoredObjects.erase(aObject); + } + + bool HasStoredObject(const ReferencePtr aObject) { + return mStoredObjects.find(aObject) != mStoredObjects.end(); + } + + void AddStoredFontData(const uint64_t aFontDataKey) { + mStoredFontData.insert(aFontDataKey); + } + + bool HasStoredFontData(const uint64_t aFontDataKey) { + return mStoredFontData.find(aFontDataKey) != mStoredFontData.end(); + } + +protected: + std::ostream *mOutputStream; + + virtual void Flush() = 0; + +#if defined(_MSC_VER) + typedef std::unordered_set<const void*> ObjectSet; + typedef std::unordered_set<uint64_t> Uint64Set; +#else + typedef std::set<const void*> ObjectSet; + typedef std::set<uint64_t> Uint64Set; +#endif + + ObjectSet mStoredObjects; + Uint64Set mStoredFontData; +}; + +class DrawEventRecorderFile : public DrawEventRecorderPrivate +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderFile) + explicit DrawEventRecorderFile(const char *aFilename); + ~DrawEventRecorderFile(); + + /** + * Returns whether a recording file is currently open. + */ + bool IsOpen(); + + /** + * Opens new file with the provided name. The recorder does NOT forget which + * objects it has recorded. This can be used with Close, so that a recording + * can be processed in chunks. The file must not already be open. + */ + void OpenNew(const char *aFilename); + + /** + * Closes the file so that it can be processed. The recorder does NOT forget + * which objects it has recorded. This can be used with OpenNew, so that a + * recording can be processed in chunks. The file must be open. + */ + void Close(); + +private: + virtual void Flush(); + + std::ofstream mOutputFile; +}; + +class DrawEventRecorderMemory final : public DrawEventRecorderPrivate +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderMemory) + + /** + * Constructs a DrawEventRecorder that stores the recording in memory. + */ + DrawEventRecorderMemory(); + + /** + * @return the current size of the recording (in chars). + */ + size_t RecordingSize(); + + /** + * Copies at most aBufferLen chars of the recording into aBuffer. + * + * @param aBuffer buffer to receive the recording chars + * @param aBufferLen length of aBuffer + * @return true if copied successfully + */ + bool CopyRecording(char* aBuffer, size_t aBufferLen); + + /** + * Wipes the internal recording buffer, but the recorder does NOT forget which + * objects it has recorded. This can be used so that a recording can be copied + * and processed in chunks, releasing memory as it goes. + */ + void WipeRecording(); + +private: + ~DrawEventRecorderMemory() {}; + + void Flush() final; + + std::stringstream mMemoryStream; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWEVENTRECORDER_H_ */ diff --git a/gfx/2d/DrawTarget.cpp b/gfx/2d/DrawTarget.cpp new file mode 100644 index 000000000..72e070dc3 --- /dev/null +++ b/gfx/2d/DrawTarget.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "Logging.h" +#include "PathHelpers.h" + +#include "DrawTargetCapture.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<DrawTargetCapture> +DrawTarget::CreateCaptureDT(const IntSize& aSize) +{ + RefPtr<DrawTargetCaptureImpl> dt = new DrawTargetCaptureImpl(); + + if (!dt->Init(aSize, this)) { + gfxWarning() << "Failed to initialize Capture DrawTarget!"; + return nullptr; + } + + return dt.forget(); +} + +void +DrawTarget::DrawCapturedDT(DrawTargetCapture *aCaptureDT, + const Matrix& aTransform) +{ + if (aTransform.HasNonIntegerTranslation()) { + gfxWarning() << "Non integer translations are not supported for DrawCaptureDT at this time!"; + return; + } + static_cast<DrawTargetCaptureImpl*>(aCaptureDT)->ReplayToDrawTarget(this, aTransform); +} + +void +DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) +{ + Matrix oldTransform = GetTransform(); + SetTransform(Matrix()); + + RefPtr<PathBuilder> pathBuilder = CreatePathBuilder(); + for (uint32_t i = 0; i < aCount; i++) { + AppendRectToPath(pathBuilder, Rect(aRects[i])); + } + RefPtr<Path> path = pathBuilder->Finish(); + PushClip(path); + + SetTransform(oldTransform); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp new file mode 100644 index 000000000..c0e4f0af2 --- /dev/null +++ b/gfx/2d/DrawTargetCairo.cpp @@ -0,0 +1,2374 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetCairo.h" + +#include "SourceSurfaceCairo.h" +#include "PathCairo.h" +#include "HelpersCairo.h" +#include "ScaledFontBase.h" +#include "BorrowedContext.h" +#include "FilterNodeSoftware.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.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" +#include "cairo-xlib-xrender.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() == 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() == 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.width += r.X(); + if (r.width < 0.f) + return false; + r.x = 0.f; + } + + if (r.XMost() > CAIRO_COORD_MAX) { + r.width = CAIRO_COORD_MAX - r.X(); + } + + if (r.Y() < 0.f) { + r.height += r.Y(); + if (r.Height() < 0.f) + return false; + + r.y = 0.f; + } + + if (r.YMost() > CAIRO_COORD_MAX) { + r.height = CAIRO_COORD_MAX - r.Y(); + } + 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; + } + default: + return true; + } +} + +static cairo_user_data_key_t surfaceDataKey; + +void +ReleaseData(void* aData) +{ + DataSourceSurface *data = static_cast<DataSourceSurface*>(aData); + data->Unmap(); + data->Release(); +} + +cairo_surface_t* +CopyToImageSurface(unsigned char *aData, + const IntRect &aRect, + int32_t aStride, + SurfaceFormat aFormat) +{ + MOZ_ASSERT(aData); + + cairo_surface_t* surf = cairo_image_surface_create(GfxFormatToCairoFormat(aFormat), + aRect.width, + aRect.height); + // 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 >= aRect.width * pixelWidth); + for (int32_t y = 0; y < aRect.height; ++y) { + memcpy(surfData + y * surfStride, + source + y * aStride, + aRect.width * 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. + */ +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; +} + +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); + 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. + */ +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. + */ +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 +{ +public: + explicit AutoClearDeviceOffset(SourceSurface* aSurface) + : mSurface(nullptr) + , mX(0) + , mY(0) + { + Init(aSurface); + } + + explicit AutoClearDeviceOffset(const Pattern& aPattern) + : mSurface(nullptr) + { + 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: + { + Color 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; + } + 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() +{ + 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)); +} + +already_AddRefed<SourceSurface> +DrawTargetCairo::Snapshot() +{ + if (!IsValid()) { + gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface " << cairo_surface_status(mSurface); + 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, &offset.y); + // 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(), aSource.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)); + cairo_pattern_set_extend(pat, 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 Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) +{ + 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); + + 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), + aSigma, aSigma); + blur.Blur(cairo_image_surface_get_data(blursurf)); + } else { + blursurf = sourcesurf; + surf = sourcesurf; + } + + WillChange(); + ClearSurfaceForUnboundedSource(aOperator); + + cairo_save(mContext); + cairo_set_operator(mContext, GfxOpToCairoOp(aOperator)); + cairo_identity_matrix(mContext); + cairo_translate(mContext, aDest.x, aDest.y); + + if (IsOperatorBoundByMask(aOperator)){ + cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); + cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + + // 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); + } else { + cairo_push_group(mContext); + cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); + cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + + // 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); + 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) && + aDest.y >= aSource.y && + aDest.y < aSource.YMost()) { + 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.x = 0; + source.y = 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 || + !IsFinite(aRect.X()) || !IsFinite(aRect.Width()) || + !IsFinite(aRect.Y()) || !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() +{ + // 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) { + return; + } + + if (!mFontOptions) { + mFontOptions = cairo_font_options_create(); + if (!mFontOptions) { + gfxWarning() << "Failed allocating Cairo font options"; + return; + } + } + + // If the current font requests subpixel AA, force it to gray since we don't + // allow subpixel AA. + cairo_get_font_options(mContext, mFontOptions); + cairo_antialias_t antialias = cairo_font_options_get_antialias(mFontOptions); + if (antialias == CAIRO_ANTIALIAS_SUBPIXEL) { + cairo_font_options_set_antialias(mFontOptions, CAIRO_ANTIALIAS_GRAY); + cairo_set_font_options(mContext, mFontOptions); + } +} + +void +DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA) +{ + DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA); +#ifdef MOZ_TREE_CAIRO + cairo_surface_set_subpixel_antialiasing(cairo_get_group_target(mContext), + aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); +#endif +} + +void +DrawTargetCairo::FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions, + const GlyphRenderingOptions*) +{ + if (mTransformSingular) { + return; + } + + if (!IsValid()) { + gfxDebug() << "FillGlyphs bad surface " << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + if (!aFont) { + gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font"; + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aPattern); + + ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont); + cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont()); + + cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); + if (!pat) + return; + + cairo_set_source(mContext, pat); + cairo_pattern_destroy(pat); + + cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + // Override any font-specific options as necessary. + SetFontOptions(); + + // 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; + } + + 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, -aOffset.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) +{ + 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, mPermitSubpixelAA); + + if (aMask) { + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); + if (surf) { + layer.mMaskPattern = cairo_pattern_create_for_surface(surf); + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(aMaskTransform, 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_ASSERT(mPushedLayers.size()); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + cairo_pop_group_to_source(mContext); + + PushedLayer layer = mPushedLayers.back(); + mPushedLayers.pop_back(); + + if (!layer.mMaskPattern) { + 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_mask(mContext, layer.mMaskPattern); + } + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mTransform, mat); + cairo_set_matrix(mContext, &mat); + + cairo_pattern_destroy(layer.mMaskPattern); + SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA); +} + +already_AddRefed<PathBuilder> +DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FillRule::FILL_WINDING */) const +{ + return MakeAndAddRef<PathBuilderCairo>(aFillRule); +} + +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); +} + +void +DrawTargetCairo::GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices, + uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) +{ + for (uint32_t i = 0; i < aNumGlyphs; i++) { + cairo_glyph_t glyph; + cairo_text_extents_t extents; + glyph.index = aGlyphIndices[i]; + glyph.x = 0; + glyph.y = 0; + cairo_glyph_extents(mContext, &glyph, 1, &extents); + + aGlyphMetrics[i].mXBearing = extents.x_bearing; + aGlyphMetrics[i].mXAdvance = extents.x_advance; + aGlyphMetrics[i].mYBearing = extents.y_bearing; + aGlyphMetrics[i].mYAdvance = extents.y_advance; + aGlyphMetrics[i].mWidth = extents.width; + aGlyphMetrics[i].mHeight = extents.height; + } +} + +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(); +} + +#ifdef CAIRO_HAS_XLIB_SURFACE +static cairo_user_data_key_t gDestroyPixmapKey; + +struct DestroyPixmapClosure { + DestroyPixmapClosure(Drawable d, Screen *s) : mPixmap(d), mScreen(s) {} + ~DestroyPixmapClosure() { + XFreePixmap(DisplayOfScreen(mScreen), mPixmap); + } + Drawable mPixmap; + Screen *mScreen; +}; + +static void +DestroyPixmap(void *data) +{ + delete static_cast<DestroyPixmapClosure*>(data); +} +#endif + +already_AddRefed<SourceSurface> +DrawTargetCairo::OptimizeSourceSurface(SourceSurface *aSurface) const +{ + RefPtr<SourceSurface> surface(aSurface); +#ifdef CAIRO_HAS_XLIB_SURFACE + cairo_surface_type_t ctype = cairo_surface_get_type(mSurface); + if (aSurface->GetType() == SurfaceType::CAIRO && + cairo_surface_get_type( + static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface()) == ctype) { + return surface.forget(); + } + + if (ctype != CAIRO_SURFACE_TYPE_XLIB) { + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + if (!size.width || !size.height) { + return surface.forget(); + } + + // Although the dimension parameters in the xCreatePixmapReq wire protocol are + // 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if + // either dimension cannot be represented by a 16-bit *signed* integer. + #define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff + + if (size.width > XLIB_IMAGE_SIDE_SIZE_LIMIT || + size.height > XLIB_IMAGE_SIDE_SIZE_LIMIT) { + return surface.forget(); + } + + SurfaceFormat format = aSurface->GetFormat(); + Screen *screen = cairo_xlib_surface_get_screen(mSurface); + Display *dpy = DisplayOfScreen(screen); + XRenderPictFormat* xrenderFormat = nullptr; + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + xrenderFormat = XRenderFindStandardFormat(dpy, PictStandardARGB32); + break; + case SurfaceFormat::X8R8G8B8_UINT32: + xrenderFormat = XRenderFindStandardFormat(dpy, PictStandardRGB24); + break; + case SurfaceFormat::A8: + xrenderFormat = XRenderFindStandardFormat(dpy, PictStandardA8); + break; + default: + return surface.forget(); + } + if (!xrenderFormat) { + return surface.forget(); + } + + Drawable pixmap = XCreatePixmap(dpy, RootWindowOfScreen(screen), + size.width, size.height, + xrenderFormat->depth); + if (!pixmap) { + return surface.forget(); + } + + auto closure = MakeUnique<DestroyPixmapClosure>(pixmap, screen); + + ScopedCairoSurface csurf( + cairo_xlib_surface_create_with_xrender_format(dpy, pixmap, + screen, xrenderFormat, + size.width, size.height)); + if (!csurf || cairo_surface_status(csurf)) { + return surface.forget(); + } + + cairo_surface_set_user_data(csurf, &gDestroyPixmapKey, + closure.release(), DestroyPixmap); + + RefPtr<DrawTargetCairo> dt = new DrawTargetCairo(); + if (!dt->Init(csurf, size, &format)) { + return surface.forget(); + } + + dt->CopySurface(aSurface, + IntRect(0, 0, size.width, size.height), + IntPoint(0, 0)); + dt->Flush(); + + surface = new SourceSurfaceCairo(csurf, size, format); +#endif + + 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: + similar = cairo_quartz_surface_create_cg_layer( + mSurface, GfxFormatToCairoContent(aFormat), aSize.width, aSize.height); + break; +#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; +} + +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) { + 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; +} + +static inline pixman_format_code_t +GfxFormatToPixmanFormat(SurfaceFormat aFormat) +{ + switch (aFormat) { + case SurfaceFormat::A8R8G8B8_UINT32: + return PIXMAN_a8r8g8b8; + case SurfaceFormat::X8R8G8B8_UINT32: + return PIXMAN_x8r8g8b8; + case SurfaceFormat::R5G6B5_UINT16: + return PIXMAN_r5g6b5; + case SurfaceFormat::A8: + return PIXMAN_a8; + default: + // Allow both BGRA and ARGB formats to be passed through unmodified, + // even though even though we are actually rendering to A8R8G8B8_UINT32. + if (aFormat == SurfaceFormat::B8G8R8A8 || + aFormat == SurfaceFormat::A8R8G8B8) { + return PIXMAN_a8r8g8b8; + } + return (pixman_format_code_t)0; + } +} + +static inline bool +GfxMatrixToPixmanTransform(const Matrix4x4 &aMatrix, pixman_transform* aResult) +{ + pixman_f_transform fTransform = {{ + { aMatrix._11, aMatrix._21, aMatrix._41 }, + { aMatrix._12, aMatrix._22, aMatrix._42 }, + { aMatrix._14, aMatrix._24, aMatrix._44 } + }}; + return pixman_transform_from_pixman_f_transform(aResult, &fTransform); +} + +#ifndef USE_SKIA +bool +DrawTarget::Draw3DTransformedSurface(SourceSurface* aSurface, const Matrix4x4& aMatrix) +{ + // Composite the 3D transform with the DT's transform. + Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform); + // 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); + // Invert the matrix into a pattern matrix for pixman. + if (!fullMat.Invert()) { + return false; + } + pixman_transform xform; + if (!GfxMatrixToPixmanTransform(fullMat, &xform)) { + return false; + } + + // Read in the source data. + RefPtr<DataSourceSurface> srcSurf = aSurface->GetDataSurface(); + pixman_format_code_t srcFormat = GfxFormatToPixmanFormat(srcSurf->GetFormat()); + if (!srcFormat) { + return false; + } + DataSourceSurface::ScopedMap srcMap(srcSurf, DataSourceSurface::READ); + if (!srcMap.IsMapped()) { + return false; + } + + // 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(), + srcFormat == PIXMAN_a8r8g8b8 ? + srcSurf->GetFormat() : SurfaceFormat::A8R8G8B8_UINT32); + if (!dstSurf) { + return false; + } + + // Wrap the surfaces in pixman images and do the transform. + pixman_image_t* dst = + pixman_image_create_bits(PIXMAN_a8r8g8b8, + xformBounds.width, xformBounds.height, + (uint32_t*)dstSurf->GetData(), dstSurf->Stride()); + if (!dst) { + return false; + } + pixman_image_t* src = + pixman_image_create_bits(srcFormat, + srcSurf->GetSize().width, srcSurf->GetSize().height, + (uint32_t*)srcMap.GetData(), srcMap.GetStride()); + if (!src) { + pixman_image_unref(dst); + return false; + } + + pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, nullptr, 0); + pixman_image_set_transform(src, &xform); + + pixman_image_composite32(PIXMAN_OP_SRC, + src, nullptr, dst, + 0, 0, 0, 0, 0, 0, + xformBounds.width, xformBounds.height); + + pixman_image_unref(dst); + pixman_image_unref(src); + + // 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; +} +#endif + +#ifdef CAIRO_HAS_XLIB_SURFACE +static bool gXRenderInitialized = false; +static bool gXRenderHasTransform = false; + +static bool +SupportsXRender(cairo_surface_t* surface) +{ + if (!surface || + cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_XLIB || + !cairo_xlib_surface_get_xrender_format(surface)) { + return false; + } + + if (gXRenderInitialized) { + return true; + } + gXRenderInitialized = true; + + cairo_device_t* device = cairo_surface_get_device(surface); + if (cairo_device_acquire(device) != CAIRO_STATUS_SUCCESS) { + return false; + } + + Display* display = cairo_xlib_surface_get_display(surface); + int major, minor; + if (XRenderQueryVersion(display, &major, &minor)) { + if (major > 0 || (major == 0 && minor >= 6)) { + gXRenderHasTransform = true; + } + } + + cairo_device_release(device); + + return true; +} +#endif + +bool +DrawTargetCairo::Draw3DTransformedSurface(SourceSurface* aSurface, const Matrix4x4& aMatrix) +{ +#if CAIRO_HAS_XLIB_SURFACE + cairo_surface_t* srcSurf = + aSurface->GetType() == SurfaceType::CAIRO ? + static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface() : nullptr; + if (!SupportsXRender(srcSurf) || !gXRenderHasTransform) { + return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix); + } + + Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform); + IntRect xformBounds = + RoundedOut( + fullMat.TransformAndClipBounds(Rect(Point(0, 0), Size(aSurface->GetSize())), + Rect(Point(0, 0), Size(GetSize())))); + if (xformBounds.IsEmpty()) { + return true; + } + fullMat.PostTranslate(-xformBounds.x, -xformBounds.y, 0); + if (!fullMat.Invert()) { + return false; + } + pixman_transform xform; + if (!GfxMatrixToPixmanTransform(fullMat, &xform)) { + return false; + } + + cairo_surface_t* xformSurf = + cairo_surface_create_similar(srcSurf, CAIRO_CONTENT_COLOR_ALPHA, + xformBounds.width, xformBounds.height); + if (!SupportsXRender(xformSurf)) { + cairo_surface_destroy(xformSurf); + return false; + } + cairo_device_t* device = cairo_surface_get_device(xformSurf); + if (cairo_device_acquire(device) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(xformSurf); + return false; + } + + Display* display = cairo_xlib_surface_get_display(xformSurf); + + Picture srcPict = XRenderCreatePicture(display, + cairo_xlib_surface_get_drawable(srcSurf), + cairo_xlib_surface_get_xrender_format(srcSurf), + 0, nullptr); + XRenderSetPictureFilter(display, srcPict, FilterBilinear, nullptr, 0); + XRenderSetPictureTransform(display, srcPict, (XTransform*)&xform); + + Picture dstPict = XRenderCreatePicture(display, + cairo_xlib_surface_get_drawable(xformSurf), + cairo_xlib_surface_get_xrender_format(xformSurf), + 0, nullptr); + + XRenderComposite(display, PictOpSrc, + srcPict, X11None, dstPict, + 0, 0, 0, 0, 0, 0, + xformBounds.width, xformBounds.height); + + XRenderFreePicture(display, srcPict); + XRenderFreePicture(display, dstPict); + + cairo_device_release(device); + cairo_surface_mark_dirty(xformSurf); + + AutoPrepareForDrawing(this, mContext); + + cairo_identity_matrix(mContext); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + cairo_set_antialias(mContext, CAIRO_ANTIALIAS_DEFAULT); + cairo_set_source_surface(mContext, xformSurf, xformBounds.x, xformBounds.y); + + cairo_new_path(mContext); + cairo_rectangle(mContext, xformBounds.x, xformBounds.y, xformBounds.width, xformBounds.height); + cairo_fill(mContext); + + cairo_surface_destroy(xformSurf); + + return true; +#else + return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix); +#endif +} + +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() +{ + 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 +} + +cairo_t* +BorrowedCairoContext::BorrowCairoContextFromDrawTarget(DrawTarget* aDT) +{ + if (aDT->GetBackendType() != BackendType::CAIRO || + aDT->IsDualDrawTarget() || + aDT->IsTiledDrawTarget()) { + return nullptr; + } + DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT); + + cairoDT->WillChange(); + + // save the state to make it easier for callers to avoid mucking with things + cairo_save(cairoDT->mContext); + + // Neuter the DrawTarget while the context is being borrowed + cairo_t* cairo = cairoDT->mContext; + cairoDT->mContext = nullptr; + + return cairo; +} + +void +BorrowedCairoContext::ReturnCairoContextToDrawTarget(DrawTarget* aDT, + cairo_t* aCairo) +{ + if (aDT->GetBackendType() != BackendType::CAIRO || + aDT->IsDualDrawTarget() || + aDT->IsTiledDrawTarget()) { + return; + } + DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT); + + cairo_restore(aCairo); + cairoDT->mContext = aCairo; +} + +#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->IsDualDrawTarget() || + 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); + mXRenderFormat = cairo_xlib_surface_get_xrender_format(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 000000000..bd0415b06 --- /dev/null +++ b/gfx/2d/DrawTargetCairo.h @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_DRAWTARGET_CAIRO_H_ +#define _MOZILLA_GFX_DRAWTARGET_CAIRO_H_ + +#include "2D.h" +#include "cairo.h" +#include "PathCairo.h" + +#include <vector> + +namespace mozilla { +namespace gfx { + +class SourceSurfaceCairo; + +class GradientStopsCairo : public GradientStops +{ + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCairo) + GradientStopsCairo(GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode) + : mExtendMode(aExtendMode) + { + for (uint32_t i = 0; i < aNumStops; ++i) { + mStops.push_back(aStops[i]); + } + } + + virtual ~GradientStopsCairo() {} + + const std::vector<GradientStop>& GetStops() const + { + return mStops; + } + + ExtendMode GetExtendMode() const + { + return mExtendMode; + } + + virtual BackendType GetBackendType() const { return BackendType::CAIRO; } + + private: + std::vector<GradientStop> mStops; + ExtendMode mExtendMode; +}; + +class DrawTargetCairo final : public DrawTarget +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetCairo, override) + friend class BorrowedCairoContext; + friend class BorrowedXlibDrawable; + + DrawTargetCairo(); + virtual ~DrawTargetCairo(); + + virtual bool IsValid() const override; + virtual DrawTargetType GetType() const override; + virtual BackendType GetBackendType() const override { return BackendType::CAIRO; } + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual IntSize GetSize() override; + + virtual bool IsCurrentGroupOpaque() override; + + virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override; + + virtual bool LockBits(uint8_t** aData, IntSize* aSize, + int32_t* aStride, SurfaceFormat* aFormat, + IntPoint* aOrigin = nullptr) override; + virtual void ReleaseBits(uint8_t* aData) override; + + virtual void Flush() override; + virtual void DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) override; + + virtual void ClearRect(const Rect &aRect) override; + + virtual void CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) override; + virtual void CopyRect(const IntRect &aSourceRect, + const IntPoint &aDestination) override; + + virtual void FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions = nullptr) override; + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()) override; + + virtual bool Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) override; + + virtual void PushClip(const Path *aPath) override; + virtual void PushClipRect(const Rect &aRect) override; + virtual void PopClip() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PopLayer() override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override; + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override; + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override; + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override; + virtual already_AddRefed<DrawTarget> + CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFormat, + float aSigma) const override; + + virtual already_AddRefed<GradientStops> + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + + virtual void GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices, + uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) override; + + virtual void *GetNativeSurface(NativeSurfaceType aType) override; + + bool Init(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat = nullptr); + bool Init(const IntSize& aSize, SurfaceFormat aFormat); + bool Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat); + + virtual void SetTransform(const Matrix& aTransform) override; + + virtual void DetachAllSnapshots() override { MarkSnapshotIndependent(); } + + // Call to set up aContext for drawing (with the current transform, etc). + // Pass the path you're going to be using if you have one. + // Implicitly calls WillChange(aPath). + void PrepareForDrawing(cairo_t* aContext, const Path* aPath = nullptr); + + static cairo_surface_t *GetDummySurface(); + + // Cairo hardcodes this as its maximum surface size. + static size_t GetMaxSurfaceSize() { + return 32767; + } + +private: // methods + // Init cairo surface without doing a cairo_surface_reference() call. + bool InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat = nullptr); + enum DrawPatternType { DRAW_FILL, DRAW_STROKE }; + void DrawPattern(const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, + DrawPatternType aDrawType, + bool aPathBoundsClip = false); + + void CopySurfaceInternal(cairo_surface_t* aSurface, + const IntRect& aSource, + const IntPoint& aDest); + + Rect GetUserSpaceClip(); + + // Call before you make any changes to the backing surface with which this + // context is associated. Pass the path you're going to be using if you have + // one. + void WillChange(const Path* aPath = nullptr); + + // Call if there is any reason to disassociate the snapshot from this draw + // target; for example, because we're going to be destroyed. + void MarkSnapshotIndependent(); + + // If the current operator is "source" then clear the destination before we + // draw into it, to simulate the effect of an unbounded source operator. + void ClearSurfaceForUnboundedSource(const CompositionOp &aOperator); + + // Set the Cairo context font options according to the current draw target + // font state. + void SetFontOptions(); + +private: // data + cairo_t* mContext; + cairo_surface_t* mSurface; + IntSize mSize; + bool mTransformSingular; + + uint8_t* mLockedBits; + + cairo_font_options_t* mFontOptions; + + struct PushedLayer + { + PushedLayer(Float aOpacity, bool aWasPermittingSubpixelAA) + : mOpacity(aOpacity) + , mMaskPattern(nullptr) + , mWasPermittingSubpixelAA(aWasPermittingSubpixelAA) + {} + Float mOpacity; + cairo_pattern_t* mMaskPattern; + bool mWasPermittingSubpixelAA; + }; + std::vector<PushedLayer> mPushedLayers; + + // The latest snapshot of this surface. This needs to be told when this + // target is modified. We keep it alive as a cache. + RefPtr<SourceSurfaceCairo> mSnapshot; + static cairo_surface_t *mDummySurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_DRAWTARGET_CAIRO_H_ diff --git a/gfx/2d/DrawTargetCapture.cpp b/gfx/2d/DrawTargetCapture.cpp new file mode 100644 index 000000000..f0584c427 --- /dev/null +++ b/gfx/2d/DrawTargetCapture.cpp @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetCapture.h" +#include "DrawCommand.h" + +namespace mozilla { +namespace gfx { + + +DrawTargetCaptureImpl::~DrawTargetCaptureImpl() +{ + uint8_t* start = &mDrawCommandStorage.front(); + + uint8_t* current = start; + + while (current < start + mDrawCommandStorage.size()) { + reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->~DrawingCommand(); + current += *(uint32_t*)current; + } +} + +bool +DrawTargetCaptureImpl::Init(const IntSize& aSize, DrawTarget* aRefDT) +{ + if (!aRefDT) { + return false; + } + + mRefDT = aRefDT; + + mSize = aSize; + return true; +} + +already_AddRefed<SourceSurface> +DrawTargetCaptureImpl::Snapshot() +{ + RefPtr<DrawTarget> dt = mRefDT->CreateSimilarDrawTarget(mSize, mRefDT->GetFormat()); + + ReplayToDrawTarget(dt, Matrix()); + + return dt->Snapshot(); +} + +void +DrawTargetCaptureImpl::DetachAllSnapshots() +{} + +#define AppendCommand(arg) new (AppendToCommandList<arg>()) arg + +void +DrawTargetCaptureImpl::DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aOptions) +{ + aSurface->GuaranteePersistance(); + AppendCommand(DrawSurfaceCommand)(aSurface, aDest, aSource, aSurfOptions, aOptions); +} + +void +DrawTargetCaptureImpl::DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions) +{ + // @todo XXX - this won't work properly long term yet due to filternodes not + // being immutable. + AppendCommand(DrawFilterCommand)(aNode, aSourceRect, aDestPoint, aOptions); +} + +void +DrawTargetCaptureImpl::ClearRect(const Rect &aRect) +{ + AppendCommand(ClearRectCommand)(aRect); +} + +void +DrawTargetCaptureImpl::MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions) +{ + aMask->GuaranteePersistance(); + AppendCommand(MaskSurfaceCommand)(aSource, aMask, aOffset, aOptions); +} + +void +DrawTargetCaptureImpl::CopySurface(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) +{ + aSurface->GuaranteePersistance(); + AppendCommand(CopySurfaceCommand)(aSurface, aSourceRect, aDestination); +} + +void +DrawTargetCaptureImpl::FillRect(const Rect& aRect, + const Pattern& aPattern, + const DrawOptions& aOptions) +{ + AppendCommand(FillRectCommand)(aRect, aPattern, aOptions); +} + +void +DrawTargetCaptureImpl::StrokeRect(const Rect& aRect, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) +{ + AppendCommand(StrokeRectCommand)(aRect, aPattern, aStrokeOptions, aOptions); +} + +void +DrawTargetCaptureImpl::StrokeLine(const Point& aStart, + const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) +{ + AppendCommand(StrokeLineCommand)(aStart, aEnd, aPattern, aStrokeOptions, aOptions); +} + +void +DrawTargetCaptureImpl::Stroke(const Path* aPath, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) +{ + AppendCommand(StrokeCommand)(aPath, aPattern, aStrokeOptions, aOptions); +} + +void +DrawTargetCaptureImpl::Fill(const Path* aPath, + const Pattern& aPattern, + const DrawOptions& aOptions) +{ + AppendCommand(FillCommand)(aPath, aPattern, aOptions); +} + +void +DrawTargetCaptureImpl::FillGlyphs(ScaledFont* aFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions, + const GlyphRenderingOptions* aRenderingOptions) +{ + AppendCommand(FillGlyphsCommand)(aFont, aBuffer, aPattern, aOptions, aRenderingOptions); +} + +void +DrawTargetCaptureImpl::Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions) +{ + AppendCommand(MaskCommand)(aSource, aMask, aOptions); +} + +void +DrawTargetCaptureImpl::PushClip(const Path* aPath) +{ + AppendCommand(PushClipCommand)(aPath); +} + +void +DrawTargetCaptureImpl::PushClipRect(const Rect& aRect) +{ + AppendCommand(PushClipRectCommand)(aRect); +} + +void +DrawTargetCaptureImpl::PopClip() +{ + AppendCommand(PopClipCommand)(); +} + +void +DrawTargetCaptureImpl::SetTransform(const Matrix& aTransform) +{ + AppendCommand(SetTransformCommand)(aTransform); +} + +void +DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransform) +{ + uint8_t* start = &mDrawCommandStorage.front(); + + uint8_t* current = start; + + while (current < start + mDrawCommandStorage.size()) { + reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, &aTransform); + current += *(uint32_t*)current; + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetCapture.h b/gfx/2d/DrawTargetCapture.h new file mode 100644 index 000000000..a60e07b56 --- /dev/null +++ b/gfx/2d/DrawTargetCapture.h @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETCAPTURE_H_ +#define MOZILLA_GFX_DRAWTARGETCAPTURE_H_ + +#include "2D.h" +#include <vector> + +#include "Filters.h" + +namespace mozilla { +namespace gfx { + +class DrawingCommand; + +class DrawTargetCaptureImpl : public DrawTargetCapture +{ +public: + DrawTargetCaptureImpl() + {} + + bool Init(const IntSize& aSize, DrawTarget* aRefDT); + + virtual BackendType GetBackendType() const { return mRefDT->GetBackendType(); } + virtual DrawTargetType GetType() const { return mRefDT->GetType(); } + + virtual already_AddRefed<SourceSurface> Snapshot(); + + virtual void DetachAllSnapshots(); + + virtual IntSize GetSize() { return mSize; } + + virtual void Flush() {} + virtual void DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aOptions); + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()); + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) { /* Not implemented */ } + + virtual void ClearRect(const Rect &aRect); + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()); + + virtual void CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination); + + virtual void FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()); + virtual void StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()); + virtual void StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()); + virtual void Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()); + virtual void Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()); + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr); + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()); + virtual void PushClip(const Path *aPath); + virtual void PushClipRect(const Rect &aRect); + virtual void PopClip(); + + virtual void SetTransform(const Matrix &aTransform); + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const + { + return mRefDT->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat); + } + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const + { + return mRefDT->OptimizeSourceSurface(aSurface); + } + + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const + { + return mRefDT->CreateSourceSurfaceFromNativeSurface(aSurface); + } + + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const + { + return mRefDT->CreateSimilarDrawTarget(aSize, aFormat); + } + + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const + { + return mRefDT->CreatePathBuilder(aFillRule); + } + + virtual already_AddRefed<GradientStops> + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const + { + return mRefDT->CreateGradientStops(aStops, aNumStops, aExtendMode); + } + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) + { + return mRefDT->CreateFilter(aType); + } + + void ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransform); + +protected: + ~DrawTargetCaptureImpl(); + +private: + + // This storage system was used to minimize the amount of heap allocations + // that are required while recording. It should be noted there's no + // guarantees on the alignments of DrawingCommands allocated in this array. + template<typename T> + T* AppendToCommandList() + { + size_t oldSize = mDrawCommandStorage.size(); + mDrawCommandStorage.resize(mDrawCommandStorage.size() + sizeof(T) + sizeof(uint32_t)); + uint8_t* nextDrawLocation = &mDrawCommandStorage.front() + oldSize; + *(uint32_t*)(nextDrawLocation) = sizeof(T) + sizeof(uint32_t); + return reinterpret_cast<T*>(nextDrawLocation + sizeof(uint32_t)); + } + RefPtr<DrawTarget> mRefDT; + + IntSize mSize; + + std::vector<uint8_t> mDrawCommandStorage; +}; + +} // namespace gfx + +} // namespace mozilla + + +#endif /* MOZILLA_GFX_DRAWTARGETCAPTURE_H_ */ diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp new file mode 100644 index 000000000..d9deb4c10 --- /dev/null +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -0,0 +1,1931 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <initguid.h> +#include "DrawTargetD2D1.h" +#include "FilterNodeSoftware.h" +#include "GradientStopsD2D.h" +#include "SourceSurfaceD2D1.h" +#include "RadialGradientEffectD2D1.h" + +#include "HelpersD2D.h" +#include "FilterNodeD2D1.h" +#include "ExtendInputEffectD2D1.h" +#include "Tools.h" + +using namespace std; + +// 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; +IDWriteFactory *DrawTargetD2D1::mDWriteFactory; +ID2D1Factory1* DrawTargetD2D1::mFactory = nullptr; + +ID2D1Factory1 *D2DFactory1() +{ + return DrawTargetD2D1::factory(); +} + +DrawTargetD2D1::DrawTargetD2D1() + : mPushedLayers(1) + , mUsedCommandListsSincePurge(0) + , mDidComplexBlendWithListInList(false) +{ +} + +DrawTargetD2D1::~DrawTargetD2D1() +{ + PopAllClips(); + + if (mSnapshot) { + // 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) { + // 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(); + } + + // 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); + } +} + +already_AddRefed<SourceSurface> +DrawTargetD2D1::Snapshot() +{ + 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(); +} + +// 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; + +void +DrawTargetD2D1::Flush() +{ + if ((mUsedCommandListsSincePurge >= kPushedLayersBeforePurge) && + 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; + mDC->EndDraw(); + mDC->BeginDraw(); + } else { + mDC->Flush(); + } + + // We no longer depend on any target. + for (TargetSet::iterator iter = mDependingOnTargets.begin(); + iter != mDependingOnTargets.end(); iter++) { + (*iter)->mDependentTargets.erase(this); + } + mDependingOnTargets.clear(); +} + +void +DrawTargetD2D1::DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aOptions) +{ + PrepareForDrawing(aOptions.mCompositionOp, ColorPattern(Color())); + + D2D1_RECT_F samplingBounds; + + if (aSurfOptions.mSamplingBounds == SamplingBounds::BOUNDED) { + samplingBounds = D2DRect(aSource); + } else { + samplingBounds = D2D1::RectF(0, 0, Float(aSurface->GetSize().width), Float(aSurface->GetSize().height)); + } + + Float xScale = aDest.width / aSource.width; + Float yScale = aDest.height / aSource.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 - aSource.x * xScale, aDest.y - aSource.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; + 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. + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + } + + if (bitmap && aSurfOptions.mSamplingBounds == SamplingBounds::UNBOUNDED) { + mDC->DrawBitmap(bitmap, D2DRect(aDest), aOptions.mAlpha, + D2DFilter(aSurfOptions.mSamplingFilter), D2DRect(aSource)); + } 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(Color())); +} + +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; + } + + PrepareForDrawing(aOptions.mCompositionOp, ColorPattern(Color())); + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + FilterNodeD2D1* node = static_cast<FilterNodeD2D1*>(aNode); + node->WillDraw(this); + + mDC->DrawImage(node->OutputEffect(), D2DPoint(aDestPoint), D2DRect(aSourceRect)); + + FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(Color())); +} + +void +DrawTargetD2D1::DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) +{ + MarkChanged(); + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + Matrix mat; + RefPtr<ID2D1Image> image = GetImageForSurface(aSurface, mat, ExtendMode::CLAMP); + + 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; + } + + // Step 1, create the shadow effect. + RefPtr<ID2D1Effect> shadowEffect; + HRESULT hr = mDC->CreateEffect(CLSID_D2D1Shadow, getter_AddRefs(shadowEffect)); + if (FAILED(hr) || !shadowEffect) { + gfxWarning() << "Failed to create shadow effect. Code: " << hexa(hr); + return; + } + shadowEffect->SetInput(0, image); + shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, aSigma); + D2D1_VECTOR_4F color = { aColor.r, aColor.g, aColor.b, aColor.a }; + shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color); + + D2D1_POINT_2F shadowPoint = D2DPoint(aDest + aOffset); + mDC->DrawImage(shadowEffect, &shadowPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); + + D2D1_POINT_2F imgPoint = D2DPoint(aDest); + mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); +} + +void +DrawTargetD2D1::ClearRect(const Rect &aRect) +{ + 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) +{ + 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; + } + + PrepareForDrawing(aOptions.mCompositionOp, aSource); + + // FillOpacityMask only works if the antialias mode is MODE_ALIASED + mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + if (!bitmap) { + gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces."; + return; + } + + IntSize size = IntSize::Truncate(bitmap->GetSize().width, bitmap->GetSize().height); + + Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height)); + + Rect dest = Rect(aOffset.x, aOffset.y, Float(size.width), Float(size.height)); + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aSource, aOptions.mAlpha); + 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) +{ + 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); + + 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.x += (aDestination.x - aSourceRect.x) - mat._31; + sourceRect.width -= (aDestination.x - aSourceRect.x) - mat._31; + sourceRect.y += (aDestination.y - aSourceRect.y) - mat._32; + sourceRect.height -= (aDestination.y - aSourceRect.y) - mat._32; + + RefPtr<ID2D1Bitmap> bitmap; + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + + if (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 (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) +{ + PrepareForDrawing(aOptions.mCompositionOp, aPattern); + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); + mDC->FillRectangle(D2DRect(aRect), brush); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void +DrawTargetD2D1::StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aOptions) +{ + PrepareForDrawing(aOptions.mCompositionOp, aPattern); + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); + 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) +{ + PrepareForDrawing(aOptions.mCompositionOp, aPattern); + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); + 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) +{ + if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) { + gfxDebug() << *this << ": Ignoring drawing call for incompatible path."; + return; + } + const PathD2D *d2dPath = static_cast<const PathD2D*>(aPath); + + PrepareForDrawing(aOptions.mCompositionOp, aPattern); + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); + 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) +{ + if (!aPath || aPath->GetBackendType() != BackendType::DIRECT2D1_1) { + gfxDebug() << *this << ": Ignoring drawing call for incompatible path."; + return; + } + const PathD2D *d2dPath = static_cast<const PathD2D*>(aPath); + + PrepareForDrawing(aOptions.mCompositionOp, aPattern); + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); + + mDC->FillGeometry(d2dPath->mGeometry, brush); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void +DrawTargetD2D1::FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions) +{ + if (aFont->GetType() != FontType::DWRITE) { + gfxDebug() << *this << ": Ignoring drawing call for incompatible font."; + return; + } + + ScaledFontDWrite *font = static_cast<ScaledFontDWrite*>(aFont); + + IDWriteRenderingParams *params = nullptr; + if (aRenderingOptions) { + if (aRenderingOptions->GetType() != FontType::DWRITE) { + gfxDebug() << *this << ": Ignoring incompatible GlyphRenderingOptions."; + // This should never happen. + MOZ_ASSERT(false); + } else { + params = static_cast<const GlyphRenderingOptionsDWrite*>(aRenderingOptions)->mParams; + } + } + + AntialiasMode aaMode = font->GetDefaultAAMode(); + + if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { + aaMode = aOptions.mAntialiasMode; + } + + PrepareForDrawing(aOptions.mCompositionOp, aPattern); + + 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) { + mDC->SetTextRenderingParams(params); + mTextRenderingParams = params; + } + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha); + + 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 (needsRepushedLayers) { + PopClipsFromDC(mDC); + + if (!mTransform.IsRectilinear()) { + mDC->PopLayer(); + } + } + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void +DrawTargetD2D1::Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions) +{ + PrepareForDrawing(aOptions.mCompositionOp, aSource); + + RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions.mAlpha); + RefPtr<ID2D1Brush> mask = CreateBrushForPattern(aMask, 1.0f); + 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) +{ + if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) { + gfxDebug() << *this << ": Ignoring clipping call for incompatible path."; + return; + } + + RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(aPath)); + + PushClipGeometry(pathD2D->GetGeometry(), D2DMatrix(mTransform)); +} + +void +DrawTargetD2D1::PushClipRect(const Rect &aRect) +{ + 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) +{ + // 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() +{ + 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(); +} + +void +DrawTargetD2D1::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds, + bool aCopyBackground) +{ + 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<ID2D1BitmapBrush> mask; + + Matrix maskTransform = aMaskTransform; + + RefPtr<ID2D1PathGeometry> clip; + if (aMask) { + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + RefPtr<ID2D1Image> image = GetImageForSurface(aMask, maskTransform, ExtendMode::CLAMP); + + // The mask is given in user space. Our layer will apply it in device space. + maskTransform = maskTransform * mTransform; + + if (image) { + RefPtr<ID2D1Bitmap> bitmap; + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + + mDC->CreateBitmapBrush(bitmap, D2D1::BitmapBrushProperties(), D2D1::BrushProperties(1.0f, D2DMatrix(maskTransform)), getter_AddRefs(mask)); + MOZ_ASSERT(bitmap); // This should always be true since it was created for a surface. + + 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() +{ + MOZ_ASSERT(CurrentLayer().mPushedClips.size() == 0); + + 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); + + mDC->PopLayer(); +} + +already_AddRefed<SourceSurface> +DrawTargetD2D1::CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const +{ + RefPtr<ID2D1Bitmap1> bitmap; + + HRESULT hr = mDC->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(), mDC, 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(); +} + +already_AddRefed<PathBuilder> +DrawTargetD2D1::CreatePathBuilder(FillRule aFillRule) const +{ + RefPtr<ID2D1PathGeometry> path; + HRESULT hr = factory()->CreatePathGeometry(getter_AddRefs(path)); + + if (FAILED(hr)) { + gfxWarning() << *this << ": Failed to create Direct2D Path Geometry. Code: " << hexa(hr); + return nullptr; + } + + RefPtr<ID2D1GeometrySink> sink; + hr = path->Open(getter_AddRefs(sink)); + if (FAILED(hr)) { + gfxWarning() << *this << ": 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); +} + +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<ID2D1GradientStopCollection> stopCollection; + + HRESULT hr = + mDC->CreateGradientStopCollection(stops, aNumStops, + D2D1_GAMMA_2_2, D2DExtend(aExtendMode, Axis::BOTH), + getter_AddRefs(stopCollection)); + delete [] stops; + + if (FAILED(hr)) { + gfxWarning() << *this << ": Failed to create GradientStopCollection. Code: " << hexa(hr); + return nullptr; + } + + return MakeAndAddRef<GradientStopsD2D>(stopCollection, Factory::GetDirect3D11Device()); +} + +already_AddRefed<FilterNode> +DrawTargetD2D1::CreateFilter(FilterType aType) +{ + return FilterNodeD2D1::Create(mDC, aType); +} + +void +DrawTargetD2D1::GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices, + uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) +{ + MOZ_ASSERT(aScaledFont->GetType() == FontType::DWRITE); + + aScaledFont->GetGlyphDesignMetrics(aGlyphIndices, aNumGlyphs, aGlyphMetrics); + + // GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to + // account for antialiasing. + for (uint32_t i = 0; i < aNumGlyphs; i++) { + if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) { + aGlyphMetrics[i].mWidth += 2.0f; + aGlyphMetrics[i].mXBearing -= 1.0f; + } + } +} + +bool +DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat) +{ + HRESULT hr; + + ID2D1Device* device = Factory::GetD2D1Device(); + if (!device) { + gfxCriticalNote << "[D2D1.1] Failed to obtain a device for DrawTargetD2D1::Init(ID3D11Texture2D*, SurfaceFormat)."; + return false; + } + + hr = device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, getter_AddRefs(mDC)); + + if (FAILED(hr)) { + gfxCriticalError() <<"[D2D1.1] 1Failed to create a DeviceContext, code: " << hexa(hr) << " format " << (int)aFormat; + return false; + } + + RefPtr<IDXGISurface> dxgiSurface; + aTexture->QueryInterface(__uuidof(IDXGISurface), + (void**)((IDXGISurface**)getter_AddRefs(dxgiSurface))); + if (!dxgiSurface) { + gfxCriticalError() <<"[D2D1.1] Failed to obtain a DXGI surface."; + return false; + } + + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(aFormat); + props.colorContext = nullptr; + props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + hr = mDC->CreateBitmapFromDxgiSurface(dxgiSurface, props, (ID2D1Bitmap1**)getter_AddRefs(mBitmap)); + + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1.1] CreateBitmapFromDxgiSurface failure Code: " << hexa(hr) << " format " << (int)aFormat; + return false; + } + + mFormat = aFormat; + D3D11_TEXTURE2D_DESC desc; + aTexture->GetDesc(&desc); + mSize.width = desc.Width; + mSize.height = desc.Height; + + // This single solid color brush system is not very 'threadsafe', however, + // issueing multiple drawing commands simultaneously to a single drawtarget + // from multiple threads is unexpected since there's no way to guarantee + // ordering in that situation anyway. + hr = mDC->CreateSolidColorBrush(D2D1::ColorF(0, 0), getter_AddRefs(mSolidColorBrush)); + + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1.1] Failure creating solid color brush (I1)."; + return false; + } + + mDC->SetTarget(CurrentTarget()); + + mDC->BeginDraw(); + + CurrentLayer().mIsOpaque = aFormat == SurfaceFormat::B8G8R8X8; + + return true; +} + +bool +DrawTargetD2D1::Init(const IntSize &aSize, SurfaceFormat aFormat) +{ + HRESULT hr; + + ID2D1Device* device = Factory::GetD2D1Device(); + if (!device) { + gfxCriticalNote << "[D2D1.1] Failed to obtain a device for DrawTargetD2D1::Init(IntSize, SurfaceFormat)."; + 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)aFormat; + return false; + } + + if (mDC->GetMaximumBitmapSize() < UINT32(aSize.width) || + mDC->GetMaximumBitmapSize() < UINT32(aSize.height)) { + // This is 'ok', so don't assert + gfxCriticalNote << "[D2D1.1] Attempt to use unsupported surface size " << aSize; + return false; + } + + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(aFormat); + props.colorContext = nullptr; + props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + hr = mDC->CreateBitmap(D2DIntSize(aSize), nullptr, 0, props, (ID2D1Bitmap1**)getter_AddRefs(mBitmap)); + + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1.1] 3CreateBitmap failure " << aSize << " Code: " << hexa(hr) << " format " << (int)aFormat; + 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 = aFormat == SurfaceFormat::B8G8R8X8; + + mDC->Clear(); + + mFormat = aFormat; + mSize = aSize; + + return true; +} + +/** + * Private helpers. + */ +uint32_t +DrawTargetD2D1::GetByteSize() const +{ + return mSize.width * mSize.height * BytesPerPixel(mFormat); +} + +ID2D1Factory1* +DrawTargetD2D1::factory() +{ + if (mFactory) { + return mFactory; + } + + 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; + } + + hr = factory->QueryInterface((ID2D1Factory1**)&mFactory); + if (FAILED(hr) || !mFactory) { + return nullptr; + } + + ExtendInputEffectD2D1::Register(mFactory); + RadialGradientEffectD2D1::Register(mFactory); + + return mFactory; +} + +IDWriteFactory* +DrawTargetD2D1::GetDWriteFactory() +{ + if (mDWriteFactory) { + return mDWriteFactory; + } + + decltype(DWriteCreateFactory)* createDWriteFactory; + HMODULE dwriteModule = LoadLibraryW(L"dwrite.dll"); + 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; +} + +void +DrawTargetD2D1::CleanupD2D() +{ + if (mFactory) { + RadialGradientEffectD2D1::Unregister(mFactory); + ExtendInputEffectD2D1::Unregister(mFactory); + mFactory->Release(); + mFactory = nullptr; + } +} + +void +DrawTargetD2D1::MarkChanged() +{ + if (mSnapshot) { + 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); + } + } + 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)->Flush(); + } + // 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); + return patternSupported && !CurrentLayer().mIsOpaque && D2DSupportsCompositeMode(aOp) && + IsOperatorBoundByMask(aOp) && aClipIsComplex; +} + +void +DrawTargetD2D1::PrepareForDrawing(CompositionOp aOp, const Pattern &aPattern) +{ + MarkChanged(); + + bool patternSupported = IsPatternSupportedByD2D(aPattern); + + if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) { + // It's important to do this before FlushTransformToDC! As this will cause + // the transform to become dirty. + PushAllClips(); + + FlushTransformToDC(); + + if (aOp != CompositionOp::OP_OVER) + mDC->SetPrimitiveBlend(D2DPrimitiveBlendMode(aOp)); + + return; + } + + 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(); +} + +void +DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern) +{ + bool patternSupported = IsPatternSupportedByD2D(aPattern); + + 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; + } + + // We don't need to preserve the current content of this layer as the output + // of the blend effect should completely replace it. + RefPtr<ID2D1Image> tmpImage = GetImageForLayerContent(false); + if (!tmpImage) { + return; + } + + blendEffect->SetInput(0, tmpImage); + blendEffect->SetInput(1, source); + blendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp)); + + mDC->DrawImage(blendEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY); + + // This may seem a little counter intuitive. If this is false, we go through the regular + // codepaths and set it to true. When this was true, GetImageForLayerContent will return + // a bitmap for the current command list and we will no longer have a complex blend + // with a list for tmpImage. Therefore we can set it to false again. + mDidComplexBlendWithListInList = !mDidComplexBlendWithListInList; + + return; + } + + const RadialGradientPattern *pat = static_cast<const RadialGradientPattern*>(&aPattern); + if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) { + // Draw nothing! + return; + } + + if (!pat->mStops) { + // 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; + } + + 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) +{ + 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 = max(aRect1.left, aRect2.left); + result.top = max(aRect1.top, aRect2.top); + result.right = min(aRect1.right, aRect2.right); + result.bottom = min(aRect1.bottom, aRect2.bottom); + + result.right = max(result.right, result.left); + result.bottom = max(result.bottom, result.top); + + return result; +} + +bool +DrawTargetD2D1::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned) +{ + if (!CurrentLayer().mPushedClips.size()) { + return false; + } + + aIsPixelAligned = true; + aClipRect = D2D1::RectF(0, 0, mSize.width, mSize.height); + 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; +} + +already_AddRefed<ID2D1Image> +DrawTargetD2D1::GetImageForLayerContent(bool aShouldPreserveContent) +{ + PopAllClips(); + + if (!CurrentLayer().mCurrentList) { + RefPtr<ID2D1Bitmap> tmpBitmap; + HRESULT hr = mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), getter_AddRefs(tmpBitmap)); + if (FAILED(hr)) { + gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(mSize))) << "[D2D1.1] 6CreateBitmap failure " << mSize << " 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(); + + tmpBitmap->CopyFromBitmap(nullptr, mBitmap, nullptr); + return tmpBitmap.forget(); + } else { + RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList; + mDC->CreateCommandList(getter_AddRefs(CurrentLayer().mCurrentList)); + mDC->SetTarget(CurrentTarget()); + list->Close(); + + RefPtr<ID2D1Bitmap1> tmpBitmap; + if (mDidComplexBlendWithListInList) { + D2D1_BITMAP_PROPERTIES1 props = + D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED)); + mDC->CreateBitmap(mBitmap->GetPixelSize(), nullptr, 0, &props, getter_AddRefs(tmpBitmap)); + mDC->SetTransform(D2D1::IdentityMatrix()); + mDC->SetTarget(tmpBitmap); + mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY); + mDC->SetTarget(CurrentTarget()); + } + + DCCommandSink sink(mDC); + + if (aShouldPreserveContent) { + list->Stream(&sink); + PushAllClips(); + } + + if (mDidComplexBlendWithListInList) { + 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, Float aAlpha) +{ + if (!IsPatternSupportedByD2D(aPattern)) { + return GetSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f)); + } + + if (aPattern.GetType() == PatternType::COLOR) { + Color color = static_cast<const ColorPattern*>(&aPattern)->mColor; + return GetSolidColorBrush(D2D1::ColorF(color.r, color.g, color.b, color.a * aAlpha)); + } + if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { + RefPtr<ID2D1LinearGradientBrush> gradBrush; + const LinearGradientPattern *pat = + static_cast<const LinearGradientPattern*>(&aPattern); + + GradientStopsD2D *stops = static_cast<GradientStopsD2D*>(pat->mStops.get()); + + if (!stops) { + gfxDebug() << "No stops specified for gradient pattern."; + return CreateTransparentBlackBrush(); + } + + if (pat->mBegin == pat->mEnd) { + uint32_t stopCount = stops->mStopCollection->GetGradientStopCount(); + vector<D2D1_GRADIENT_STOP> d2dStops(stopCount); + stops->mStopCollection->GetGradientStops(&d2dStops.front(), stopCount); + d2dStops.back().color.a *= aAlpha; + return GetSolidColorBrush(d2dStops.back().color); + } + + mDC->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2DPoint(pat->mBegin), + D2DPoint(pat->mEnd)), + D2D1::BrushProperties(aAlpha, 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); + + GradientStopsD2D *stops = static_cast<GradientStopsD2D*>(pat->mStops.get()); + + if (!stops) { + gfxDebug() << "No stops specified for gradient pattern."; + return CreateTransparentBlackBrush(); + } + + // 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(aAlpha, 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<ID2D1Image> image = GetImageForSurface(pat->mSurface, mat, pat->mExtendMode, !pat->mSamplingRect.IsEmpty() ? &pat->mSamplingRect : nullptr); + + if (pat->mSurface->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; + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + if (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)); + 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 (!image) { + return CreateTransparentBlackBrush(); + } + + if (pat->mSamplingRect.IsEmpty()) { + RefPtr<ID2D1Bitmap> bitmap; + image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + if (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(aAlpha, 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 (pat->mSurface->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(aAlpha, 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) +{ + RefPtr<ID2D1Image> image; + + switch (aSurface->GetType()) { + case SurfaceType::D2D1_1_IMAGE: + { + SourceSurfaceD2D1 *surf = static_cast<SourceSurfaceD2D1*>(aSurface); + image = surf->GetImage(); + AddDependencyOnSource(surf); + } + break; + default: + { + RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface(); + if (!dataSurf) { + gfxWarning() << "Invalid surface type."; + return nullptr; + } + return CreatePartialBitmapForSurface(dataSurf, mTransform, 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<DataSourceSurface> data = aSurface->GetDataSurface(); + + RefPtr<ID2D1Bitmap1> bitmap; + { + DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!map.IsMapped())) { + return nullptr; + } + + HRESULT hr = mDC->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(), mDC, 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); +} + +} +} diff --git a/gfx/2d/DrawTargetD2D1.h b/gfx/2d/DrawTargetD2D1.h new file mode 100644 index 000000000..624fb58cc --- /dev/null +++ b/gfx/2d/DrawTargetD2D1.h @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETD2D1_H_ +#define MOZILLA_GFX_DRAWTARGETD2D1_H_ + +#include "2D.h" +#include <d3d11.h> +#include <d2d1_1.h> +#include "PathD2D.h" +#include "HelpersD2D.h" + +#include <vector> +#include <sstream> + +#include <unordered_set> + +struct IDWriteFactory; + +namespace mozilla { +namespace gfx { + +class SourceSurfaceD2D1; + +const int32_t kLayerCacheSize1 = 5; + +class DrawTargetD2D1 : public DrawTarget +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D1, override) + DrawTargetD2D1(); + virtual ~DrawTargetD2D1(); + + virtual DrawTargetType GetType() const override { return DrawTargetType::HARDWARE_RASTER; } + virtual BackendType GetBackendType() const override { return BackendType::DIRECT2D1_1; } + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual IntSize GetSize() override { return mSize; } + + virtual void Flush() override; + virtual void DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aOptions) override; + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) override; + virtual void ClearRect(const Rect &aRect) override; + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) override; + + virtual void FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr) override; + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void PushClip(const Path *aPath) override; + virtual void PushClipRect(const Rect &aRect) override; + virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override; + + virtual void PopClip() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PopLayer() override; + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override; + + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override { return nullptr; } + + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override; + + virtual already_AddRefed<GradientStops> + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + + virtual bool SupportsRegionClipping() const override { return false; } + virtual bool IsCurrentGroupOpaque() override { return CurrentLayer().mIsOpaque; } + + virtual void *GetNativeSurface(NativeSurfaceType aType) override { return nullptr; } + + virtual void DetachAllSnapshots() override { MarkChanged(); } + + virtual void GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices, + uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) override; + + bool Init(const IntSize &aSize, SurfaceFormat aFormat); + bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat); + uint32_t GetByteSize() const; + + // This function will get an image for a surface, it may adjust the source + // transform for any transformation of the resulting image relative to the + // oritingal SourceSurface. + already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform, + ExtendMode aExtendMode, const IntRect* aSourceRect = nullptr); + + already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface *aSurface, ExtendMode aExtendMode) { + Matrix mat; + return GetImageForSurface(aSurface, mat, aExtendMode, nullptr); + } + + static ID2D1Factory1 *factory(); + static void CleanupD2D(); + static IDWriteFactory *GetDWriteFactory(); + + operator std::string() const { + std::stringstream stream; + stream << "DrawTargetD2D 1.1 (" << this << ")"; + return stream.str(); + } + + static uint32_t GetMaxSurfaceSize() { + return D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + } + + static uint64_t mVRAMUsageDT; + static uint64_t mVRAMUsageSS; + +private: + friend class SourceSurfaceD2D1; + + typedef std::unordered_set<DrawTargetD2D1*> TargetSet; + + // This function will mark the surface as changing, and make sure any + // copy-on-write snapshots are notified. + void MarkChanged(); + bool ShouldClipTemporarySurfaceDrawing(CompositionOp aOp, const Pattern& aPattern, bool aClipIsComplex); + void PrepareForDrawing(CompositionOp aOp, const Pattern &aPattern); + void FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern); + void FlushTransformToDC() { + if (mTransformDirty) { + mDC->SetTransform(D2DMatrix(mTransform)); + mTransformDirty = false; + } + } + void AddDependencyOnSource(SourceSurfaceD2D1* aSource); + + // Must be called with all clips popped and an identity matrix set. + already_AddRefed<ID2D1Image> GetImageForLayerContent(bool aShouldPreserveContent = true); + + ID2D1Image* CurrentTarget() + { + if (CurrentLayer().mCurrentList) { + return CurrentLayer().mCurrentList; + } + return mBitmap; + } + + // This returns the clipped geometry, in addition it returns aClipBounds which + // represents the intersection of all pixel-aligned rectangular clips that + // are currently set. The returned clipped geometry must be clipped by these + // bounds to correctly reflect the total clip. This is in device space and + // only for clips applied to the -current layer-. + already_AddRefed<ID2D1Geometry> GetClippedGeometry(IntRect *aClipBounds); + + already_AddRefed<ID2D1Geometry> GetInverseClippedGeometry(); + + // This gives the device space clip rect applied to the -current layer-. + bool GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned); + + void PopAllClips(); + void PushAllClips(); + void PushClipsToDC(ID2D1DeviceContext *aDC, bool aForceIgnoreAlpha = false, const D2D1_RECT_F& aMaxRect = D2D1::InfiniteRect()); + void PopClipsFromDC(ID2D1DeviceContext *aDC); + + already_AddRefed<ID2D1Brush> CreateTransparentBlackBrush(); + already_AddRefed<ID2D1SolidColorBrush> GetSolidColorBrush(const D2D_COLOR_F& aColor); + already_AddRefed<ID2D1Brush> CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f); + + void PushClipGeometry(ID2D1Geometry* aGeometry, const D2D1_MATRIX_3X2_F& aTransform, bool aPixelAligned = false); + + void PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform, + bool aPixelAligned = false, bool aForceIgnoreAlpha = false, + const D2D1_RECT_F& aLayerRect = D2D1::InfiniteRect()); + + IntSize mSize; + + RefPtr<ID3D11Device> mDevice; + RefPtr<ID3D11Texture2D> mTexture; + RefPtr<ID2D1Geometry> mCurrentClippedGeometry; + // This is only valid if mCurrentClippedGeometry is non-null. And will + // only be the intersection of all pixel-aligned retangular clips. This is in + // device space. + IntRect mCurrentClipBounds; + mutable RefPtr<ID2D1DeviceContext> mDC; + RefPtr<ID2D1Bitmap1> mBitmap; + RefPtr<ID2D1CommandList> mCommandList; + + RefPtr<ID2D1SolidColorBrush> mSolidColorBrush; + + // We store this to prevent excessive SetTextRenderingParams calls. + RefPtr<IDWriteRenderingParams> mTextRenderingParams; + + // List of pushed clips. + struct PushedClip + { + D2D1_RECT_F mBounds; + // If mGeometry is non-null, the mTransform member will be used. + D2D1_MATRIX_3X2_F mTransform; + RefPtr<ID2D1Geometry> mGeometry; + // Indicates if mBounds, and when non-null, mGeometry with mTransform + // applied, are pixel-aligned. + bool mIsPixelAligned; + }; + + // List of pushed layers. + struct PushedLayer + { + PushedLayer() : mClipsArePushed(false), mIsOpaque(false), mOldPermitSubpixelAA(false) {} + + std::vector<PushedClip> mPushedClips; + RefPtr<ID2D1CommandList> mCurrentList; + // True if the current clip stack is pushed to the CurrentTarget(). + bool mClipsArePushed; + bool mIsOpaque; + bool mOldPermitSubpixelAA; + }; + std::vector<PushedLayer> mPushedLayers; + PushedLayer& CurrentLayer() + { + return mPushedLayers.back(); + } + + // The latest snapshot of this surface. This needs to be told when this + // target is modified. We keep it alive as a cache. + RefPtr<SourceSurfaceD2D1> mSnapshot; + // A list of targets we need to flush when we're modified. + TargetSet mDependentTargets; + // A list of targets which have this object in their mDependentTargets set + TargetSet mDependingOnTargets; + + uint32_t mUsedCommandListsSincePurge; + // When a BlendEffect has been drawn to a command list, and that command list is + // subsequently used -again- as an input to a blend effect for a command list, + // this causes an infinite recursion inside D2D as it tries to resolve the bounds. + // If we resolve the current command list before this happens + // we can avoid the subsequent hang. (See bug 1293586) + bool mDidComplexBlendWithListInList; + + static ID2D1Factory1 *mFactory; + static IDWriteFactory *mDWriteFactory; +}; + +} +} + +#endif /* MOZILLA_GFX_DRAWTARGETD2D_H_ */ diff --git a/gfx/2d/DrawTargetDual.cpp b/gfx/2d/DrawTargetDual.cpp new file mode 100644 index 000000000..87714f123 --- /dev/null +++ b/gfx/2d/DrawTargetDual.cpp @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetDual.h" +#include "Tools.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +class DualSurface +{ +public: + inline explicit DualSurface(SourceSurface *aSurface) + { + if (!aSurface) { + mA = mB = nullptr; + return; + } + + if (aSurface->GetType() != SurfaceType::DUAL_DT) { + mA = mB = aSurface; + return; + } + + SourceSurfaceDual *ssDual = + static_cast<SourceSurfaceDual*>(aSurface); + mA = ssDual->mA; + mB = ssDual->mB; + } + + SourceSurface *mA; + SourceSurface *mB; +}; + +/* This only needs to split patterns up for SurfacePatterns. Only in that + * case can we be dealing with a 'dual' source (SourceSurfaceDual) and do + * we need to pass separate patterns into our destination DrawTargets. + */ +class DualPattern +{ +public: + inline explicit DualPattern(const Pattern &aPattern) + : mPatternsInitialized(false) + { + if (aPattern.GetType() != PatternType::SURFACE) { + mA = mB = &aPattern; + return; + } + + const SurfacePattern *surfPat = + static_cast<const SurfacePattern*>(&aPattern); + + if (surfPat->mSurface->GetType() != SurfaceType::DUAL_DT) { + mA = mB = &aPattern; + return; + } + + const SourceSurfaceDual *ssDual = + static_cast<const SourceSurfaceDual*>(surfPat->mSurface.get()); + mA = new (mSurfPatA.addr()) SurfacePattern(ssDual->mA, surfPat->mExtendMode, + surfPat->mMatrix, + surfPat->mSamplingFilter); + mB = new (mSurfPatB.addr()) SurfacePattern(ssDual->mB, surfPat->mExtendMode, + surfPat->mMatrix, + surfPat->mSamplingFilter); + mPatternsInitialized = true; + } + + inline ~DualPattern() + { + if (mPatternsInitialized) { + mA->~Pattern(); + mB->~Pattern(); + } + } + + ClassStorage<SurfacePattern> mSurfPatA; + ClassStorage<SurfacePattern> mSurfPatB; + + const Pattern *mA; + const Pattern *mB; + + bool mPatternsInitialized; +}; + +void +DrawTargetDual::DetachAllSnapshots() +{ + mA->DetachAllSnapshots(); + mB->DetachAllSnapshots(); +} + +void +DrawTargetDual::DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions) +{ + DualSurface surface(aSurface); + mA->DrawSurface(surface.mA, aDest, aSource, aSurfOptions, aOptions); + mB->DrawSurface(surface.mB, aDest, aSource, aSurfOptions, aOptions); +} + +void +DrawTargetDual::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, + const Color &aColor, const Point &aOffset, + Float aSigma, CompositionOp aOp) +{ + DualSurface surface(aSurface); + mA->DrawSurfaceWithShadow(surface.mA, aDest, aColor, aOffset, aSigma, aOp); + mB->DrawSurfaceWithShadow(surface.mB, aDest, aColor, aOffset, aSigma, aOp); +} + +void +DrawTargetDual::MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions) +{ + DualPattern source(aSource); + DualSurface mask(aMask); + mA->MaskSurface(*source.mA, mask.mA, aOffset, aOptions); + mB->MaskSurface(*source.mB, mask.mB, aOffset, aOptions); +} + +void +DrawTargetDual::CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect, + const IntPoint &aDestination) +{ + DualSurface surface(aSurface); + mA->CopySurface(surface.mA, aSourceRect, aDestination); + mB->CopySurface(surface.mB, aSourceRect, aDestination); +} + +void +DrawTargetDual::FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) +{ + DualPattern pattern(aPattern); + mA->FillRect(aRect, *pattern.mA, aOptions); + mB->FillRect(aRect, *pattern.mB, aOptions); +} + +void +DrawTargetDual::StrokeRect(const Rect &aRect, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) +{ + DualPattern pattern(aPattern); + mA->StrokeRect(aRect, *pattern.mA, aStrokeOptions, aOptions); + mB->StrokeRect(aRect, *pattern.mB, aStrokeOptions, aOptions); +} + +void +DrawTargetDual::StrokeLine(const Point &aStart, const Point &aEnd, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) +{ + DualPattern pattern(aPattern); + mA->StrokeLine(aStart, aEnd, *pattern.mA, aStrokeOptions, aOptions); + mB->StrokeLine(aStart, aEnd, *pattern.mB, aStrokeOptions, aOptions); +} + +void +DrawTargetDual::Stroke(const Path *aPath, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) +{ + DualPattern pattern(aPattern); + mA->Stroke(aPath, *pattern.mA, aStrokeOptions, aOptions); + mB->Stroke(aPath, *pattern.mB, aStrokeOptions, aOptions); +} + +void +DrawTargetDual::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aOptions) +{ + DualPattern pattern(aPattern); + mA->Fill(aPath, *pattern.mA, aOptions); + mB->Fill(aPath, *pattern.mB, aOptions); +} + +void +DrawTargetDual::FillGlyphs(ScaledFont *aScaledFont, const GlyphBuffer &aBuffer, + const Pattern &aPattern, const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions) +{ + DualPattern pattern(aPattern); + mA->FillGlyphs(aScaledFont, aBuffer, *pattern.mA, aOptions, aRenderingOptions); + mB->FillGlyphs(aScaledFont, aBuffer, *pattern.mB, aOptions, aRenderingOptions); +} + +void +DrawTargetDual::Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions) +{ + DualPattern source(aSource); + DualPattern mask(aMask); + mA->Mask(*source.mA, *mask.mA, aOptions); + mB->Mask(*source.mB, *mask.mB, aOptions); +} + +void +DrawTargetDual::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds, + bool aCopyBackground) +{ + DualSurface mask(aMask); + mA->PushLayer(aOpaque, aOpacity, mask.mA, aMaskTransform, aBounds, aCopyBackground); + mB->PushLayer(aOpaque, aOpacity, mask.mB, aMaskTransform, aBounds, aCopyBackground); +} + +already_AddRefed<DrawTarget> +DrawTargetDual::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const +{ + RefPtr<DrawTarget> dtA = mA->CreateSimilarDrawTarget(aSize, aFormat); + RefPtr<DrawTarget> dtB = mB->CreateSimilarDrawTarget(aSize, aFormat); + + if (!dtA || !dtB) { + gfxWarning() << "Failure to allocate a similar DrawTargetDual. Size: " << aSize; + return nullptr; + } + + return MakeAndAddRef<DrawTargetDual>(dtA, dtB); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetDual.h b/gfx/2d/DrawTargetDual.h new file mode 100644 index 000000000..190346e44 --- /dev/null +++ b/gfx/2d/DrawTargetDual.h @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETDUAL_H_ +#define MOZILLA_GFX_DRAWTARGETDUAL_H_ + +#include <vector> +#include <sstream> + +#include "SourceSurfaceDual.h" + +#include "2D.h" +#include "Filters.h" + +namespace mozilla { +namespace gfx { + +#define FORWARD_FUNCTION(funcName) \ + virtual void funcName() override { mA->funcName(); mB->funcName(); } +#define FORWARD_FUNCTION1(funcName, var1Type, var1Name) \ + virtual void funcName(var1Type var1Name) override { mA->funcName(var1Name); mB->funcName(var1Name); } + +/* This is a special type of DrawTarget. It duplicates all drawing calls + * accross two drawtargets. An exception to this is when a snapshot of another + * dual DrawTarget is used as the source for any surface data. In this case + * the snapshot of the first source DrawTarget is used as a source for the call + * to the first destination DrawTarget (mA) and the snapshot of the second + * source DrawTarget is used at the source for the second destination + * DrawTarget (mB). This class facilitates black-background/white-background + * drawing for per-component alpha extraction for backends which do not support + * native component alpha. + */ +class DrawTargetDual : public DrawTarget +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetDual, override) + DrawTargetDual(DrawTarget *aA, DrawTarget *aB) + : mA(aA) + , mB(aB) + { + mFormat = aA->GetFormat(); + } + + virtual DrawTargetType GetType() const override { return mA->GetType(); } + virtual BackendType GetBackendType() const override { return mA->GetBackendType(); } + virtual already_AddRefed<SourceSurface> Snapshot() override { + return MakeAndAddRef<SourceSurfaceDual>(mA, mB); + } + virtual IntSize GetSize() override { return mA->GetSize(); } + + virtual void DetachAllSnapshots() override; + + FORWARD_FUNCTION(Flush) + FORWARD_FUNCTION1(PushClip, const Path *, aPath) + FORWARD_FUNCTION1(PushClipRect, const Rect &, aRect) + FORWARD_FUNCTION(PopClip) + FORWARD_FUNCTION(PopLayer) + FORWARD_FUNCTION1(ClearRect, const Rect &, aRect) + + virtual void SetTransform(const Matrix &aTransform) override { + mTransform = aTransform; + mA->SetTransform(aTransform); + mB->SetTransform(aTransform); + } + + virtual void DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect & aSource, + const DrawSurfaceOptions &aSurfOptions, const DrawOptions &aOptions) override; + + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()) override + { + mA->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions); + mB->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions); + } + + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, + const Color &aColor, const Point &aOffset, + Float aSigma, CompositionOp aOp) override; + + virtual void CopySurface(SourceSurface *aSurface, const IntRect &aSourceRect, + const IntPoint &aDestination) override; + + virtual void FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) override; + + virtual void StrokeRect(const Rect &aRect, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) override; + + virtual void StrokeLine(const Point &aStart, const Point &aEnd, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) override; + + virtual void Stroke(const Path *aPath, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) override; + + virtual void Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aOptions) override; + + virtual void FillGlyphs(ScaledFont *aScaledFont, const GlyphBuffer &aBuffer, + const Pattern &aPattern, const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions) override; + + virtual void Mask(const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions) override; + + virtual void PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const override + { + return mA->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat); + } + + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override + { + return mA->OptimizeSourceSurface(aSurface); + } + + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override + { + return mA->CreateSourceSurfaceFromNativeSurface(aSurface); + } + + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override + { + return mA->CreatePathBuilder(aFillRule); + } + + virtual already_AddRefed<GradientStops> + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override + { + return mA->CreateGradientStops(aStops, aNumStops, aExtendMode); + } + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override + { + return mA->CreateFilter(aType); + } + + virtual void *GetNativeSurface(NativeSurfaceType aType) override + { + return nullptr; + } + + virtual bool IsDualDrawTarget() const override + { + return true; + } + + virtual bool IsCurrentGroupOpaque() override { return mA->IsCurrentGroupOpaque(); } + +private: + RefPtr<DrawTarget> mA; + RefPtr<DrawTarget> mB; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWTARGETDUAL_H_ */ diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp new file mode 100644 index 000000000..b8eda90fe --- /dev/null +++ b/gfx/2d/DrawTargetRecording.cpp @@ -0,0 +1,737 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetRecording.h" +#include "PathRecording.h" +#include <stdio.h> + +#include "Logging.h" +#include "Tools.h" +#include "Filters.h" +#include "mozilla/UniquePtr.h" +#include "RecordingTypes.h" + +namespace mozilla { +namespace gfx { + +struct RecordingSourceSurfaceUserData +{ + void *refPtr; + RefPtr<DrawEventRecorderPrivate> recorder; +}; + +void RecordingSourceSurfaceUserDataFunc(void *aUserData) +{ + RecordingSourceSurfaceUserData *userData = + static_cast<RecordingSourceSurfaceUserData*>(aUserData); + + userData->recorder->RemoveStoredObject(userData->refPtr); + userData->recorder->RecordEvent( + RecordedSourceSurfaceDestruction(userData->refPtr)); + + delete userData; +} + +static void +StoreSourceSurface(DrawEventRecorderPrivate *aRecorder, SourceSurface *aSurface, + DataSourceSurface *aDataSurf, const char *reason) +{ + if (!aDataSurf) { + gfxWarning() << "Recording failed to record SourceSurface for " << reason; + // Insert a bogus source surface. + int32_t stride = aSurface->GetSize().width * BytesPerPixel(aSurface->GetFormat()); + UniquePtr<uint8_t[]> sourceData(new uint8_t[stride * aSurface->GetSize().height]()); + aRecorder->RecordEvent( + RecordedSourceSurfaceCreation(aSurface, sourceData.get(), stride, + aSurface->GetSize(), aSurface->GetFormat())); + } else { + DataSourceSurface::ScopedMap map(aDataSurf, DataSourceSurface::READ); + aRecorder->RecordEvent( + RecordedSourceSurfaceCreation(aSurface, map.GetData(), map.GetStride(), + aDataSurf->GetSize(), aDataSurf->GetFormat())); + } +} + +static void +EnsureSurfaceStored(DrawEventRecorderPrivate *aRecorder, SourceSurface *aSurface, + const char *reason) +{ + if (aRecorder->HasStoredObject(aSurface)) { + return; + } + + RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface(); + StoreSourceSurface(aRecorder, aSurface, dataSurf, reason); + aRecorder->AddStoredObject(aSurface); + + RecordingSourceSurfaceUserData *userData = new RecordingSourceSurfaceUserData; + userData->refPtr = aSurface; + userData->recorder = aRecorder; + aSurface->AddUserData(reinterpret_cast<UserDataKey*>(aRecorder), + userData, &RecordingSourceSurfaceUserDataFunc); + return; +} + +class SourceSurfaceRecording : public SourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceRecording) + SourceSurfaceRecording(SourceSurface *aFinalSurface, DrawEventRecorderPrivate *aRecorder) + : mFinalSurface(aFinalSurface), mRecorder(aRecorder) + { + mRecorder->AddStoredObject(this); + } + + ~SourceSurfaceRecording() + { + mRecorder->RemoveStoredObject(this); + mRecorder->RecordEvent(RecordedSourceSurfaceDestruction(this)); + } + + virtual SurfaceType GetType() const { return SurfaceType::RECORDING; } + virtual IntSize GetSize() const { return mFinalSurface->GetSize(); } + virtual SurfaceFormat GetFormat() const { return mFinalSurface->GetFormat(); } + virtual already_AddRefed<DataSourceSurface> GetDataSurface() { return mFinalSurface->GetDataSurface(); } + + RefPtr<SourceSurface> mFinalSurface; + RefPtr<DrawEventRecorderPrivate> mRecorder; +}; + +class GradientStopsRecording : public GradientStops +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsRecording) + GradientStopsRecording(GradientStops *aFinalGradientStops, DrawEventRecorderPrivate *aRecorder) + : mFinalGradientStops(aFinalGradientStops), mRecorder(aRecorder) + { + mRecorder->AddStoredObject(this); + } + + ~GradientStopsRecording() + { + mRecorder->RemoveStoredObject(this); + mRecorder->RecordEvent(RecordedGradientStopsDestruction(this)); + } + + virtual BackendType GetBackendType() const { return BackendType::RECORDING; } + + RefPtr<GradientStops> mFinalGradientStops; + RefPtr<DrawEventRecorderPrivate> mRecorder; +}; + +static SourceSurface * +GetSourceSurface(SourceSurface *aSurface) +{ + if (aSurface->GetType() != SurfaceType::RECORDING) { + return aSurface; + } + + return static_cast<SourceSurfaceRecording*>(aSurface)->mFinalSurface; +} + +static GradientStops * +GetGradientStops(GradientStops *aStops) +{ + if (aStops->GetBackendType() != BackendType::RECORDING) { + return aStops; + } + + return static_cast<GradientStopsRecording*>(aStops)->mFinalGradientStops; +} + +class FilterNodeRecording : public FilterNode +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeRecording, override) + using FilterNode::SetAttribute; + + FilterNodeRecording(FilterNode *aFinalFilterNode, DrawEventRecorderPrivate *aRecorder) + : mFinalFilterNode(aFinalFilterNode), mRecorder(aRecorder) + { + mRecorder->AddStoredObject(this); + } + + ~FilterNodeRecording() + { + mRecorder->RemoveStoredObject(this); + mRecorder->RecordEvent(RecordedFilterNodeDestruction(this)); + } + + static FilterNode* + GetFilterNode(FilterNode* aNode) + { + if (aNode->GetBackendType() != FILTER_BACKEND_RECORDING) { + gfxWarning() << "Non recording filter node used with recording DrawTarget!"; + return aNode; + } + + return static_cast<FilterNodeRecording*>(aNode)->mFinalFilterNode; + } + + virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) override + { + EnsureSurfaceStored(mRecorder, aSurface, "SetInput"); + + mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aSurface)); + mFinalFilterNode->SetInput(aIndex, GetSourceSurface(aSurface)); + } + virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override + { + MOZ_ASSERT(mRecorder->HasStoredObject(aFilter)); + + mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aFilter)); + mFinalFilterNode->SetInput(aIndex, GetFilterNode(aFilter)); + } + + +#define FORWARD_SET_ATTRIBUTE(type, argtype) \ + virtual void SetAttribute(uint32_t aIndex, type aValue) override { \ + mRecorder->RecordEvent(RecordedFilterNodeSetAttribute(this, aIndex, aValue, RecordedFilterNodeSetAttribute::ARGTYPE_##argtype)); \ + mFinalFilterNode->SetAttribute(aIndex, aValue); \ + } + + FORWARD_SET_ATTRIBUTE(bool, BOOL); + FORWARD_SET_ATTRIBUTE(uint32_t, UINT32); + FORWARD_SET_ATTRIBUTE(Float, FLOAT); + FORWARD_SET_ATTRIBUTE(const Size&, SIZE); + FORWARD_SET_ATTRIBUTE(const IntSize&, INTSIZE); + FORWARD_SET_ATTRIBUTE(const IntPoint&, INTPOINT); + FORWARD_SET_ATTRIBUTE(const Rect&, RECT); + FORWARD_SET_ATTRIBUTE(const IntRect&, INTRECT); + FORWARD_SET_ATTRIBUTE(const Point&, POINT); + FORWARD_SET_ATTRIBUTE(const Matrix&, MATRIX); + FORWARD_SET_ATTRIBUTE(const Matrix5x4&, MATRIX5X4); + FORWARD_SET_ATTRIBUTE(const Point3D&, POINT3D); + FORWARD_SET_ATTRIBUTE(const Color&, COLOR); + +#undef FORWARD_SET_ATTRIBUTE + + virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override { + mRecorder->RecordEvent(RecordedFilterNodeSetAttribute(this, aIndex, aFloat, aSize)); + mFinalFilterNode->SetAttribute(aIndex, aFloat, aSize); + } + + virtual FilterBackend GetBackendType() override { return FILTER_BACKEND_RECORDING; } + + RefPtr<FilterNode> mFinalFilterNode; + RefPtr<DrawEventRecorderPrivate> mRecorder; +}; + +struct AdjustedPattern +{ + explicit AdjustedPattern(const Pattern &aPattern) + : mPattern(nullptr) + { + mOrigPattern = const_cast<Pattern*>(&aPattern); + } + + ~AdjustedPattern() { + if (mPattern) { + mPattern->~Pattern(); + } + } + + operator Pattern*() + { + switch(mOrigPattern->GetType()) { + case PatternType::COLOR: + return mOrigPattern; + case PatternType::SURFACE: + { + SurfacePattern *surfPat = static_cast<SurfacePattern*>(mOrigPattern); + mPattern = + new (mSurfPat) SurfacePattern(GetSourceSurface(surfPat->mSurface), + surfPat->mExtendMode, surfPat->mMatrix, + surfPat->mSamplingFilter, + surfPat->mSamplingRect); + return mPattern; + } + case PatternType::LINEAR_GRADIENT: + { + LinearGradientPattern *linGradPat = static_cast<LinearGradientPattern*>(mOrigPattern); + mPattern = + new (mLinGradPat) LinearGradientPattern(linGradPat->mBegin, linGradPat->mEnd, + GetGradientStops(linGradPat->mStops), + linGradPat->mMatrix); + return mPattern; + } + case PatternType::RADIAL_GRADIENT: + { + RadialGradientPattern *radGradPat = static_cast<RadialGradientPattern*>(mOrigPattern); + mPattern = + new (mRadGradPat) RadialGradientPattern(radGradPat->mCenter1, radGradPat->mCenter2, + radGradPat->mRadius1, radGradPat->mRadius2, + GetGradientStops(radGradPat->mStops), + radGradPat->mMatrix); + return mPattern; + } + default: + return new (mColPat) ColorPattern(Color()); + } + + return mPattern; + } + + union { + char mColPat[sizeof(ColorPattern)]; + char mLinGradPat[sizeof(LinearGradientPattern)]; + char mRadGradPat[sizeof(RadialGradientPattern)]; + char mSurfPat[sizeof(SurfacePattern)]; + }; + + Pattern *mOrigPattern; + Pattern *mPattern; +}; + +DrawTargetRecording::DrawTargetRecording(DrawEventRecorder *aRecorder, DrawTarget *aDT, bool aHasData) + : mRecorder(static_cast<DrawEventRecorderPrivate*>(aRecorder)) + , mFinalDT(aDT) +{ + RefPtr<SourceSurface> snapshot = aHasData ? mFinalDT->Snapshot() : nullptr; + mRecorder->RecordEvent(RecordedDrawTargetCreation(this, + mFinalDT->GetBackendType(), + mFinalDT->GetSize(), + mFinalDT->GetFormat(), + aHasData, snapshot)); + mFormat = mFinalDT->GetFormat(); +} + +DrawTargetRecording::DrawTargetRecording(const DrawTargetRecording *aDT, + DrawTarget *aSimilarDT) + : mRecorder(aDT->mRecorder) + , mFinalDT(aSimilarDT) +{ + mRecorder->RecordEvent(RecordedCreateSimilarDrawTarget(this, + mFinalDT->GetSize(), + mFinalDT->GetFormat())); + mFormat = mFinalDT->GetFormat(); +} + +DrawTargetRecording::~DrawTargetRecording() +{ + mRecorder->RecordEvent(RecordedDrawTargetDestruction(this)); +} + +void +DrawTargetRecording::FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions) +{ + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(RecordedFillRect(this, aRect, aPattern, aOptions)); + mFinalDT->FillRect(aRect, *AdjustedPattern(aPattern), aOptions); +} + +void +DrawTargetRecording::StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aOptions) +{ + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(RecordedStrokeRect(this, aRect, aPattern, aStrokeOptions, aOptions)); + mFinalDT->StrokeRect(aRect, *AdjustedPattern(aPattern), aStrokeOptions, aOptions); +} + +void +DrawTargetRecording::StrokeLine(const Point &aBegin, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aOptions) +{ + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(RecordedStrokeLine(this, aBegin, aEnd, aPattern, aStrokeOptions, aOptions)); + mFinalDT->StrokeLine(aBegin, aEnd, *AdjustedPattern(aPattern), aStrokeOptions, aOptions); +} + +void +DrawTargetRecording::Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions) +{ + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(RecordedFill(this, pathRecording, aPattern, aOptions)); + mFinalDT->Fill(pathRecording->mPath, *AdjustedPattern(aPattern), aOptions); +} + +struct RecordingFontUserData +{ + void *refPtr; + RefPtr<DrawEventRecorderPrivate> recorder; +}; + +void RecordingFontUserDataDestroyFunc(void *aUserData) +{ + RecordingFontUserData *userData = + static_cast<RecordingFontUserData*>(aUserData); + + userData->recorder->RecordEvent(RecordedScaledFontDestruction(userData->refPtr)); + + delete userData; +} + +void +DrawTargetRecording::FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions) +{ + EnsurePatternDependenciesStored(aPattern); + + if (!aFont->GetUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()))) { + RecordedFontData fontData(aFont); + RecordedFontDetails fontDetails; + if (fontData.GetFontDetails(fontDetails)) { + // Try to serialise the whole font, just in case this is a web font that + // is not present on the system. + if (!mRecorder->HasStoredFontData(fontDetails.fontDataKey)) { + mRecorder->RecordEvent(fontData); + mRecorder->AddStoredFontData(fontDetails.fontDataKey); + } + mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, fontDetails)); + } else { + // If that fails, record just the font description and try to load it from + // the system on the other side. + RecordedFontDescriptor fontDesc(aFont); + if (fontDesc.IsValid()) { + mRecorder->RecordEvent(fontDesc); + } else { + gfxWarning() << "DrawTargetRecording::FillGlyphs failed to serialise ScaledFont"; + } + } + RecordingFontUserData *userData = new RecordingFontUserData; + userData->refPtr = aFont; + userData->recorder = mRecorder; + aFont->AddUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()), userData, + &RecordingFontUserDataDestroyFunc); + } + + mRecorder->RecordEvent(RecordedFillGlyphs(this, aFont, aPattern, aOptions, aBuffer.mGlyphs, aBuffer.mNumGlyphs)); + mFinalDT->FillGlyphs(aFont, aBuffer, *AdjustedPattern(aPattern), aOptions, aRenderingOptions); +} + +void +DrawTargetRecording::Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions) +{ + EnsurePatternDependenciesStored(aSource); + EnsurePatternDependenciesStored(aMask); + + mRecorder->RecordEvent(RecordedMask(this, aSource, aMask, aOptions)); + mFinalDT->Mask(*AdjustedPattern(aSource), *AdjustedPattern(aMask), aOptions); +} + +void +DrawTargetRecording::MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions) +{ + EnsurePatternDependenciesStored(aSource); + EnsureSurfaceStored(mRecorder, aMask, "MaskSurface"); + + mRecorder->RecordEvent(RecordedMaskSurface(this, aSource, aMask, aOffset, aOptions)); + mFinalDT->MaskSurface(*AdjustedPattern(aSource), GetSourceSurface(aMask), aOffset, aOptions); +} + +void +DrawTargetRecording::Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aOptions) +{ + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(RecordedStroke(this, pathRecording, aPattern, aStrokeOptions, aOptions)); + mFinalDT->Stroke(pathRecording->mPath, *AdjustedPattern(aPattern), aStrokeOptions, aOptions); +} + +already_AddRefed<SourceSurface> +DrawTargetRecording::Snapshot() +{ + RefPtr<SourceSurface> surf = mFinalDT->Snapshot(); + + RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder); + + mRecorder->RecordEvent(RecordedSnapshot(retSurf, this)); + + return retSurf.forget(); +} + +void +DrawTargetRecording::DetachAllSnapshots() +{ + mFinalDT->DetachAllSnapshots(); +} + +void +DrawTargetRecording::DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aOptions) +{ + EnsureSurfaceStored(mRecorder, aSurface, "DrawSurface"); + + mRecorder->RecordEvent(RecordedDrawSurface(this, aSurface, aDest, aSource, aSurfOptions, aOptions)); + mFinalDT->DrawSurface(GetSourceSurface(aSurface), aDest, aSource, aSurfOptions, aOptions); +} + +void +DrawTargetRecording::DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOp) +{ + EnsureSurfaceStored(mRecorder, aSurface, "DrawSurfaceWithShadow"); + + mRecorder->RecordEvent(RecordedDrawSurfaceWithShadow(this, aSurface, aDest, aColor, aOffset, aSigma, aOp)); + mFinalDT->DrawSurfaceWithShadow(GetSourceSurface(aSurface), aDest, aColor, aOffset, aSigma, aOp); +} + +void +DrawTargetRecording::DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions) +{ + MOZ_ASSERT(mRecorder->HasStoredObject(aNode)); + + mRecorder->RecordEvent(RecordedDrawFilter(this, aNode, aSourceRect, aDestPoint, aOptions)); + mFinalDT->DrawFilter(FilterNodeRecording::GetFilterNode(aNode), aSourceRect, aDestPoint, aOptions); +} + +already_AddRefed<FilterNode> +DrawTargetRecording::CreateFilter(FilterType aType) +{ + RefPtr<FilterNode> node = mFinalDT->CreateFilter(aType); + + RefPtr<FilterNode> retNode = new FilterNodeRecording(node, mRecorder); + + mRecorder->RecordEvent(RecordedFilterNodeCreation(retNode, aType)); + + return retNode.forget(); +} + +void +DrawTargetRecording::ClearRect(const Rect &aRect) +{ + mRecorder->RecordEvent(RecordedClearRect(this, aRect)); + mFinalDT->ClearRect(aRect); +} + +void +DrawTargetRecording::CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) +{ + EnsureSurfaceStored(mRecorder, aSurface, "CopySurface"); + + mRecorder->RecordEvent(RecordedCopySurface(this, aSurface, aSourceRect, aDestination)); + mFinalDT->CopySurface(GetSourceSurface(aSurface), aSourceRect, aDestination); +} + +void +DrawTargetRecording::PushClip(const Path *aPath) +{ + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + + mRecorder->RecordEvent(RecordedPushClip(this, pathRecording)); + mFinalDT->PushClip(pathRecording->mPath); +} + +void +DrawTargetRecording::PushClipRect(const Rect &aRect) +{ + mRecorder->RecordEvent(RecordedPushClipRect(this, aRect)); + mFinalDT->PushClipRect(aRect); +} + +void +DrawTargetRecording::PopClip() +{ + mRecorder->RecordEvent(RecordedPopClip(this)); + mFinalDT->PopClip(); +} + +void +DrawTargetRecording::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground) +{ + if (aMask) { + EnsureSurfaceStored(mRecorder, aMask, "PushLayer"); + } + + mRecorder->RecordEvent(RecordedPushLayer(this, aOpaque, aOpacity, aMask, + aMaskTransform, aBounds, + aCopyBackground)); + mFinalDT->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, + aCopyBackground); +} + +void +DrawTargetRecording::PopLayer() +{ + mRecorder->RecordEvent(RecordedPopLayer(this)); + mFinalDT->PopLayer(); +} + +already_AddRefed<SourceSurface> +DrawTargetRecording::CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const +{ + RefPtr<SourceSurface> surf = mFinalDT->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat); + + RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder); + + mRecorder->RecordEvent(RecordedSourceSurfaceCreation(retSurf, aData, aStride, aSize, aFormat)); + + return retSurf.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetRecording::OptimizeSourceSurface(SourceSurface *aSurface) const +{ + RefPtr<SourceSurface> surf = mFinalDT->OptimizeSourceSurface(aSurface); + + RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder); + + RefPtr<DataSourceSurface> dataSurf = surf->GetDataSurface(); + + if (!dataSurf) { + // Let's try get it off the original surface. + dataSurf = aSurface->GetDataSurface(); + } + + StoreSourceSurface(mRecorder, retSurf, dataSurf, "OptimizeSourceSurface"); + + return retSurf.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetRecording::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const +{ + RefPtr<SourceSurface> surf = mFinalDT->CreateSourceSurfaceFromNativeSurface(aSurface); + + RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording(surf, mRecorder); + + RefPtr<DataSourceSurface> dataSurf = surf->GetDataSurface(); + StoreSourceSurface(mRecorder, retSurf, dataSurf, "CreateSourceSurfaceFromNativeSurface"); + + return retSurf.forget(); +} + +already_AddRefed<DrawTarget> +DrawTargetRecording::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const +{ + RefPtr<DrawTarget> similarDT = + mFinalDT->CreateSimilarDrawTarget(aSize, aFormat); + if (!similarDT) { + return nullptr; + } + + similarDT = new DrawTargetRecording(this, similarDT); + return similarDT.forget(); +} + +already_AddRefed<PathBuilder> +DrawTargetRecording::CreatePathBuilder(FillRule aFillRule) const +{ + RefPtr<PathBuilder> builder = mFinalDT->CreatePathBuilder(aFillRule); + return MakeAndAddRef<PathBuilderRecording>(builder, aFillRule); +} + +already_AddRefed<GradientStops> +DrawTargetRecording::CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode) const +{ + RefPtr<GradientStops> stops = mFinalDT->CreateGradientStops(aStops, aNumStops, aExtendMode); + + RefPtr<GradientStops> retStops = new GradientStopsRecording(stops, mRecorder); + + mRecorder->RecordEvent(RecordedGradientStopsCreation(retStops, aStops, aNumStops, aExtendMode)); + + return retStops.forget(); +} + +void +DrawTargetRecording::SetTransform(const Matrix &aTransform) +{ + mRecorder->RecordEvent(RecordedSetTransform(this, aTransform)); + DrawTarget::SetTransform(aTransform); + mFinalDT->SetTransform(aTransform); +} + +already_AddRefed<PathRecording> +DrawTargetRecording::EnsurePathStored(const Path *aPath) +{ + RefPtr<PathRecording> pathRecording; + if (aPath->GetBackendType() == BackendType::RECORDING) { + pathRecording = const_cast<PathRecording*>(static_cast<const PathRecording*>(aPath)); + if (mRecorder->HasStoredObject(aPath)) { + return pathRecording.forget(); + } + } else { + MOZ_ASSERT(!mRecorder->HasStoredObject(aPath)); + FillRule fillRule = aPath->GetFillRule(); + RefPtr<PathBuilder> builder = mFinalDT->CreatePathBuilder(fillRule); + RefPtr<PathBuilderRecording> builderRecording = + new PathBuilderRecording(builder, fillRule); + aPath->StreamToSink(builderRecording); + pathRecording = builderRecording->Finish().downcast<PathRecording>(); + } + + mRecorder->RecordEvent(RecordedPathCreation(pathRecording)); + mRecorder->AddStoredObject(pathRecording); + pathRecording->mStoredRecorders.push_back(mRecorder); + + return pathRecording.forget(); +} + +void +DrawTargetRecording::EnsurePatternDependenciesStored(const Pattern &aPattern) +{ + switch (aPattern.GetType()) { + case PatternType::COLOR: + // No dependencies here. + return; + case PatternType::LINEAR_GRADIENT: + { + MOZ_ASSERT(mRecorder->HasStoredObject(static_cast<const LinearGradientPattern*>(&aPattern)->mStops)); + return; + } + case PatternType::RADIAL_GRADIENT: + { + MOZ_ASSERT(mRecorder->HasStoredObject(static_cast<const RadialGradientPattern*>(&aPattern)->mStops)); + return; + } + case PatternType::SURFACE: + { + const SurfacePattern *pat = static_cast<const SurfacePattern*>(&aPattern); + EnsureSurfaceStored(mRecorder, pat->mSurface, "EnsurePatternDependenciesStored"); + return; + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetRecording.h b/gfx/2d/DrawTargetRecording.h new file mode 100644 index 000000000..359d1f6be --- /dev/null +++ b/gfx/2d/DrawTargetRecording.h @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETRECORDING_H_ +#define MOZILLA_GFX_DRAWTARGETRECORDING_H_ + +#include "2D.h" +#include "DrawEventRecorder.h" + +namespace mozilla { +namespace gfx { + +class DrawTargetRecording : public DrawTarget +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetRecording, override) + DrawTargetRecording(DrawEventRecorder *aRecorder, DrawTarget *aDT, bool aHasData = false); + + ~DrawTargetRecording(); + + virtual DrawTargetType GetType() const override { return mFinalDT->GetType(); } + virtual BackendType GetBackendType() const override { return mFinalDT->GetBackendType(); } + virtual bool IsRecording() const override { return true; } + + virtual already_AddRefed<SourceSurface> Snapshot() override; + + virtual void DetachAllSnapshots() override; + + virtual IntSize GetSize() override { return mFinalDT->GetSize(); } + + /* Ensure that the DrawTarget backend has flushed all drawing operations to + * this draw target. This must be called before using the backing surface of + * this draw target outside of GFX 2D code. + */ + virtual void Flush() override { mFinalDT->Flush(); } + + /* + * Draw a surface to the draw target. Possibly doing partial drawing or + * applying scaling. No sampling happens outside the source. + * + * aSurface Source surface to draw + * aDest Destination rectangle that this drawing operation should draw to + * aSource Source rectangle in aSurface coordinates, this area of aSurface + * will be stretched to the size of aDest. + * aOptions General draw options that are applied to the operation + * aSurfOptions DrawSurface options that are applied + */ + virtual void DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Blend a surface to the draw target with a shadow. The shadow is drawn as a + * gaussian blur using a specified sigma. The shadow is clipped to the size + * of the input surface, so the input surface should contain a transparent + * border the size of the approximate coverage of the blur (3 * aSigma). + * NOTE: This function works in device space! + * + * aSurface Source surface to draw. + * aDest Destination point that this drawing operation should draw to. + * aColor Color of the drawn shadow + * aOffset Offset of the shadow + * aSigma Sigma used for the guassian filter kernel + * aOperator Composition operator used + */ + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) override; + + /* + * Clear a rectangle on the draw target to transparent black. This will + * respect the clipping region and transform. + * + * aRect Rectangle to clear + */ + virtual void ClearRect(const Rect &aRect) override; + + /* + * This is essentially a 'memcpy' between two surfaces. It moves a pixel + * aligned area from the source surface unscaled directly onto the + * drawtarget. This ignores both transform and clip. + * + * aSurface Surface to copy from + * aSourceRect Source rectangle to be copied + * aDest Destination point to copy the surface to + */ + virtual void CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) override; + + /* + * Fill a rectangle on the DrawTarget with a certain source pattern. + * + * aRect Rectangle that forms the mask of this filling operation + * aPattern Pattern that forms the source of this filling operation + * aOptions Options that are applied to this operation + */ + virtual void FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Stroke a rectangle on the DrawTarget with a certain source pattern. + * + * aRect Rectangle that forms the mask of this stroking operation + * aPattern Pattern that forms the source of this stroking operation + * aOptions Options that are applied to this operation + */ + virtual void StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Stroke a line on the DrawTarget with a certain source pattern. + * + * aStart Starting point of the line + * aEnd End point of the line + * aPattern Pattern that forms the source of this stroking operation + * aOptions Options that are applied to this operation + */ + virtual void StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Stroke a path on the draw target with a certain source pattern. + * + * aPath Path that is to be stroked + * aPattern Pattern that should be used for the stroke + * aStrokeOptions Stroke options used for this operation + * aOptions Draw options used for this operation + */ + virtual void Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Fill a path on the draw target with a certain source pattern. + * + * aPath Path that is to be filled + * aPattern Pattern that should be used for the fill + * aOptions Draw options used for this operation + */ + virtual void Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Fill a series of clyphs on the draw target with a certain source pattern. + */ + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr) override; + + /* + * This takes a source pattern and a mask, and composites the source pattern + * onto the destination surface using the alpha channel of the mask pattern + * as a mask for the operation. + * + * aSource Source pattern + * aMask Mask pattern + * aOptions Drawing options + */ + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()) override; + + /* + * Push a clip to the DrawTarget. + * + * aPath The path to clip to + */ + virtual void PushClip(const Path *aPath) override; + + /* + * Push an axis-aligned rectangular clip to the DrawTarget. This rectangle + * is specified in user space. + * + * aRect The rect to clip to + */ + virtual void PushClipRect(const Rect &aRect) override; + + /* Pop a clip from the DrawTarget. A pop without a corresponding push will + * be ignored. + */ + virtual void PopClip() override; + + /** + * Push a 'layer' to the DrawTarget, a layer is a temporary surface that all + * drawing will be redirected to, this is used for example to support group + * opacity or the masking of groups. Clips must be balanced within a layer, + * i.e. between a matching PushLayer/PopLayer pair there must be as many + * PushClip(Rect) calls as there are PopClip calls. + * + * @param aOpaque Whether the layer will be opaque + * @param aOpacity Opacity of the layer + * @param aMask Mask applied to the layer + * @param aMaskTransform Transform applied to the layer mask + * @param aBounds Optional bounds in device space to which the layer is + * limited in size. + * @param aCopyBackground Whether to copy the background into the layer, this + * is only supported when aOpaque is true. + */ + virtual void PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + + /** + * This balances a call to PushLayer and proceeds to blend the layer back + * onto the background. This blend will blend the temporary surface back + * onto the target in device space using POINT sampling and operator over. + */ + virtual void PopLayer() override; + + /* + * Create a SourceSurface optimized for use with this DrawTarget from + * existing bitmap data in memory. + * + * The SourceSurface does not take ownership of aData, and may be freed at any time. + */ + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const override; + + /* + * Create a SourceSurface optimized for use with this DrawTarget from + * an arbitrary other SourceSurface. This may return aSourceSurface or some + * other existing surface. + */ + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override; + + /* + * Create a SourceSurface for a type of NativeSurface. This may fail if the + * draw target does not know how to deal with the type of NativeSurface passed + * in. + */ + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override; + + /* + * Create a DrawTarget whose snapshot is optimized for use with this DrawTarget. + */ + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override; + + /* + * Create a path builder with the specified fillmode. + * + * We need the fill mode up front because of Direct2D. + * ID2D1SimplifiedGeometrySink requires the fill mode + * to be set before calling BeginFigure(). + */ + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override; + + /* + * Create a GradientStops object that holds information about a set of + * gradient stops, this object is required for linear or radial gradient + * patterns to represent the color stops in the gradient. + * + * aStops An array of gradient stops + * aNumStops Number of stops in the array aStops + * aExtendNone This describes how to extend the stop color outside of the + * gradient area. + */ + virtual already_AddRefed<GradientStops> + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + + /* + * Set a transform on the surface, this transform is applied at drawing time + * to both the mask and source of the operation. + */ + virtual void SetTransform(const Matrix &aTransform) override; + + /* Tries to get a native surface for a DrawTarget, this may fail if the + * draw target cannot convert to this surface type. + */ + virtual void *GetNativeSurface(NativeSurfaceType aType) override { return mFinalDT->GetNativeSurface(aType); } + + virtual bool IsCurrentGroupOpaque() override { + return mFinalDT->IsCurrentGroupOpaque(); + } + +private: + /** + * Used for creating a DrawTargetRecording for a CreateSimilarDrawTarget call. + * We have to call CreateSimilarDrawTarget on mFinalDT up front and pass it in + * as it can fail. + * + * @param aDT DrawTargetRecording on which CreateSimilarDrawTarget was called + * @param aSimilarDT Similar DrawTarget created from aDT.mFinalDT. + */ + DrawTargetRecording(const DrawTargetRecording *aDT, + DrawTarget *aSimilarDT); + + Path *GetPathForPathRecording(const Path *aPath) const; + already_AddRefed<PathRecording> EnsurePathStored(const Path *aPath); + void EnsurePatternDependenciesStored(const Pattern &aPattern); + + RefPtr<DrawEventRecorderPrivate> mRecorder; + RefPtr<DrawTarget> mFinalDT; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWTARGETRECORDING_H_ */ diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp new file mode 100644 index 000000000..9eaac5674 --- /dev/null +++ b/gfx/2d/DrawTargetSkia.cpp @@ -0,0 +1,2135 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetSkia.h" +#include "SourceSurfaceSkia.h" +#include "ScaledFontBase.h" +#include "ScaledFontCairo.h" +#include "skia/include/core/SkBitmapDevice.h" +#include "FilterNodeSoftware.h" +#include "HelpersSkia.h" + +#include "mozilla/ArrayUtils.h" + +#include "skia/include/core/SkSurface.h" +#include "skia/include/core/SkTypeface.h" +#include "skia/include/effects/SkGradientShader.h" +#include "skia/include/core/SkColorFilter.h" +#include "skia/include/effects/SkBlurImageFilter.h" +#include "skia/include/effects/SkLayerRasterizer.h" +#include "skia/src/core/SkSpecialImage.h" +#include "Blur.h" +#include "Logging.h" +#include "Tools.h" +#include "DataSurfaceHelpers.h" +#include <algorithm> + +#ifdef USE_SKIA_GPU +#include "GLDefs.h" +#include "skia/include/gpu/SkGr.h" +#include "skia/include/gpu/GrContext.h" +#include "skia/include/gpu/GrDrawContext.h" +#include "skia/include/gpu/gl/GrGLInterface.h" +#include "skia/src/image/SkImage_Gpu.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +#include "BorrowedContext.h" +#include <ApplicationServices/ApplicationServices.h> +#include "mozilla/Vector.h" +#include "ScaledFontMac.h" +#include "CGTextDrawing.h" +#endif + +#ifdef XP_WIN +#include "ScaledFontDWrite.h" +#endif + +namespace mozilla { +namespace gfx { + +class GradientStopsSkia : public GradientStops +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsSkia) + 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 { 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(); + } +} + +#ifdef IS_BIG_ENDIAN +static const int kARGBAlphaOffset = 0; +#else +static const int kARGBAlphaOffset = 3; +#endif + +static void +WriteRGBXFormat(uint8_t* aData, const IntSize &aSize, + const int32_t aStride, SurfaceFormat aFormat) +{ + if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { + return; + } + + int height = aSize.height; + int width = aSize.width * 4; + + for (int row = 0; row < height; ++row) { + for (int column = 0; column < width; column += 4) { + aData[column + kARGBAlphaOffset] = 0xFF; + } + aData += aStride; + } + + return; +} + +#ifdef DEBUG +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) +{ + if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { + return true; + } + + int height = aSize.height; + int width = aSize.width; + const int pixelSize = 4; + const int strideDiff = aStride - (width * pixelSize); + MOZ_ASSERT(width * pixelSize <= aStride); + + const int topLeft = 0; + const int topRight = width * pixelSize - pixelSize; + const int bottomRight = aStride * height - strideDiff - pixelSize; + const int bottomLeft = aStride * height - aStride; + + // Lastly the center pixel + int middleRowHeight = height / 2; + int middleRowWidth = (width / 2) * pixelSize; + const int middle = aStride * middleRowHeight + middleRowWidth; + + const int offsets[] = { topLeft, topRight, bottomRight, bottomLeft, middle }; + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(offsets); i++) { + int offset = offsets[i]; + if (aData[offset + kARGBAlphaOffset] != 0xFF) { + int row = offset / aStride; + int column = (offset % aStride) / pixelSize; + gfxCriticalError() << "RGBX corner pixel at (" << column << "," << row << ") in " + << width << "x" << height << " surface 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) +{ + if (!aSurface) { + gfxDebug() << "Creating null Skia image from null SourceSurface"; + return nullptr; + } + + if (aSurface->GetType() == SurfaceType::SKIA) { + return static_cast<SourceSurfaceSkia*>(aSurface)->GetImage(); + } + + DataSourceSurface* surf = aSurface->GetDataSurface().take(); + if (!surf) { + gfxWarning() << "Failed getting DataSourceSurface for Skia image"; + return nullptr; + } + + SkPixmap pixmap(MakeSkiaImageInfo(surf->GetSize(), surf->GetFormat()), + surf->GetData(), surf->Stride()); + sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, ReleaseTemporarySurface, surf); + if (!image) { + ReleaseTemporarySurface(nullptr, surf); + gfxDebug() << "Failed making Skia raster image for temporary surface"; + } + + // Skia doesn't support RGBX surfaces so ensure that the alpha value is opaque white. + MOZ_ASSERT(VerifyRGBXCorners(surf->GetData(), surf->GetSize(), + surf->Stride(), surf->GetFormat())); + return image; +} + +DrawTargetSkia::DrawTargetSkia() + : mSnapshot(nullptr) +#ifdef MOZ_WIDGET_COCOA + , mCG(nullptr) + , mColorSpace(nullptr) + , mCanvasData(nullptr) + , mCGSize(0, 0) +#endif +{ +} + +DrawTargetSkia::~DrawTargetSkia() +{ +#ifdef MOZ_WIDGET_COCOA + if (mCG) { + CGContextRelease(mCG); + mCG = nullptr; + } + + if (mColorSpace) { + CGColorSpaceRelease(mColorSpace); + mColorSpace = nullptr; + } +#endif +} + +already_AddRefed<SourceSurface> +DrawTargetSkia::Snapshot() +{ + 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(SkBudgeted::kNo); + } + if (!snapshot->InitFromImage(image, mFormat, this)) { + return nullptr; + } + mSnapshot = snapshot; + } + + return snapshot.forget(); +} + +bool +DrawTargetSkia::LockBits(uint8_t** aData, IntSize* aSize, + int32_t* aStride, SurfaceFormat* aFormat, + IntPoint* aOrigin) +{ + // Ensure the layer is at the origin if required. + SkIPoint origin = mCanvas->getTopDevice()->getOrigin(); + if (!aOrigin && !origin.isZero()) { + return false; + } + + /* Test if the canvas' device has accessible pixels first, as actually + * accessing the pixels may trigger side-effects, even if it fails. + */ + if (!mCanvas->peekPixels(nullptr)) { + return false; + } + + SkImageInfo info; + size_t rowBytes; + void* pixels = mCanvas->accessTopLayerPixels(&info, &rowBytes); + if (!pixels) { + 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 inline bool +SkImageIsMask(const sk_sp<SkImage>& aImage) +{ + SkPixmap pixmap; + if (aImage->peekPixels(&pixmap)) { + return pixmap.colorType() == kAlpha_8_SkColorType; +#ifdef USE_SKIA_GPU + } else if (GrTexture* tex = aImage->getTexture()) { + return GrPixelConfigIsAlphaOnly(tex->config()); +#endif + } else { + return false; + } +} + +static bool +ExtractAlphaBitmap(sk_sp<SkImage> aImage, SkBitmap* aResultBitmap) +{ + SkImageInfo info = SkImageInfo::MakeA8(aImage->width(), aImage->height()); + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(info, SkAlign4(info.minRowBytes())) || + !aImage->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), 0, 0)) { + gfxWarning() << "Failed reading alpha pixels for Skia bitmap"; + return false; + } + + *aResultBitmap = bitmap; + return true; +} + +static sk_sp<SkImage> +ExtractAlphaForSurface(SourceSurface* aSurface) +{ + sk_sp<SkImage> image = GetSkImageForSurface(aSurface); + if (!image) { + return nullptr; + } + if (SkImageIsMask(image)) { + return image; + } + + SkBitmap bitmap; + if (!ExtractAlphaBitmap(image, &bitmap)) { + return nullptr; + } + + // Mark the bitmap immutable so that it will be shared rather than copied. + bitmap.setImmutable(); + return SkImage::MakeFromBitmap(bitmap); +} + +static void +SetPaintPattern(SkPaint& aPaint, const Pattern& aPattern, Float aAlpha = 1.0, Point aOffset = Point(0, 0)) +{ + switch (aPattern.GetType()) { + case PatternType::COLOR: { + Color 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 = static_cast<GradientStopsSkia*>(pat.mStops.get()); + if (!stops || stops->mCount < 2 || + !pat.mBegin.IsFinite() || !pat.mEnd.IsFinite()) { + aPaint.setColor(SK_ColorTRANSPARENT); + } else { + SkShader::TileMode 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); + mat.postTranslate(SkFloatToScalar(aOffset.x), SkFloatToScalar(aOffset.y)); + sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, + &stops->mColors.front(), + &stops->mPositions.front(), + stops->mCount, + mode, 0, &mat); + aPaint.setShader(shader); + } + break; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern); + GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get()); + if (!stops || stops->mCount < 2 || + !pat.mCenter1.IsFinite() || !IsFinite(pat.mRadius1) || + !pat.mCenter2.IsFinite() || !IsFinite(pat.mRadius2)) { + aPaint.setColor(SK_ColorTRANSPARENT); + } else { + SkShader::TileMode 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); + mat.postTranslate(SkFloatToScalar(aOffset.x), SkFloatToScalar(aOffset.y)); + 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); + aPaint.setShader(shader); + } + break; + } + case PatternType::SURFACE: { + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); + sk_sp<SkImage> image = GetSkImageForSurface(pat.mSurface); + if (!image) { + aPaint.setColor(SK_ColorTRANSPARENT); + break; + } + + SkMatrix mat; + GfxMatrixToSkiaMatrix(pat.mMatrix, mat); + mat.postTranslate(SkFloatToScalar(aOffset.x), SkFloatToScalar(aOffset.y)); + + if (!pat.mSamplingRect.IsEmpty()) { + image = ExtractSubset(image, pat.mSamplingRect); + mat.preTranslate(pat.mSamplingRect.x, pat.mSamplingRect.y); + } + + SkShader::TileMode xTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS); + SkShader::TileMode yTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS); + + aPaint.setShader(image->makeShader(xTileMode, yTileMode, &mat)); + + if (pat.mSamplingFilter == SamplingFilter::POINT) { + aPaint.setFilterQuality(kNone_SkFilterQuality); + } + 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->getClipDeviceBounds(&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, Point aOffset = Point(0, 0)) + : mNeedsRestore(false), mAlpha(1.0) + { + Init(aCanvas, aOptions, aMaskBounds, false); + SetPaintPattern(mPaint, aPattern, mAlpha, aOffset); + } + + 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 + mCanvas->saveLayer(nullptr, &temp); + mNeedsRestore = true; + } else { + mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha)); + mAlpha = aOptions.mAlpha; + } + mPaint.setFilterQuality(kLow_SkFilterQuality); + } + + // TODO: Maybe add an operator overload to access this easier? + SkPaint mPaint; + bool mNeedsRestore; + SkCanvas* mCanvas; + 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(); + + sk_sp<SkImage> image = GetSkImageForSurface(aSurface); + if (!image) { + return; + } + + SkRect destRect = RectToSkRect(aDest); + SkRect sourceRect = RectToSkRect(aSource); + bool forceGroup = SkImageIsMask(image) && + aOptions.mCompositionOp != CompositionOp::OP_OVER; + + AutoPaintSetup paint(mCanvas.get(), aOptions, &aDest, forceGroup); + if (aSurfOptions.mSamplingFilter == SamplingFilter::POINT) { + paint.mPaint.setFilterQuality(kNone_SkFilterQuality); + } + + mCanvas->drawImageRect(image, sourceRect, destRect, &paint.mPaint); +} + +DrawTargetType +DrawTargetSkia::GetType() const +{ +#ifdef USE_SKIA_GPU + if (mGrContext) { + return DrawTargetType::HARDWARE_RASTER; + } +#endif + return DrawTargetType::SOFTWARE_RASTER; +} + +void +DrawTargetSkia::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 +DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) +{ + if (aSurface->GetSize().IsEmpty()) { + return; + } + + MarkChanged(); + + sk_sp<SkImage> image = GetSkImageForSurface(aSurface); + 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 + aOffset); + + SkBitmap blurMask; + if (!UsingSkiaGPU() && + ExtractAlphaBitmap(image, &blurMask)) { + // Prefer using our own box blur instead of Skia's when we're + // not using the GPU. 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()), + aSigma, aSigma); + blurMask.lockPixels(); + blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels())); + blurMask.unlockPixels(); + blurMask.notifyPixelsChanged(); + + shadowPaint.setColor(ColorToSkColor(aColor, 1.0f)); + + mCanvas->drawBitmap(blurMask, shadowDest.x, shadowDest.y, &shadowPaint); + } else { + sk_sp<SkImageFilter> blurFilter(SkBlurImageFilter::Make(aSigma, aSigma, nullptr)); + sk_sp<SkColorFilter> colorFilter( + SkColorFilter::MakeModeFilter(ColorToSkColor(aColor, 1.0f), SkBlendMode::kSrcIn)); + + shadowPaint.setImageFilter(blurFilter); + shadowPaint.setColorFilter(colorFilter); + + mCanvas->drawImage(image, shadowDest.x, shadowDest.y, &shadowPaint); + } + + // Composite the original image after the shadow + auto dest = IntPoint::Round(aDest); + mCanvas->drawImage(image, dest.x, dest.y, &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.get(), aOptions, aPattern, &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.get(), aOptions, aPattern); + if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { + return; + } + + if (!skiaPath->GetPath().isFinite()) { + return; + } + + mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); +} + +void +DrawTargetSkia::StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aOptions) +{ + MarkChanged(); + AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern); + if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { + return; + } + + mCanvas->drawRect(RectToSkRect(aRect), paint.mPaint); +} + +void +DrawTargetSkia::StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aOptions) +{ + MarkChanged(); + AutoPaintSetup paint(mCanvas.get(), 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.get(), aOptions, aPattern); + + if (!skiaPath->GetPath().isFinite()) { + return; + } + + mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); +} + +bool +DrawTargetSkia::ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode) +{ + // For non-opaque surfaces, only allow subpixel AA if explicitly permitted. + if (!IsOpaque(mFormat) && !mPermitSubpixelAA) { + return false; + } + + if (aAntialiasMode == AntialiasMode::DEFAULT) { + switch (aFontType) { + case FontType::MAC: + case FontType::GDI: + case FontType::DWRITE: + case FontType::FONTCONFIG: + return true; + default: + // TODO: Figure out what to do for the other platforms. + return false; + } + } + return (aAntialiasMode == AntialiasMode::SUBPIXEL); +} + +#ifdef MOZ_WIDGET_COCOA +class CGClipApply : public SkCanvas::ClipVisitor { +public: + explicit CGClipApply(CGContextRef aCGContext) + : mCG(aCGContext) {} + void clipRect(const SkRect& aRect, SkCanvas::ClipOp op, bool antialias) override { + CGRect rect = CGRectMake(aRect.x(), aRect.y(), aRect.width(), aRect.height()); + CGContextClipToRect(mCG, rect); + } + + void clipRRect(const SkRRect& rrect, SkCanvas::ClipOp op, bool antialias) override { + SkPath path; + path.addRRect(rrect); + clipPath(path, op, antialias); + } + + void clipPath(const SkPath& aPath, SkCanvas::ClipOp, bool antialias) override { + SkPath::Iter iter(aPath, true); + SkPoint source[4]; + SkPath::Verb verb; + RefPtr<PathBuilderCG> pathBuilder = + new PathBuilderCG(GetFillRule(aPath.getFillType())); + + while ((verb = iter.next(source)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + { + SkPoint dest = source[0]; + pathBuilder->MoveTo(Point(dest.fX, dest.fY)); + break; + } + case SkPath::kLine_Verb: + { + // The first point should be the end point of whatever + // verb we got to get here. + SkPoint second = source[1]; + pathBuilder->LineTo(Point(second.fX, second.fY)); + break; + } + case SkPath::kQuad_Verb: + { + SkPoint second = source[1]; + SkPoint third = source[2]; + + pathBuilder->QuadraticBezierTo(Point(second.fX, second.fY), + Point(third.fX, third.fY)); + break; + } + case SkPath::kCubic_Verb: + { + SkPoint second = source[1]; + SkPoint third = source[2]; + SkPoint fourth = source[2]; + + pathBuilder->BezierTo(Point(second.fX, second.fY), + Point(third.fX, third.fY), + Point(fourth.fX, fourth.fY)); + break; + } + case SkPath::kClose_Verb: + { + pathBuilder->Close(); + break; + } + default: + { + SkDEBUGFAIL("unknown verb"); + break; + } + } // end switch + } // end while + + RefPtr<Path> path = pathBuilder->Finish(); + PathCG* cgPath = static_cast<PathCG*>(path.get()); + + // Weirdly, CoreGraphics clips empty paths as all shown + // but empty rects as all clipped. We detect this situation and + // workaround it appropriately + if (CGPathIsEmpty(cgPath->GetPath())) { + CGContextClipToRect(mCG, CGRectZero); + return; + } + + CGContextBeginPath(mCG); + CGContextAddPath(mCG, cgPath->GetPath()); + + if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) { + CGContextEOClip(mCG); + } else { + CGContextClip(mCG); + } + } + +private: + CGContextRef mCG; +}; + +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, + sk_sp<SkCanvas> aCanvas) +{ + // 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, 0, aDT->GetSize().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. + // CGClipApply applies clips in device space, so it would be a mistake + // to transform these clips. + CGClipApply clipApply(aCGContext); + aCanvas->replayClips(&clipApply); + + CGContextConcatCTM(aCGContext, GfxMatrixToCGAffineTransform(aDT->GetTransform())); + return true; +} + +static bool +SetupCGGlyphs(CGContextRef aCGContext, + const GlyphBuffer& aBuffer, + Vector<CGGlyph,32>& aGlyphs, + Vector<CGPoint,32>& aPositions) +{ + // Flip again so we draw text in right side up. Transform (3) from the top + CGContextScaleCTM(aCGContext, 1, -1); + + if (!aGlyphs.resizeUninitialized(aBuffer.mNumGlyphs) || + !aPositions.resizeUninitialized(aBuffer.mNumGlyphs)) { + gfxDevCrash(LogReason::GlyphAllocFailedCG) << "glyphs/positions allocation failed"; + return false; + } + + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + aGlyphs[i] = aBuffer.mGlyphs[i].mIndex; + + // Flip the y coordinates so that text ends up in the right spot after the (3) flip + // Inversion from (4) in the comments. + aPositions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x, + -aBuffer.mGlyphs[i].mPosition.y); + } + + return true; +} +// End long comment about transforms. SetupCGContext and SetupCGGlyphs should stay +// next to each other. + +// The context returned from this method will have the origin +// in the top left and will hvae applied all the neccessary clips +// and transforms to the CGContext. See the comment above +// SetupCGContext. +CGContextRef +DrawTargetSkia::BorrowCGContext(const DrawOptions &aOptions) +{ + int32_t stride; + SurfaceFormat format; + IntSize size; + + uint8_t* aSurfaceData = nullptr; + if (!LockBits(&aSurfaceData, &size, &stride, &format)) { + NS_WARNING("Could not lock skia bits to wrap CG around"); + return nullptr; + } + + if ((aSurfaceData == mCanvasData) && mCG && (mCGSize == size)) { + // If our canvas data still points to the same data, + // we can reuse the CG Context + CGContextSaveGState(mCG); + CGContextSetAlpha(mCG, aOptions.mAlpha); + SetupCGContext(this, mCG, mCanvas); + 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 = aSurfaceData; + 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) { + 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); + return mCG; +} + +void +DrawTargetSkia::ReturnCGContext(CGContextRef aCGContext) +{ + MOZ_ASSERT(aCGContext == mCG); + ReleaseBits(mCanvasData); + CGContextRestoreGState(aCGContext); +} + +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); + return; +} + +static void +SetFontColor(CGContextRef aCGContext, CGColorSpaceRef aColorSpace, const Pattern& aPattern) +{ + const Color& color = static_cast<const ColorPattern&>(aPattern).mColor; + CGColorRef textColor = ColorToCGColor(aColorSpace, color); + CGContextSetFillColorWithColor(aCGContext, textColor); + CGColorRelease(textColor); +} + +/*** + * We need this to support subpixel AA text on OS X in two cases: + * text in DrawTargets that are not opaque and text over vibrant backgrounds. + * Skia normally doesn't support subpixel AA text on transparent backgrounds. + * To get around this, we have to wrap the Skia bytes with a CGContext and ask + * CG to draw the text. + * In vibrancy cases, we have to use a private API, + * CGContextSetFontSmoothingBackgroundColor, which sets the expected + * background color the text will draw onto so that CG can render the text + * properly. After that, we have to go back and fixup the pixels + * such that their alpha values are correct. + */ +bool +DrawTargetSkia::FillGlyphsWithCG(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions) +{ + MOZ_ASSERT(aFont->GetType() == FontType::MAC); + MOZ_ASSERT(aPattern.GetType() == PatternType::COLOR); + + CGContextRef cgContext = BorrowCGContext(aOptions); + if (!cgContext) { + return false; + } + + Vector<CGGlyph,32> glyphs; + Vector<CGPoint,32> positions; + if (!SetupCGGlyphs(cgContext, aBuffer, glyphs, positions)) { + ReturnCGContext(cgContext); + return false; + } + + SetFontSmoothingBackgroundColor(cgContext, mColorSpace, aRenderingOptions); + SetFontColor(cgContext, mColorSpace, aPattern); + + ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont); + if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) { + ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, glyphs.begin(), + positions.begin(), + aBuffer.mNumGlyphs, cgContext); + } else { + CGContextSetFont(cgContext, macFont->mFont); + CGContextSetFontSize(cgContext, macFont->mSize); + CGContextShowGlyphsAtPositions(cgContext, glyphs.begin(), positions.begin(), + aBuffer.mNumGlyphs); + } + + // Calculate the area of the text we just drew + CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs]; + CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation, + glyphs.begin(), bboxes, aBuffer.mNumGlyphs); + CGRect extents = ComputeGlyphsExtents(bboxes, positions.begin(), aBuffer.mNumGlyphs, 1.0f); + delete[] bboxes; + + CGAffineTransform cgTransform = CGContextGetCTM(cgContext); + extents = CGRectApplyAffineTransform(extents, cgTransform); + + // Have to round it out to ensure we fully cover all pixels + Rect rect(extents.origin.x, extents.origin.y, extents.size.width, extents.size.height); + rect.RoundOut(); + extents = CGRectMake(rect.x, rect.y, rect.width, rect.height); + + EnsureValidPremultipliedData(cgContext, extents); + + ReturnCGContext(cgContext); + return true; +} + +static bool +HasFontSmoothingBackgroundColor(const GlyphRenderingOptions* aRenderingOptions) +{ + // This should generally only be true if we have a popup context menu + if (aRenderingOptions && aRenderingOptions->GetType() == FontType::MAC) { + Color fontSmoothingBackgroundColor = + static_cast<const GlyphRenderingOptionsCG*>(aRenderingOptions)->FontSmoothingBackgroundColor(); + return fontSmoothingBackgroundColor.a > 0; + } + + return false; +} + +static bool +ShouldUseCGToFillGlyphs(const GlyphRenderingOptions* aOptions, const Pattern& aPattern) +{ + return HasFontSmoothingBackgroundColor(aOptions) && + aPattern.GetType() == PatternType::COLOR; +} + +#endif + +static bool +CanDrawFont(ScaledFont* aFont) +{ + switch (aFont->GetType()) { + case FontType::SKIA: + case FontType::CAIRO: + case FontType::FONTCONFIG: + case FontType::MAC: + case FontType::GDI: + case FontType::DWRITE: + return true; + default: + return false; + } +} + +void +DrawTargetSkia::FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions, + const GlyphRenderingOptions *aRenderingOptions) +{ + if (!CanDrawFont(aFont)) { + return; + } + + MarkChanged(); + +#ifdef MOZ_WIDGET_COCOA + if (ShouldUseCGToFillGlyphs(aRenderingOptions, aPattern)) { + if (FillGlyphsWithCG(aFont, aBuffer, aPattern, aOptions, aRenderingOptions)) { + return; + } + } +#endif + + ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont); + SkTypeface* typeface = skiaFont->GetSkTypeface(); + if (!typeface) { + return; + } + + AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern); + AntialiasMode aaMode = aFont->GetDefaultAAMode(); + if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { + aaMode = aOptions.mAntialiasMode; + } + bool aaEnabled = aaMode != AntialiasMode::NONE; + + paint.mPaint.setAntiAlias(aaEnabled); + paint.mPaint.setTypeface(sk_ref_sp(typeface)); + paint.mPaint.setTextSize(SkFloatToScalar(skiaFont->mSize)); + paint.mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + + bool shouldLCDRenderText = ShouldLCDRenderText(aFont->GetType(), aaMode); + paint.mPaint.setLCDRenderText(shouldLCDRenderText); + + bool useSubpixelText = true; + + switch (aFont->GetType()) { + case FontType::SKIA: + case FontType::CAIRO: + case FontType::FONTCONFIG: + // SkFontHost_cairo does not support subpixel text positioning, + // so only enable it for other font hosts. + useSubpixelText = false; + break; + case FontType::MAC: + if (aaMode == AntialiasMode::GRAY) { + // 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. + paint.mPaint.setHinting(SkPaint::kNo_Hinting); + } + break; + case FontType::GDI: + { + if (!shouldLCDRenderText && aaEnabled) { + // If we have non LCD GDI text, render the fonts as cleartype and convert them + // to grayscale. This seems to be what Chrome and IE are doing on Windows 7. + // This also applies if cleartype is disabled system wide. + paint.mPaint.setFlags(paint.mPaint.getFlags() | SkPaint::kGenA8FromLCD_Flag); + } + break; + } +#ifdef XP_WIN + case FontType::DWRITE: + { + ScaledFontDWrite* dwriteFont = static_cast<ScaledFontDWrite*>(aFont); + paint.mPaint.setEmbeddedBitmapText(dwriteFont->UseEmbeddedBitmaps()); + + if (dwriteFont->ForceGDIMode()) { + paint.mPaint.setEmbeddedBitmapText(true); + useSubpixelText = false; + } + break; + } +#endif + default: + break; + } + + paint.mPaint.setSubpixelText(useSubpixelText); + + std::vector<uint16_t> indices; + std::vector<SkPoint> offsets; + indices.resize(aBuffer.mNumGlyphs); + offsets.resize(aBuffer.mNumGlyphs); + + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + indices[i] = aBuffer.mGlyphs[i].mIndex; + offsets[i].fX = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.x); + offsets[i].fY = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.y); + } + + mCanvas->drawPosText(&indices.front(), aBuffer.mNumGlyphs*2, &offsets.front(), paint.mPaint); +} + +void +DrawTargetSkia::Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions) +{ + MarkChanged(); + AutoPaintSetup paint(mCanvas.get(), aOptions, aSource); + + SkPaint maskPaint; + SetPaintPattern(maskPaint, aMask); + + SkLayerRasterizer::Builder builder; + builder.addLayer(maskPaint); + sk_sp<SkLayerRasterizer> raster(builder.detach()); + paint.mPaint.setRasterizer(raster); + + mCanvas->drawPaint(paint.mPaint); +} + +void +DrawTargetSkia::MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions) +{ + MarkChanged(); + AutoPaintSetup paint(mCanvas.get(), aOptions, aSource, nullptr, -aOffset); + + sk_sp<SkImage> alphaMask = ExtractAlphaForSurface(aMask); + if (!alphaMask) { + gfxDebug() << *this << ": MaskSurface() failed to extract alpha for mask"; + return; + } + + mCanvas->drawImage(alphaMask, aOffset.x, aOffset.y, &paint.mPaint); +} + +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. + sk_sp<SkImage> srcImage = GetSkImageForSurface(aSurface); + 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; + } + sk_sp<SkCanvas> dstCanvas( + SkCanvas::NewRasterDirect( + SkImageInfo::Make(xformBounds.width, xformBounds.height, + GfxFormatToSkiaColorType(dstSurf->GetFormat()), + kPremul_SkAlphaType), + dstSurf->GetData(), dstSurf->Stride())); + if (!dstCanvas) { + return false; + } + + // Do the transform. + SkPaint paint; + paint.setAntiAlias(true); + paint.setFilterQuality(kLow_SkFilterQuality); + paint.setBlendMode(SkBlendMode::kSrc); + + SkMatrix xform; + GfxMatrixToSkiaMatrix(fullMat, xform); + dstCanvas->setMatrix(xform); + + dstCanvas->drawImage(srcImage, 0, 0, &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(); + + sk_sp<SkImage> image = GetSkImageForSurface(aSurface); + if (!image) { + return true; + } + + mCanvas->save(); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setFilterQuality(kLow_SkFilterQuality); + + SkMatrix xform; + GfxMatrixToSkiaMatrix(aMatrix, xform); + mCanvas->concat(xform); + + mCanvas->drawImage(image, 0, 0, &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 USE_SKIA_GPU + if (UsingSkiaGPU()) { + // Try to create a GPU draw target first if we're currently using the GPU. + // Mark the DT as cached so that shadow DTs, extracted subrects, and similar can be reused. + if (target->InitWithGrContext(mGrContext.get(), aSize, aFormat, true)) { + return target.forget(); + } + // Otherwise, just fall back to a software draw target. + } +#endif + +#ifdef DEBUG + if (!IsBackedByPixels(mCanvas.get())) { + // 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::UsingSkiaGPU() const +{ +#ifdef USE_SKIA_GPU + return !!mGrContext; +#else + return false; +#endif +} + +#ifdef USE_SKIA_GPU +already_AddRefed<SourceSurface> +DrawTargetSkia::OptimizeGPUSourceSurface(SourceSurface *aSurface) const +{ + // Check if the underlying SkImage already has an associated GrTexture. + sk_sp<SkImage> image = GetSkImageForSurface(aSurface); + if (!image || image->isTextureBacked()) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + // Upload the SkImage to a GrTexture otherwise. + sk_sp<SkImage> texture = image->makeTextureImage(mGrContext.get()); + if (texture) { + // Create a new SourceSurfaceSkia whose SkImage contains the GrTexture. + RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); + if (surface->InitFromImage(texture, aSurface->GetFormat())) { + return surface.forget(); + } + } + + // The data was too big to fit in a GrTexture. + if (aSurface->GetType() == SurfaceType::SKIA) { + // It is already a Skia source surface, so just reuse it as-is. + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + // Wrap it in a Skia source surface so that can do tiled uploads on-demand. + RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); + surface->InitFromImage(image); + return surface.forget(); +} +#endif + +already_AddRefed<SourceSurface> +DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const +{ +#ifdef USE_SKIA_GPU + if (UsingSkiaGPU()) { + return OptimizeGPUSourceSurface(aSurface); + } +#endif + + if (aSurface->GetType() == SurfaceType::SKIA) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); + + // 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(dataSurface->GetData(), dataSurface->GetSize(), + dataSurface->Stride(), dataSurface->GetFormat()); + return dataSurface.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetSkia::OptimizeSourceSurface(SourceSurface *aSurface) const +{ +#ifdef USE_SKIA_GPU + if (UsingSkiaGPU()) { + return OptimizeGPUSourceSurface(aSurface); + } +#endif + + 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(); + MOZ_ASSERT(VerifyRGBXFormat(dataSurface->GetData(), dataSurface->GetSize(), + dataSurface->Stride(), dataSurface->GetFormat())); + return dataSurface.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetSkia::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const +{ +#ifdef USE_SKIA_GPU + if (aSurface.mType == NativeSurfaceType::OPENGL_TEXTURE && UsingSkiaGPU()) { + // Wrap the OpenGL texture id in a Skia texture handle. + GrBackendTextureDesc texDesc; + texDesc.fWidth = aSurface.mSize.width; + texDesc.fHeight = aSurface.mSize.height; + texDesc.fOrigin = kTopLeft_GrSurfaceOrigin; + texDesc.fConfig = GfxFormatToGrConfig(aSurface.mFormat); + + GrGLTextureInfo texInfo; + texInfo.fTarget = LOCAL_GL_TEXTURE_2D; + texInfo.fID = (GrGLuint)(uintptr_t)aSurface.mSurface; + texDesc.fTextureHandle = reinterpret_cast<GrBackendObject>(&texInfo); + + sk_sp<SkImage> texture = + SkImage::MakeFromAdoptedTexture(mGrContext.get(), texDesc, + GfxFormatToSkiaAlphaType(aSurface.mFormat)); + RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia(); + if (texture && newSurf->InitFromImage(texture, aSurface.mFormat)) { + return newSurf.forget(); + } + return nullptr; + } +#endif + + return nullptr; +} + +void +DrawTargetSkia::CopySurface(SourceSurface *aSurface, + const IntRect& aSourceRect, + const IntPoint &aDestination) +{ + MarkChanged(); + + sk_sp<SkImage> image = GetSkImageForSurface(aSurface); + if (!image) { + return; + } + + mCanvas->save(); + mCanvas->setMatrix(SkMatrix::MakeTrans(SkIntToScalar(aDestination.x), SkIntToScalar(aDestination.y))); + mCanvas->clipRect(SkRect::MakeIWH(aSourceRect.width, aSourceRect.height), kReplace_SkClipOp); + + SkPaint paint; + if (!image->isOpaque()) { + // Keep the xfermode as SOURCE_OVER for opaque bitmaps + // http://code.google.com/p/skia/issues/detail?id=628 + paint.setBlendMode(SkBlendMode::kSrc); + } + // drawImage with A8 images ends up doing a mask operation + // so we need to clear before + if (SkImageIsMask(image)) { + mCanvas->clear(SK_ColorTRANSPARENT); + } + mCanvas->drawImage(image, -SkIntToScalar(aSourceRect.x), -SkIntToScalar(aSourceRect.y), &paint); + mCanvas->restore(); +} + +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 = SkAlign4(info.minRowBytes()); + mSurface = SkSurface::MakeRaster(info, stride, nullptr); + if (!mSurface) { + return false; + } + + mSize = aSize; + mFormat = aFormat; + mCanvas = sk_ref_sp(mSurface->getCanvas()); + + if (info.isOpaque()) { + mCanvas->clear(SK_ColorBLACK); + } + return true; +} + +bool +DrawTargetSkia::Init(SkCanvas* aCanvas) +{ + mCanvas = sk_ref_sp(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.get())) { + 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()); + return true; +} + +#ifdef USE_SKIA_GPU +/** Indicating a DT should be cached means that space will be reserved in Skia's cache + * for the render target at creation time, with any unused resources exceeding the cache + * limits being purged. When the DT is freed, it will then be guaranteed to be kept around + * for subsequent allocations until it gets incidentally purged. + * + * If it is not marked as cached, no space will be purged to make room for the render + * target in the cache. When the DT is freed, If there is space within the resource limits + * it may be added to the cache, otherwise it will be freed immediately if the cache is + * already full. + * + * If you want to ensure that the resources will be kept around for reuse, it is better + * to mark them as cached. Such resources should be short-lived to ensure they don't + * permanently tie up cache resource limits. Long-lived resources should generally be + * left as uncached. + * + * In neither case will cache resource limits affect whether the resource allocation + * succeeds. The amount of in-use GPU resources is allowed to exceed the size of the cache. + * Thus, only hard GPU out-of-memory conditions will cause resource allocation to fail. + */ +bool +DrawTargetSkia::InitWithGrContext(GrContext* aGrContext, + const IntSize &aSize, + SurfaceFormat aFormat, + bool aCached) +{ + MOZ_ASSERT(aGrContext, "null GrContext"); + + if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) { + return false; + } + + // Create a GPU rendertarget/texture using the supplied GrContext. + // NewRenderTarget also implicitly clears the underlying texture on creation. + mSurface = + SkSurface::MakeRenderTarget(aGrContext, + SkBudgeted(aCached), + MakeSkiaImageInfo(aSize, aFormat)); + if (!mSurface) { + return false; + } + + mGrContext = sk_ref_sp(aGrContext); + mSize = aSize; + mFormat = aFormat; + mCanvas = sk_ref_sp(mSurface->getCanvas()); + return true; +} + +#endif + +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)); + + mSurface = SkSurface::MakeRasterDirect(MakeSkiaImageInfo(aSize, aFormat), aData, aStride); + if (!mSurface) { + return false; + } + + mSize = aSize; + mFormat = aFormat; + mCanvas = sk_ref_sp(mSurface->getCanvas()); + return true; +} + +void +DrawTargetSkia::SetTransform(const Matrix& aTransform) +{ + SkMatrix mat; + GfxMatrixToSkiaMatrix(aTransform, mat); + mCanvas->setMatrix(mat); + mTransform = aTransform; +} + +void* +DrawTargetSkia::GetNativeSurface(NativeSurfaceType aType) +{ +#ifdef USE_SKIA_GPU + if (aType == NativeSurfaceType::OPENGL_TEXTURE && mSurface) { + GrBackendObject handle = mSurface->getTextureHandle(SkSurface::kFlushRead_BackendHandleAccess); + if (handle) { + return (void*)(uintptr_t)reinterpret_cast<GrGLTextureInfo *>(handle)->fID; + } + } +#endif + return nullptr; +} + + +already_AddRefed<PathBuilder> +DrawTargetSkia::CreatePathBuilder(FillRule aFillRule) const +{ + return MakeAndAddRef<PathBuilderSkia>(aFillRule); +} + +void +DrawTargetSkia::ClearRect(const Rect &aRect) +{ + MarkChanged(); + mCanvas->save(); + mCanvas->clipRect(RectToSkRect(aRect), kIntersect_SkClipOp, 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(), kIntersect_SkClipOp, 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, kIntersect_SkClipOp); +} + +void +DrawTargetSkia::PushClipRect(const Rect& aRect) +{ + SkRect rect = RectToSkRect(aRect); + + mCanvas->save(); + mCanvas->clipRect(rect, kIntersect_SkClipOp, true); +} + +void +DrawTargetSkia::PopClip() +{ + mCanvas->restore(); +} + +// Image filter that just passes the source through to the result unmodified. +class CopyLayerImageFilter : public SkImageFilter +{ +public: + CopyLayerImageFilter() + : SkImageFilter(nullptr, 0, nullptr) + {} + + virtual sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, + const Context& ctx, + SkIPoint* offset) const override { + offset->set(0, 0); + return sk_ref_sp(source); + } + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(CopyLayerImageFilter) +}; + +sk_sp<SkFlattenable> +CopyLayerImageFilter::CreateProc(SkReadBuffer& buffer) +{ + SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 0); + return sk_make_sp<CopyLayerImageFilter>(); +} + +#ifndef SK_IGNORE_TO_STRING +void +CopyLayerImageFilter::toString(SkString* str) const +{ + str->append("CopyLayerImageFilter: ()"); +} +#endif + +void +DrawTargetSkia::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds, + bool aCopyBackground) +{ + PushedLayer layer(GetPermitSubpixelAA(), aOpaque, aOpacity, aMask, aMaskTransform); + mPushedLayers.push_back(layer); + + SkPaint paint; + + // If we have a mask, set the opacity to 0 so that SkCanvas::restore skips + // implicitly drawing the layer so that we can properly mask it in PopLayer. + paint.setAlpha(aMask ? 0 : ColorFloatToByte(aOpacity)); + + SkRect bounds = IntRectToSkRect(aBounds); + + sk_sp<SkImageFilter> backdrop(aCopyBackground ? new CopyLayerImageFilter : nullptr); + + SkCanvas::SaveLayerRec saveRec(aBounds.IsEmpty() ? nullptr : &bounds, + &paint, + backdrop.get(), + aOpaque ? SkCanvas::kIsOpaque_SaveLayerFlag : 0); + + mCanvas->saveLayer(saveRec); + + SetPermitSubpixelAA(aOpaque); + +#ifdef MOZ_WIDGET_COCOA + CGContextRelease(mCG); + mCG = nullptr; +#endif +} + +void +DrawTargetSkia::PopLayer() +{ + MarkChanged(); + + MOZ_ASSERT(mPushedLayers.size()); + const PushedLayer& layer = mPushedLayers.back(); + + if (layer.mMask) { + // If we have a mask, take a reference to the top layer's device so that + // we can mask it ourselves. This assumes we forced SkCanvas::restore to + // skip implicitly drawing the layer. + sk_sp<SkBaseDevice> layerDevice = sk_ref_sp(mCanvas->getTopDevice()); + SkIRect layerBounds = layerDevice->getGlobalBounds(); + sk_sp<SkImage> layerImage; + SkPixmap layerPixmap; + if (layerDevice->peekPixels(&layerPixmap)) { + layerImage = SkImage::MakeFromRaster(layerPixmap, nullptr, nullptr); +#ifdef USE_SKIA_GPU + } else if (GrDrawContext* drawCtx = mCanvas->internal_private_accessTopLayerDrawContext()) { + drawCtx->prepareForExternalIO(); + if (GrTexture* tex = drawCtx->accessRenderTarget()->asTexture()) { + layerImage = sk_make_sp<SkImage_Gpu>(layerBounds.width(), layerBounds.height(), + kNeedNewImageUniqueID, + layerDevice->imageInfo().alphaType(), + tex, nullptr, SkBudgeted::kNo); + } +#endif + } + + // Restore the background with the layer's device left alive. + mCanvas->restore(); + + SkPaint paint; + paint.setAlpha(ColorFloatToByte(layer.mOpacity)); + + SkMatrix maskMat, layerMat; + // Get the total transform affecting the mask, considering its pattern + // transform and the current canvas transform. + GfxMatrixToSkiaMatrix(layer.mMaskTransform, maskMat); + maskMat.postConcat(mCanvas->getTotalMatrix()); + if (!maskMat.invert(&layerMat)) { + gfxDebug() << *this << ": PopLayer() failed to invert mask transform"; + } else { + // The layer should not be affected by the current canvas transform, + // even though the mask is. So first we use the inverse of the transform + // affecting the mask, then add back on the layer's origin. + layerMat.preTranslate(layerBounds.x(), layerBounds.y()); + + if (layerImage) { + paint.setShader(layerImage->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &layerMat)); + } else { + paint.setColor(SK_ColorTRANSPARENT); + } + + sk_sp<SkImage> alphaMask = ExtractAlphaForSurface(layer.mMask); + if (!alphaMask) { + gfxDebug() << *this << ": PopLayer() failed to extract alpha for mask"; + } else { + mCanvas->save(); + + // The layer may be smaller than the canvas size, so make sure drawing is + // clipped to within the bounds of the layer. + mCanvas->resetMatrix(); + mCanvas->clipRect(SkRect::Make(layerBounds)); + + mCanvas->setMatrix(maskMat); + mCanvas->drawImage(alphaMask, 0, 0, &paint); + + mCanvas->restore(); + } + } + } else { + mCanvas->restore(); + } + + 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() +{ + if (mSnapshot) { + mSnapshot->DrawTargetWillChange(); + mSnapshot = nullptr; + + // Handle copying of any image snapshots bound to the surface. + if (mSurface) { + mSurface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode); + } + } +} + +void +DrawTargetSkia::SnapshotDestroyed() +{ + mSnapshot = nullptr; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetSkia.h b/gfx/2d/DrawTargetSkia.h new file mode 100644 index 000000000..c2e43da89 --- /dev/null +++ b/gfx/2d/DrawTargetSkia.h @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_SOURCESURFACESKIA_H +#define _MOZILLA_GFX_SOURCESURFACESKIA_H + +#include "skia/include/core/SkCanvas.h" +#include "skia/include/core/SkSurface.h" + +#include "2D.h" +#include "HelpersSkia.h" +#include "Rect.h" +#include "PathSkia.h" +#include <sstream> +#include <vector> + +#ifdef MOZ_WIDGET_COCOA +#include <ApplicationServices/ApplicationServices.h> +#endif + +namespace mozilla { +namespace gfx { + +class SourceSurfaceSkia; + +class DrawTargetSkia : public DrawTarget +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetSkia, override) + DrawTargetSkia(); + virtual ~DrawTargetSkia(); + + virtual DrawTargetType GetType() const override; + virtual BackendType GetBackendType() const override { return BackendType::SKIA; } + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual IntSize GetSize() override { return mSize; } + virtual bool LockBits(uint8_t** aData, IntSize* aSize, + int32_t* aStride, SurfaceFormat* aFormat, + IntPoint* aOrigin = nullptr) override; + virtual void ReleaseBits(uint8_t* aData) override; + virtual void Flush() override; + virtual void DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) override; + virtual void ClearRect(const Rect &aRect) override; + virtual void CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) override; + virtual void FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; +#ifdef MOZ_WIDGET_COCOA + CGContextRef BorrowCGContext(const DrawOptions &aOptions); + void ReturnCGContext(CGContextRef); + bool FillGlyphsWithCG(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr); +#endif + + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr) override; + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()) override; + virtual bool Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) override; + virtual void PushClip(const Path *aPath) override; + virtual void PushClipRect(const Rect& aRect) override; + virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override; + virtual void PopClip() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PopLayer() override; + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const override; + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override; + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override; + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override; + virtual already_AddRefed<GradientStops> CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + virtual void SetTransform(const Matrix &aTransform) override; + virtual void *GetNativeSurface(NativeSurfaceType aType) override; + virtual void DetachAllSnapshots() override { MarkChanged(); } + + bool Init(const IntSize &aSize, SurfaceFormat aFormat); + bool Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat, bool aUninitialized = false); + bool Init(SkCanvas* aCanvas); + +#ifdef USE_SKIA_GPU + bool InitWithGrContext(GrContext* aGrContext, + const IntSize &aSize, + SurfaceFormat aFormat, + bool aCached); + virtual bool + InitWithGrContext(GrContext* aGrContext, + const IntSize &aSize, + SurfaceFormat aFormat) override { + return InitWithGrContext(aGrContext, aSize, aFormat, false); + } + + already_AddRefed<SourceSurface> OptimizeGPUSourceSurface(SourceSurface *aSurface) const; +#endif + + // Skia assumes that texture sizes fit in 16-bit signed integers. + static size_t GetMaxSurfaceSize() { + return 32767; + } + + operator std::string() const { + std::stringstream stream; + stream << "DrawTargetSkia(" << this << ")"; + return stream.str(); + } + +private: + friend class SourceSurfaceSkia; + void SnapshotDestroyed(); + + void MarkChanged(); + + bool ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode); + + bool UsingSkiaGPU() const; + + struct PushedLayer + { + PushedLayer(bool aOldPermitSubpixelAA, + bool aOpaque, + Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform) + : mOldPermitSubpixelAA(aOldPermitSubpixelAA), + mOpaque(aOpaque), + mOpacity(aOpacity), + mMask(aMask), + mMaskTransform(aMaskTransform) + {} + bool mOldPermitSubpixelAA; + bool mOpaque; + Float mOpacity; + RefPtr<SourceSurface> mMask; + Matrix mMaskTransform; + }; + std::vector<PushedLayer> mPushedLayers; + +#ifdef USE_SKIA_GPU + sk_sp<GrContext> mGrContext; +#endif + + IntSize mSize; + sk_sp<SkSurface> mSurface; + sk_sp<SkCanvas> mCanvas; + SourceSurfaceSkia* mSnapshot; + +#ifdef MOZ_WIDGET_COCOA + CGContextRef mCG; + CGColorSpaceRef mColorSpace; + uint8_t* mCanvasData; + IntSize mCGSize; +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_SOURCESURFACESKIA_H diff --git a/gfx/2d/DrawTargetTiled.cpp b/gfx/2d/DrawTargetTiled.cpp new file mode 100644 index 000000000..fd7465408 --- /dev/null +++ b/gfx/2d/DrawTargetTiled.cpp @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetTiled.h" +#include "Logging.h" +#include "PathHelpers.h" + +using namespace std; + +namespace mozilla { +namespace gfx { + +DrawTargetTiled::DrawTargetTiled() +{ +} + +bool +DrawTargetTiled::Init(const TileSet& aTiles) +{ + if (!aTiles.mTileCount) { + return false; + } + + mTiles.reserve(aTiles.mTileCount); + for (size_t i = 0; i < aTiles.mTileCount; ++i) { + mTiles.push_back(TileInternal(aTiles.mTiles[i])); + if (!aTiles.mTiles[i].mDrawTarget) { + return false; + } + if (mTiles[0].mDrawTarget->GetFormat() != mTiles.back().mDrawTarget->GetFormat() || + mTiles[0].mDrawTarget->GetBackendType() != mTiles.back().mDrawTarget->GetBackendType()) { + return false; + } + uint32_t newXMost = max(mRect.XMost(), + mTiles[i].mTileOrigin.x + mTiles[i].mDrawTarget->GetSize().width); + uint32_t newYMost = max(mRect.YMost(), + mTiles[i].mTileOrigin.y + mTiles[i].mDrawTarget->GetSize().height); + mRect.x = min(mRect.x, mTiles[i].mTileOrigin.x); + mRect.y = min(mRect.y, mTiles[i].mTileOrigin.y); + mRect.width = newXMost - mRect.x; + mRect.height = newYMost - mRect.y; + mTiles[i].mDrawTarget->SetTransform(Matrix::Translation(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y)); + } + mFormat = mTiles[0].mDrawTarget->GetFormat(); + return true; +} + +already_AddRefed<SourceSurface> +DrawTargetTiled::Snapshot() +{ + return MakeAndAddRef<SnapshotTiled>(mTiles, mRect); +} + +void +DrawTargetTiled::DetachAllSnapshots() +{} + +// Skip the mClippedOut check since this is only used for Flush() which +// should happen even if we're clipped. +#define TILED_COMMAND(command) \ + void \ + DrawTargetTiled::command() \ + { \ + for (size_t i = 0; i < mTiles.size(); i++) { \ + mTiles[i].mDrawTarget->command(); \ + } \ + } +#define TILED_COMMAND1(command, type1) \ + void \ + DrawTargetTiled::command(type1 arg1) \ + { \ + for (size_t i = 0; i < mTiles.size(); i++) { \ + if (!mTiles[i].mClippedOut) \ + mTiles[i].mDrawTarget->command(arg1); \ + } \ + } +#define TILED_COMMAND3(command, type1, type2, type3) \ + void \ + DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3) \ + { \ + for (size_t i = 0; i < mTiles.size(); i++) { \ + if (!mTiles[i].mClippedOut) \ + mTiles[i].mDrawTarget->command(arg1, arg2, arg3); \ + } \ + } +#define TILED_COMMAND4(command, type1, type2, type3, type4) \ + void \ + DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3, type4 arg4) \ + { \ + for (size_t i = 0; i < mTiles.size(); i++) { \ + if (!mTiles[i].mClippedOut) \ + mTiles[i].mDrawTarget->command(arg1, arg2, arg3, arg4); \ + } \ + } +#define TILED_COMMAND5(command, type1, type2, type3, type4, type5) \ + void \ + DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5) \ + { \ + for (size_t i = 0; i < mTiles.size(); i++) { \ + if (!mTiles[i].mClippedOut) \ + mTiles[i].mDrawTarget->command(arg1, arg2, arg3, arg4, arg5); \ + } \ + } + +TILED_COMMAND(Flush) +TILED_COMMAND4(DrawFilter, FilterNode*, const Rect&, const Point&, const DrawOptions&) +TILED_COMMAND1(ClearRect, const Rect&) +TILED_COMMAND4(MaskSurface, const Pattern&, SourceSurface*, Point, const DrawOptions&) +TILED_COMMAND5(FillGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&, const DrawOptions&, const GlyphRenderingOptions*) +TILED_COMMAND3(Mask, const Pattern&, const Pattern&, const DrawOptions&) + +void +DrawTargetTiled::PushClip(const Path* aPath) +{ + mClippedOutTilesStack.push_back(std::vector<uint32_t>()); + std::vector<uint32_t>& clippedTiles = mClippedOutTilesStack.back(); + + Rect deviceRect = aPath->GetBounds(mTransform); + + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut) { + if (deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->PushClip(aPath); + } else { + mTiles[i].mClippedOut = true; + clippedTiles.push_back(i); + } + } + } +} + +void +DrawTargetTiled::PushClipRect(const Rect& aRect) +{ + mClippedOutTilesStack.push_back(std::vector<uint32_t>()); + std::vector<uint32_t>& clippedTiles = mClippedOutTilesStack.back(); + + Rect deviceRect = mTransform.TransformBounds(aRect); + + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut) { + if (deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->PushClipRect(aRect); + } else { + mTiles[i].mClippedOut = true; + clippedTiles.push_back(i); + } + } + } +} + +void +DrawTargetTiled::PopClip() +{ + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut) { + mTiles[i].mDrawTarget->PopClip(); + } + } + + std::vector<uint32_t>& clippedTiles = mClippedOutTilesStack.back(); + for (size_t i = 0; i < clippedTiles.size(); i++) { + mTiles[clippedTiles[i]].mClippedOut = false; + } + + mClippedOutTilesStack.pop_back(); +} + +void +DrawTargetTiled::CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) +{ + for (size_t i = 0; i < mTiles.size(); i++) { + IntPoint tileOrigin = mTiles[i].mTileOrigin; + IntSize tileSize = mTiles[i].mDrawTarget->GetSize(); + if (!IntRect(aDestination, aSourceRect.Size()).Intersects(IntRect(tileOrigin, tileSize))) { + continue; + } + // CopySurface ignores the transform, account for that here. + mTiles[i].mDrawTarget->CopySurface(aSurface, aSourceRect, aDestination - tileOrigin); + } +} + +void +DrawTargetTiled::SetTransform(const Matrix& aTransform) +{ + for (size_t i = 0; i < mTiles.size(); i++) { + Matrix mat = aTransform; + mat.PostTranslate(Float(-mTiles[i].mTileOrigin.x), Float(-mTiles[i].mTileOrigin.y)); + mTiles[i].mDrawTarget->SetTransform(mat); + } + DrawTarget::SetTransform(aTransform); +} + +void +DrawTargetTiled::DrawSurface(SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, const DrawSurfaceOptions& aSurfaceOptions, const DrawOptions& aDrawOptions) +{ + Rect deviceRect = mTransform.TransformBounds(aDest); + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut && + deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->DrawSurface(aSurface, aDest, aSource, aSurfaceOptions, aDrawOptions); + } + } +} + +void +DrawTargetTiled::FillRect(const Rect& aRect, const Pattern& aPattern, const DrawOptions& aDrawOptions) +{ + Rect deviceRect = mTransform.TransformBounds(aRect); + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut && + deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->FillRect(aRect, aPattern, aDrawOptions); + } + } +} + +void +DrawTargetTiled::Stroke(const Path* aPath, const Pattern& aPattern, const StrokeOptions& aStrokeOptions, const DrawOptions& aDrawOptions) +{ + // Approximate the stroke extents, since Path::GetStrokeExtents can be slow + Rect deviceRect = aPath->GetBounds(mTransform); + deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform)); + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut && + deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->Stroke(aPath, aPattern, aStrokeOptions, aDrawOptions); + } + } +} + +void +DrawTargetTiled::StrokeRect(const Rect& aRect, const Pattern& aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions& aDrawOptions) +{ + Rect deviceRect = mTransform.TransformBounds(aRect); + Margin strokeMargin = MaxStrokeExtents(aStrokeOptions, mTransform); + Rect outerRect = deviceRect; + outerRect.Inflate(strokeMargin); + Rect innerRect; + if (mTransform.IsRectilinear()) { + // If rects are mapped to rects, we can compute the inner rect + // of the stroked rect. + innerRect = deviceRect; + innerRect.Deflate(strokeMargin); + } + for (size_t i = 0; i < mTiles.size(); i++) { + if (mTiles[i].mClippedOut) { + continue; + } + Rect tileRect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height); + if (outerRect.Intersects(tileRect) && !innerRect.Contains(tileRect)) { + mTiles[i].mDrawTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aDrawOptions); + } + } +} + +void +DrawTargetTiled::StrokeLine(const Point& aStart, const Point& aEnd, const Pattern& aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions& aDrawOptions) +{ + Rect lineBounds = Rect(aStart, Size()).UnionEdges(Rect(aEnd, Size())); + Rect deviceRect = mTransform.TransformBounds(lineBounds); + deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform)); + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut && + deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aDrawOptions); + } + } +} + +void +DrawTargetTiled::Fill(const Path* aPath, const Pattern& aPattern, const DrawOptions& aDrawOptions) +{ + Rect deviceRect = aPath->GetBounds(mTransform); + for (size_t i = 0; i < mTiles.size(); i++) { + if (!mTiles[i].mClippedOut && + deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x, + mTiles[i].mTileOrigin.y, + mTiles[i].mDrawTarget->GetSize().width, + mTiles[i].mDrawTarget->GetSize().height))) { + mTiles[i].mDrawTarget->Fill(aPath, aPattern, aDrawOptions); + } + } +} + +void +DrawTargetTiled::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds, + bool aCopyBackground) +{ + // XXX - not sure this is what we want or whether we want to continue drawing to a larger + // intermediate surface, that would require tweaking the code in here a little though. + for (size_t i = 0; i < mTiles.size(); i++) { + IntRect bounds = aBounds; + bounds.MoveBy(-mTiles[i].mTileOrigin); + mTiles[i].mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds); + } +} + +void +DrawTargetTiled::PopLayer() +{ + // XXX - not sure this is what we want or whether we want to continue drawing to a larger + // intermediate surface, that would require tweaking the code in here a little though. + for (size_t i = 0; i < mTiles.size(); i++) { + mTiles[i].mDrawTarget->PopLayer(); + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetTiled.h b/gfx/2d/DrawTargetTiled.h new file mode 100644 index 000000000..23e8318a3 --- /dev/null +++ b/gfx/2d/DrawTargetTiled.h @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETTILED_H_ +#define MOZILLA_GFX_DRAWTARGETTILED_H_ + +#include "2D.h" +#include "Filters.h" +#include "Logging.h" + +#include <vector> + +namespace mozilla { +namespace gfx { + +struct TileInternal : public Tile { + TileInternal() + : mClippedOut(false) + {} + + explicit TileInternal(const Tile& aOther) + : Tile(aOther) + , mClippedOut(false) + {} + + bool mClippedOut; +}; + + +class DrawTargetTiled : public DrawTarget +{ +public: + DrawTargetTiled(); + + bool Init(const TileSet& mTiles); + + virtual bool IsTiledDrawTarget() const override { return true; } + + virtual DrawTargetType GetType() const override { return mTiles[0].mDrawTarget->GetType(); } + virtual BackendType GetBackendType() const override { return mTiles[0].mDrawTarget->GetBackendType(); } + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual void DetachAllSnapshots() override; + virtual IntSize GetSize() override { + MOZ_ASSERT(mRect.width > 0 && mRect.height > 0); + return IntSize(mRect.XMost(), mRect.YMost()); + } + + virtual void Flush() override; + virtual void DrawSurface(SourceSurface *aSurface, + const Rect &aDest, + const Rect &aSource, + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aOptions) override; + virtual void DrawFilter(FilterNode *aNode, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface *aSurface, + const Point &aDest, + const Color &aColor, + const Point &aOffset, + Float aSigma, + CompositionOp aOperator) override { /* Not implemented */ MOZ_CRASH("GFX: DrawSurfaceWithShadow"); } + + virtual void ClearRect(const Rect &aRect) override; + virtual void MaskSurface(const Pattern &aSource, + SourceSurface *aMask, + Point aOffset, + const DrawOptions &aOptions = DrawOptions()) override; + + virtual void CopySurface(SourceSurface *aSurface, + const IntRect &aSourceRect, + const IntPoint &aDestination) override; + + virtual void FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point &aStart, + const Point &aEnd, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void Stroke(const Path *aPath, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions = StrokeOptions(), + const DrawOptions &aOptions = DrawOptions()) override; + virtual void Fill(const Path *aPath, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void FillGlyphs(ScaledFont *aFont, + const GlyphBuffer &aBuffer, + const Pattern &aPattern, + const DrawOptions &aOptions = DrawOptions(), + const GlyphRenderingOptions *aRenderingOptions = nullptr) override; + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()) override; + virtual void PushClip(const Path *aPath) override; + virtual void PushClipRect(const Rect &aRect) override; + virtual void PopClip() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PopLayer() override; + + + virtual void SetTransform(const Matrix &aTransform) override; + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) const override + { + return mTiles[0].mDrawTarget->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat); + } + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override + { + return mTiles[0].mDrawTarget->OptimizeSourceSurface(aSurface); + } + + virtual already_AddRefed<SourceSurface> + CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override + { + return mTiles[0].mDrawTarget->CreateSourceSurfaceFromNativeSurface(aSurface); + } + + virtual already_AddRefed<DrawTarget> + CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override + { + return mTiles[0].mDrawTarget->CreateSimilarDrawTarget(aSize, aFormat); + } + + virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override + { + return mTiles[0].mDrawTarget->CreatePathBuilder(aFillRule); + } + + virtual already_AddRefed<GradientStops> + CreateGradientStops(GradientStop *aStops, + uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override + { + return mTiles[0].mDrawTarget->CreateGradientStops(aStops, aNumStops, aExtendMode); + } + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override + { + return mTiles[0].mDrawTarget->CreateFilter(aType); + } + +private: + std::vector<TileInternal> mTiles; + std::vector<std::vector<uint32_t> > mClippedOutTilesStack; + IntRect mRect; +}; + +class SnapshotTiled : public SourceSurface +{ +public: + SnapshotTiled(const std::vector<TileInternal>& aTiles, const IntRect& aRect) + : mRect(aRect) + { + for (size_t i = 0; i < aTiles.size(); i++) { + mSnapshots.push_back(aTiles[i].mDrawTarget->Snapshot()); + mOrigins.push_back(aTiles[i].mTileOrigin); + } + } + + virtual SurfaceType GetType() const { return SurfaceType::TILED; } + virtual IntSize GetSize() const { + MOZ_ASSERT(mRect.width > 0 && mRect.height > 0); + return IntSize(mRect.XMost(), mRect.YMost()); + } + virtual SurfaceFormat GetFormat() const { return mSnapshots[0]->GetFormat(); } + + virtual already_AddRefed<DataSourceSurface> GetDataSurface() + { + RefPtr<DataSourceSurface> surf = Factory::CreateDataSourceSurface(GetSize(), GetFormat()); + + DataSourceSurface::MappedSurface mappedSurf; + if (!surf->Map(DataSourceSurface::MapType::WRITE, &mappedSurf)) { + gfxCriticalError() << "DrawTargetTiled::GetDataSurface failed to map surface"; + return nullptr; + } + + { + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, mappedSurf.mData, + GetSize(), mappedSurf.mStride, GetFormat()); + + if (!dt) { + gfxWarning() << "DrawTargetTiled::GetDataSurface failed in CreateDrawTargetForData"; + surf->Unmap(); + return nullptr; + } + for (size_t i = 0; i < mSnapshots.size(); i++) { + RefPtr<DataSourceSurface> dataSurf = mSnapshots[i]->GetDataSurface(); + dt->CopySurface(dataSurf, IntRect(IntPoint(0, 0), mSnapshots[i]->GetSize()), mOrigins[i]); + } + } + surf->Unmap(); + + return surf.forget(); + } + + std::vector<RefPtr<SourceSurface>> mSnapshots; + std::vector<IntPoint> mOrigins; + IntRect mRect; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/DrawingJob.cpp b/gfx/2d/DrawingJob.cpp new file mode 100644 index 000000000..728e330f4 --- /dev/null +++ b/gfx/2d/DrawingJob.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawingJob.h" +#include "JobScheduler.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace gfx { + +DrawingJobBuilder::DrawingJobBuilder() +{} + +DrawingJobBuilder::~DrawingJobBuilder() +{ + MOZ_ASSERT(!mDrawTarget); +} + +void +DrawingJob::Clear() +{ + mCommandBuffer = nullptr; + mCursor = 0; +} + +void +DrawingJobBuilder::BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset, + SyncObject* aStart) +{ + MOZ_ASSERT(mCommandOffsets.empty()); + MOZ_ASSERT(aTarget); + mDrawTarget = aTarget; + mOffset = aOffset; + mStart = aStart; +} + +DrawingJob* +DrawingJobBuilder::EndDrawingJob(CommandBuffer* aCmdBuffer, + SyncObject* aCompletion, + WorkerThread* aPinToWorker) +{ + MOZ_ASSERT(mDrawTarget); + DrawingJob* task = new DrawingJob(mDrawTarget, mOffset, mStart, aCompletion, aPinToWorker); + task->mCommandBuffer = aCmdBuffer; + task->mCommandOffsets = Move(mCommandOffsets); + + mDrawTarget = nullptr; + mOffset = IntPoint(); + mStart = nullptr; + + return task; +} + +DrawingJob::DrawingJob(DrawTarget* aTarget, IntPoint aOffset, + SyncObject* aStart, SyncObject* aCompletion, + WorkerThread* aPinToWorker) +: Job(aStart, aCompletion, aPinToWorker) +, mCommandBuffer(nullptr) +, mCursor(0) +, mDrawTarget(aTarget) +, mOffset(aOffset) +{ + mCommandOffsets.reserve(64); +} + +JobStatus +DrawingJob::Run() +{ + while (mCursor < mCommandOffsets.size()) { + + const DrawingCommand* cmd = mCommandBuffer->GetDrawingCommand(mCommandOffsets[mCursor]); + + if (!cmd) { + return JobStatus::Error; + } + + cmd->ExecuteOnDT(mDrawTarget); + + ++mCursor; + } + + return JobStatus::Complete; +} + +DrawingJob::~DrawingJob() +{ + Clear(); +} + +const DrawingCommand* +CommandBuffer::GetDrawingCommand(ptrdiff_t aId) +{ + return static_cast<DrawingCommand*>(mStorage.GetStorage(aId)); +} + +CommandBuffer::~CommandBuffer() +{ + mStorage.ForEach([](void* item){ + static_cast<DrawingCommand*>(item)->~DrawingCommand(); + }); + mStorage.Clear(); +} + +void +CommandBufferBuilder::BeginCommandBuffer(size_t aBufferSize) +{ + MOZ_ASSERT(!mCommands); + mCommands = new CommandBuffer(aBufferSize); +} + +already_AddRefed<CommandBuffer> +CommandBufferBuilder::EndCommandBuffer() +{ + return mCommands.forget(); +} + +} // namespace +} // namespace diff --git a/gfx/2d/DrawingJob.h b/gfx/2d/DrawingJob.h new file mode 100644 index 000000000..a384dabda --- /dev/null +++ b/gfx/2d/DrawingJob.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMMANDBUFFER_H_ +#define MOZILLA_GFX_COMMANDBUFFER_H_ + +#include <stdint.h> + +#include "mozilla/RefPtr.h" +#include "mozilla/Assertions.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/JobScheduler.h" +#include "mozilla/gfx/IterableArena.h" +#include "mozilla/RefCounted.h" +#include "DrawCommand.h" + +namespace mozilla { +namespace gfx { + +class DrawingCommand; +class PrintCommand; +class SignalCommand; +class DrawingJob; +class WaitCommand; + +class SyncObject; +class MultiThreadedJobQueue; + +class DrawTarget; + +class DrawingJobBuilder; +class CommandBufferBuilder; + +/// Contains a sequence of immutable drawing commands that are typically used by +/// several DrawingJobs. +/// +/// CommandBuffer objects are built using CommandBufferBuilder. +class CommandBuffer : public external::AtomicRefCounted<CommandBuffer> +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(CommandBuffer) + + ~CommandBuffer(); + + const DrawingCommand* GetDrawingCommand(ptrdiff_t aId); + +protected: + explicit CommandBuffer(size_t aSize = 256) + : mStorage(IterableArena::GROWABLE, aSize) + {} + + IterableArena mStorage; + friend class CommandBufferBuilder; +}; + +/// Generates CommandBuffer objects. +/// +/// The builder is a separate object to ensure that commands are not added to a +/// submitted CommandBuffer. +class CommandBufferBuilder +{ +public: + void BeginCommandBuffer(size_t aBufferSize = 256); + + already_AddRefed<CommandBuffer> EndCommandBuffer(); + + /// Build the CommandBuffer, command after command. + /// This must be used between BeginCommandBuffer and EndCommandBuffer. + template<typename T, typename... Args> + ptrdiff_t AddCommand(Args&&... aArgs) + { + static_assert(IsBaseOf<DrawingCommand, T>::value, + "T must derive from DrawingCommand"); + return mCommands->mStorage.Alloc<T>(Forward<Args>(aArgs)...); + } + + bool HasCommands() const { return !!mCommands; } + +protected: + RefPtr<CommandBuffer> mCommands; +}; + +/// Stores multiple commands to be executed sequencially. +class DrawingJob : public Job { +public: + ~DrawingJob(); + + virtual JobStatus Run() override; + +protected: + DrawingJob(DrawTarget* aTarget, + IntPoint aOffset, + SyncObject* aStart, + SyncObject* aCompletion, + WorkerThread* aPinToWorker = nullptr); + + /// Runs the tasks's destructors and resets the buffer. + void Clear(); + + std::vector<ptrdiff_t> mCommandOffsets; + RefPtr<CommandBuffer> mCommandBuffer; + uint32_t mCursor; + + RefPtr<DrawTarget> mDrawTarget; + IntPoint mOffset; + + friend class DrawingJobBuilder; +}; + +/// Generates DrawingJob objects. +/// +/// The builder is a separate object to ensure that commands are not added to a +/// submitted DrawingJob. +class DrawingJobBuilder { +public: + DrawingJobBuilder(); + + ~DrawingJobBuilder(); + + /// Allocates a DrawingJob. + /// + /// call this method before starting to add commands. + void BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset, + SyncObject* aStart = nullptr); + + /// Build the DrawingJob, command after command. + /// This must be used between BeginDrawingJob and EndDrawingJob. + void AddCommand(ptrdiff_t offset) + { + mCommandOffsets.push_back(offset); + } + + /// Finalizes and returns the drawing task. + /// + /// If aCompletion is not null, the sync object will be signaled after the + /// task buffer is destroyed (and after the destructor of the tasks have run). + /// In most cases this means after the completion of all tasks in the task buffer, + /// but also when the task buffer is destroyed due to an error. + DrawingJob* EndDrawingJob(CommandBuffer* aCmdBuffer, + SyncObject* aCompletion = nullptr, + WorkerThread* aPinToWorker = nullptr); + + /// Returns true between BeginDrawingJob and EndDrawingJob, false otherwise. + bool HasDrawingJob() const { return !!mDrawTarget; } + +protected: + std::vector<ptrdiff_t> mCommandOffsets; + RefPtr<DrawTarget> mDrawTarget; + IntPoint mOffset; + RefPtr<SyncObject> mStart; +}; + +} // namespace +} // namespace + +#endif diff --git a/gfx/2d/ExtendInputEffectD2D1.cpp b/gfx/2d/ExtendInputEffectD2D1.cpp new file mode 100644 index 000000000..d48de53c3 --- /dev/null +++ b/gfx/2d/ExtendInputEffectD2D1.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ExtendInputEffectD2D1.h" + +#include "Logging.h" + +#include "ShadersD2D1.h" +#include "HelpersD2D.h" + +#include <vector> + +#define TEXTW(x) L##x +#define XML(X) TEXTW(#X) // This macro creates a single string from multiple lines of text. + +static const PCWSTR kXmlDescription = + XML( + <?xml version='1.0'?> + <Effect> + <!-- System Properties --> + <Property name='DisplayName' type='string' value='ExtendInputEffect'/> + <Property name='Author' type='string' value='Mozilla'/> + <Property name='Category' type='string' value='Utility Effects'/> + <Property name='Description' type='string' value='This effect is used to extend the output rect of any input effect to a specified rect.'/> + <Inputs> + <Input name='InputEffect'/> + </Inputs> + <Property name='OutputRect' type='vector4'> + <Property name='DisplayName' type='string' value='Output Rect'/> + </Property> + </Effect> + ); + +namespace mozilla { +namespace gfx { + +ExtendInputEffectD2D1::ExtendInputEffectD2D1() + : mRefCount(0) + , mOutputRect(D2D1::Vector4F(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX)) +{ +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, ID2D1TransformGraph* pTransformGraph) +{ + HRESULT hr; + hr = pTransformGraph->SetSingleTransformNode(this); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) +{ + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP_(ULONG) +ExtendInputEffectD2D1::AddRef() +{ + return ++mRefCount; +} + +IFACEMETHODIMP_(ULONG) +ExtendInputEffectD2D1::Release() +{ + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::QueryInterface(const IID &aIID, void **aPtr) +{ + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this)); + } else if (aIID == IID_ID2D1EffectImpl) { + *aPtr = static_cast<ID2D1EffectImpl*>(this); + } else if (aIID == IID_ID2D1DrawTransform) { + *aPtr = static_cast<ID2D1DrawTransform*>(this); + } else if (aIID == IID_ID2D1Transform) { + *aPtr = static_cast<ID2D1Transform*>(this); + } else if (aIID == IID_ID2D1TransformNode) { + *aPtr = static_cast<ID2D1TransformNode*>(this); + } else { + return E_NOINTERFACE; + } + + static_cast<IUnknown*>(*aPtr)->AddRef(); + return S_OK; +} + +static D2D1_RECT_L +ConvertFloatToLongRect(const D2D1_VECTOR_4F& aRect) +{ + // Clamp values to LONG range. We can't use std::min/max here because we want + // the comparison to operate on a type that's different from the type of the + // result. + return D2D1::RectL(aRect.x <= LONG_MIN ? LONG_MIN : LONG(aRect.x), + aRect.y <= LONG_MIN ? LONG_MIN : LONG(aRect.y), + aRect.z >= LONG_MAX ? LONG_MAX : LONG(aRect.z), + aRect.w >= LONG_MAX ? LONG_MAX : LONG(aRect.w)); +} + +static D2D1_RECT_L +IntersectRect(const D2D1_RECT_L& aRect1, const D2D1_RECT_L& aRect2) +{ + return D2D1::RectL(std::max(aRect1.left, aRect2.left), + std::max(aRect1.top, aRect2.top), + std::min(aRect1.right, aRect2.right), + std::min(aRect1.bottom, aRect2.bottom)); +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::MapInputRectsToOutputRect(const D2D1_RECT_L* pInputRects, + const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, + D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect) +{ + // This transform only accepts one input, so there will only be one input rect. + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + // Set the output rect to the specified rect. This is the whole purpose of this effect. + *pOutputRect = ConvertFloatToLongRect(mOutputRect); + *pOutputOpaqueSubRect = IntersectRect(*pOutputRect, pInputOpaqueSubRects[0]); + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const +{ + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pInputRects = *pOutputRect; + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::MapInvalidRect(UINT32 inputIndex, + D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const +{ + MOZ_ASSERT(inputIndex == 0); + + *pInvalidOutputRect = invalidInputRect; + return S_OK; +} + +HRESULT +ExtendInputEffectD2D1::Register(ID2D1Factory1 *aFactory) +{ + D2D1_PROPERTY_BINDING bindings[] = { + D2D1_VALUE_TYPE_BINDING(L"OutputRect", &ExtendInputEffectD2D1::SetOutputRect, &ExtendInputEffectD2D1::GetOutputRect), + }; + HRESULT hr = aFactory->RegisterEffectFromString(CLSID_ExtendInputEffect, kXmlDescription, bindings, 1, CreateEffect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to register extend input effect."; + } + return hr; +} + +void +ExtendInputEffectD2D1::Unregister(ID2D1Factory1 *aFactory) +{ + aFactory->UnregisterEffect(CLSID_ExtendInputEffect); +} + +HRESULT __stdcall +ExtendInputEffectD2D1::CreateEffect(IUnknown **aEffectImpl) +{ + *aEffectImpl = static_cast<ID2D1EffectImpl*>(new ExtendInputEffectD2D1()); + (*aEffectImpl)->AddRef(); + + return S_OK; +} + +} +} diff --git a/gfx/2d/ExtendInputEffectD2D1.h b/gfx/2d/ExtendInputEffectD2D1.h new file mode 100644 index 000000000..724c6a71e --- /dev/null +++ b/gfx/2d/ExtendInputEffectD2D1.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_EXTENDINPUTEFFECTD2D1_H_ +#define MOZILLA_GFX_EXTENDINPUTEFFECTD2D1_H_ + +#include <d2d1_1.h> +#include <d2d1effectauthor.h> +#include <d2d1effecthelpers.h> + +#include "2D.h" +#include "mozilla/Attributes.h" + +// {97143DC6-CBC4-4DD4-A8BA-13342B0BA46D} +DEFINE_GUID(CLSID_ExtendInputEffect, +0x5fb55c7c, 0xd795, 0x4ba3, 0xa9, 0x5c, 0x22, 0x82, 0x5d, 0x0c, 0x4d, 0xf7); + +namespace mozilla { +namespace gfx { + +enum { + EXTENDINPUT_PROP_OUTPUT_RECT = 0 +}; + +// An effect type that passes through its input unchanged but sets the effect's +// output rect to a specified rect. Unlike the built-in Crop effect, the +// ExtendInput effect can extend the input rect, and not just make it smaller. +// The added margins are filled with transparent black. +// Some effects have different output depending on their input effect's output +// rect, for example the Border effect (which repeats the edges of its input +// effect's output rect) or the component transfer and color matrix effects +// (which can transform transparent pixels into non-transparent ones, but only +// inside their input effect's output rect). +class ExtendInputEffectD2D1 final : public ID2D1EffectImpl + , public ID2D1DrawTransform +{ +public: + // ID2D1EffectImpl + IFACEMETHODIMP Initialize(ID2D1EffectContext* pContextInternal, ID2D1TransformGraph* pTransformGraph); + IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType); + IFACEMETHODIMP SetGraph(ID2D1TransformGraph* pGraph); + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppOutput); + + // ID2D1Transform + IFACEMETHODIMP MapInputRectsToOutputRect(const D2D1_RECT_L* pInputRects, + const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, + D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect); + IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const; + IFACEMETHODIMP MapInvalidRect(UINT32 inputIndex, + D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const; + + // ID2D1TransformNode + IFACEMETHODIMP_(UINT32) GetInputCount() const { return 1; } + + // ID2D1DrawTransform + IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo *pDrawInfo) { return S_OK; } + + static HRESULT Register(ID2D1Factory1* aFactory); + static void Unregister(ID2D1Factory1* aFactory); + static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl); + + HRESULT SetOutputRect(D2D1_VECTOR_4F aOutputRect) + { mOutputRect = aOutputRect; return S_OK; } + D2D1_VECTOR_4F GetOutputRect() const { return mOutputRect; } + +private: + ExtendInputEffectD2D1(); + + uint32_t mRefCount; + D2D1_VECTOR_4F mOutputRect; +}; + +} +} +#undef SIMPLE_PROP + +#endif diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp new file mode 100644 index 000000000..5cd5d14ea --- /dev/null +++ b/gfx/2d/Factory.cpp @@ -0,0 +1,985 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" + +#ifdef USE_CAIRO +#include "DrawTargetCairo.h" +#include "ScaledFontCairo.h" +#include "SourceSurfaceCairo.h" +#endif + +#ifdef USE_SKIA +#include "DrawTargetSkia.h" +#include "ScaledFontBase.h" +#ifdef MOZ_ENABLE_FREETYPE +#define USE_SKIA_FREETYPE +#include "ScaledFontCairo.h" +#endif +#endif + +#if defined(WIN32) +#include "ScaledFontWin.h" +#include "NativeFontResourceGDI.h" +#endif + +#ifdef XP_DARWIN +#include "ScaledFontMac.h" +#include "NativeFontResourceMac.h" +#endif + +#ifdef MOZ_WIDGET_GTK +#include "ScaledFontFontconfig.h" +#endif + +#ifdef WIN32 +#include "DrawTargetD2D1.h" +#include "ScaledFontDWrite.h" +#include "NativeFontResourceDWrite.h" +#include <d3d10_1.h> +#include "HelpersD2D.h" +#endif + +#include "DrawTargetDual.h" +#include "DrawTargetTiled.h" +#include "DrawTargetRecording.h" + +#include "SourceSurfaceRawData.h" + +#include "DrawEventRecorder.h" + +#include "Logging.h" + +#include "mozilla/CheckedInt.h" + +#if defined(MOZ_LOGGING) +GFX2D_API mozilla::LogModule* +GetGFX2DLog() +{ + static mozilla::LazyLogModule sLog("gfx2d"); + return sLog; +} +#endif + +// The following code was largely taken from xpcom/glue/SSE.cpp and +// made a little simpler. +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +#ifdef HAVE_CPUID_H + +#if !(defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) +// cpuid.h is available on gcc 4.3 and higher on i386 and x86_64 +#include <cpuid.h> + +static inline bool +HasCPUIDBit(unsigned int level, CPUIDRegister reg, unsigned int bit) +{ + unsigned int regs[4]; + return __get_cpuid(level, ®s[0], ®s[1], ®s[2], ®s[3]) && + (regs[reg] & bit); +} +#endif + +#define HAVE_CPU_DETECTION +#else + +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) +// MSVC 2005 or later supports __cpuid by intrin.h +#include <intrin.h> + +#define HAVE_CPU_DETECTION +#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__)) + +// Define a function identical to MSVC function. +#ifdef __i386 +static void +__cpuid(int CPUInfo[4], int InfoType) +{ + asm ( + "xchg %esi, %ebx\n" + "cpuid\n" + "movl %eax, (%edi)\n" + "movl %ebx, 4(%edi)\n" + "movl %ecx, 8(%edi)\n" + "movl %edx, 12(%edi)\n" + "xchg %esi, %ebx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %edi + : "%ecx", "%edx", "%esi" + ); +} +#else +static void +__cpuid(int CPUInfo[4], int InfoType) +{ + asm ( + "xchg %rsi, %rbx\n" + "cpuid\n" + "movl %eax, (%rdi)\n" + "movl %ebx, 4(%rdi)\n" + "movl %ecx, 8(%rdi)\n" + "movl %edx, 12(%rdi)\n" + "xchg %rsi, %rbx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %rdi + : "%ecx", "%edx", "%rsi" + ); +} + +#define HAVE_CPU_DETECTION +#endif +#endif + +#ifdef HAVE_CPU_DETECTION +static inline bool +HasCPUIDBit(unsigned int level, CPUIDRegister reg, unsigned int bit) +{ + // Check that the level in question is supported. + volatile int regs[4]; + __cpuid((int *)regs, level & 0x80000000u); + if (unsigned(regs[0]) < level) + return false; + __cpuid((int *)regs, level); + return !!(unsigned(regs[reg]) & bit); +} +#endif +#endif + +namespace mozilla { +namespace gfx { + +// In Gecko, this value is managed by gfx.logging.level in gfxPrefs. +int32_t LoggingPrefs::sGfxLogLevel = LOG_DEFAULT; + +#ifdef WIN32 +ID3D11Device *Factory::mD3D11Device = nullptr; +ID2D1Device *Factory::mD2D1Device = nullptr; +IDWriteFactory *Factory::mDWriteFactory = nullptr; +#endif + +DrawEventRecorder *Factory::mRecorder; + +mozilla::gfx::Config* Factory::sConfig = nullptr; + +void +Factory::Init(const Config& aConfig) +{ + MOZ_ASSERT(!sConfig); + sConfig = new Config(aConfig); + + // Make sure we don't completely break rendering because of a typo in the + // pref or whatnot. + const int32_t kMinAllocPref = 10000000; + const int32_t kMinSizePref = 2048; + if (sConfig->mMaxAllocSize < kMinAllocPref) { + sConfig->mMaxAllocSize = kMinAllocPref; + } + if (sConfig->mMaxTextureSize < kMinSizePref) { + sConfig->mMaxTextureSize = kMinSizePref; + } +} + +void +Factory::ShutDown() +{ + if (sConfig) { + delete sConfig->mLogForwarder; + delete sConfig; + sConfig = nullptr; + } +} + +bool +Factory::HasSSE2() +{ +#if defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2) + // gcc with -msse2 (default on OSX and x86-64) + // cl.exe with -arch:SSE2 (default on x64 compiler) + return true; +#elif defined(HAVE_CPU_DETECTION) + static enum { + UNINITIALIZED, + NO_SSE2, + HAS_SSE2 + } sDetectionState = UNINITIALIZED; + + if (sDetectionState == UNINITIALIZED) { + sDetectionState = HasCPUIDBit(1u, edx, (1u<<26)) ? HAS_SSE2 : NO_SSE2; + } + return sDetectionState == HAS_SSE2; +#else + return false; +#endif +} + +// If the size is "reasonable", we want gfxCriticalError to assert, so +// this is the option set up for it. +inline int LoggerOptionsBasedOnSize(const IntSize& aSize) +{ + return CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize)); +} + +bool +Factory::ReasonableSurfaceSize(const IntSize &aSize) +{ + return Factory::CheckSurfaceSize(aSize, 8192); +} + +bool +Factory::AllowedSurfaceSize(const IntSize &aSize) +{ + if (sConfig) { + return Factory::CheckSurfaceSize(aSize, + sConfig->mMaxTextureSize, + sConfig->mMaxAllocSize); + } + + return CheckSurfaceSize(aSize); +} + +bool +Factory::CheckBufferSize(int32_t bufSize) +{ + return !sConfig || bufSize < sConfig->mMaxAllocSize; +} + +bool +Factory::CheckSurfaceSize(const IntSize &sz, + int32_t extentLimit, + int32_t allocLimit) +{ + if (sz.width <= 0 || sz.height <= 0) { + gfxDebug() << "Surface width or height <= 0!"; + return false; + } + + // reject images with sides bigger than limit + if (extentLimit && (sz.width > extentLimit || sz.height > extentLimit)) { + gfxDebug() << "Surface size too large (exceeds extent limit)!"; + return false; + } + +#if defined(XP_MACOSX) + // CoreGraphics is limited to images < 32K in *height*, + // so clamp all surfaces on the Mac to that height + if (sz.height > SHRT_MAX) { + gfxDebug() << "Surface size too large (exceeds CoreGraphics limit)!"; + return false; + } +#endif + + // assuming 4 bytes per pixel, make sure the allocation size + // doesn't overflow a int32_t either + CheckedInt<int32_t> stride = GetAlignedStride<16>(sz.width, 4); + if (!stride.isValid() || stride.value() == 0) { + gfxDebug() << "Surface size too large (stride overflows int32_t)!"; + return false; + } + + CheckedInt<int32_t> numBytes = stride * sz.height; + if (!numBytes.isValid()) { + gfxDebug() << "Surface size too large (allocation size would overflow int32_t)!"; + return false; + } + + if (allocLimit && allocLimit < numBytes.value()) { + gfxDebug() << "Surface size too large (exceeds allocation limit)!"; + return false; + } + + return true; +} + +already_AddRefed<DrawTarget> +Factory::CreateDrawTarget(BackendType aBackend, const IntSize &aSize, SurfaceFormat aFormat) +{ + if (!AllowedSurfaceSize(aSize)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to allocate a surface due to invalid size (CDT) " << aSize; + return nullptr; + } + + RefPtr<DrawTarget> retVal; + switch (aBackend) { +#ifdef WIN32 + case BackendType::DIRECT2D1_1: + { + RefPtr<DrawTargetD2D1> newTarget; + newTarget = new DrawTargetD2D1(); + if (newTarget->Init(aSize, aFormat)) { + retVal = newTarget; + } + break; + } +#endif +#ifdef USE_SKIA + case BackendType::SKIA: + { + RefPtr<DrawTargetSkia> newTarget; + newTarget = new DrawTargetSkia(); + if (newTarget->Init(aSize, aFormat)) { + retVal = newTarget; + } + break; + } +#endif +#ifdef USE_CAIRO + case BackendType::CAIRO: + { + RefPtr<DrawTargetCairo> newTarget; + newTarget = new DrawTargetCairo(); + if (newTarget->Init(aSize, aFormat)) { + retVal = newTarget; + } + break; + } +#endif + default: + return nullptr; + } + + if (mRecorder && retVal) { + return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal); + } + + if (!retVal) { + // Failed + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to create DrawTarget, Type: " << int(aBackend) << " Size: " << aSize; + } + + return retVal.forget(); +} + +already_AddRefed<DrawTarget> +Factory::CreateRecordingDrawTarget(DrawEventRecorder *aRecorder, DrawTarget *aDT) +{ + return MakeAndAddRef<DrawTargetRecording>(aRecorder, aDT); +} + +already_AddRefed<DrawTarget> +Factory::CreateDrawTargetForData(BackendType aBackend, + unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat, + bool aUninitialized) +{ + MOZ_ASSERT(aData); + if (!AllowedSurfaceSize(aSize)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to allocate a surface due to invalid size (DTD) " << aSize; + return nullptr; + } + + RefPtr<DrawTarget> retVal; + + switch (aBackend) { +#ifdef USE_SKIA + case BackendType::SKIA: + { + RefPtr<DrawTargetSkia> newTarget; + newTarget = new DrawTargetSkia(); + if (newTarget->Init(aData, aSize, aStride, aFormat, aUninitialized)) { + retVal = newTarget; + } + break; + } +#endif +#ifdef USE_CAIRO + case BackendType::CAIRO: + { + RefPtr<DrawTargetCairo> newTarget; + newTarget = new DrawTargetCairo(); + if (newTarget->Init(aData, aSize, aStride, aFormat)) { + retVal = newTarget.forget(); + } + break; + } +#endif + default: + gfxCriticalNote << "Invalid draw target type specified: " << (int)aBackend; + return nullptr; + } + + if (mRecorder && retVal) { + return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal, true); + } + + if (!retVal) { + gfxCriticalNote << "Failed to create DrawTarget, Type: " << int(aBackend) << " Size: " << aSize << ", Data: " << hexa((void *)aData) << ", Stride: " << aStride; + } + + return retVal.forget(); +} + +already_AddRefed<DrawTarget> +Factory::CreateTiledDrawTarget(const TileSet& aTileSet) +{ + RefPtr<DrawTargetTiled> dt = new DrawTargetTiled(); + + if (!dt->Init(aTileSet)) { + return nullptr; + } + + return dt.forget(); +} + +bool +Factory::DoesBackendSupportDataDrawtarget(BackendType aType) +{ + switch (aType) { + case BackendType::DIRECT2D: + case BackendType::DIRECT2D1_1: + case BackendType::RECORDING: + case BackendType::NONE: + case BackendType::BACKEND_LAST: + return false; + case BackendType::CAIRO: + case BackendType::SKIA: + return true; + } + + return false; +} + +uint32_t +Factory::GetMaxSurfaceSize(BackendType aType) +{ + switch (aType) { + case BackendType::CAIRO: + return DrawTargetCairo::GetMaxSurfaceSize(); +#ifdef USE_SKIA + case BackendType::SKIA: + return DrawTargetSkia::GetMaxSurfaceSize(); +#endif +#ifdef WIN32 + case BackendType::DIRECT2D1_1: + return DrawTargetD2D1::GetMaxSurfaceSize(); +#endif + default: + return 0; + } +} + +already_AddRefed<ScaledFont> +Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize) +{ + switch (aNativeFont.mType) { +#ifdef WIN32 + case NativeFontType::DWRITE_FONT_FACE: + { + return MakeAndAddRef<ScaledFontDWrite>(static_cast<IDWriteFontFace*>(aNativeFont.mFont), aSize); + } +#if defined(USE_CAIRO) || defined(USE_SKIA) + case NativeFontType::GDI_FONT_FACE: + { + return MakeAndAddRef<ScaledFontWin>(static_cast<LOGFONT*>(aNativeFont.mFont), aSize); + } +#endif +#endif +#ifdef XP_DARWIN + case NativeFontType::MAC_FONT_FACE: + { + return MakeAndAddRef<ScaledFontMac>(static_cast<CGFontRef>(aNativeFont.mFont), aSize); + } +#endif +#if defined(USE_CAIRO) || defined(USE_SKIA_FREETYPE) + case NativeFontType::CAIRO_FONT_FACE: + { + return MakeAndAddRef<ScaledFontCairo>(static_cast<cairo_scaled_font_t*>(aNativeFont.mFont), aSize); + } +#endif + default: + gfxWarning() << "Invalid native font type specified."; + return nullptr; + } +} + +already_AddRefed<NativeFontResource> +Factory::CreateNativeFontResource(uint8_t *aData, uint32_t aSize, + FontType aType) +{ + switch (aType) { +#ifdef WIN32 + case FontType::DWRITE: + { + return NativeFontResourceDWrite::Create(aData, aSize, + /* aNeedsCairo = */ false); + } +#endif + case FontType::CAIRO: +#ifdef USE_SKIA + case FontType::SKIA: +#endif + { +#ifdef WIN32 + if (GetDWriteFactory()) { + return NativeFontResourceDWrite::Create(aData, aSize, + /* aNeedsCairo = */ true); + } else { + return NativeFontResourceGDI::Create(aData, aSize, + /* aNeedsCairo = */ true); + } +#elif XP_DARWIN + return NativeFontResourceMac::Create(aData, aSize); +#else + gfxWarning() << "Unable to create cairo scaled font from truetype data"; + return nullptr; +#endif + } + default: + gfxWarning() << "Unable to create requested font resource from truetype data"; + return nullptr; + } +} + +already_AddRefed<ScaledFont> +Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont, Float aSize, cairo_scaled_font_t* aScaledFont) +{ +#ifdef USE_CAIRO + // In theory, we could pull the NativeFont out of the cairo_scaled_font_t*, + // but that would require a lot of code that would be otherwise repeated in + // various backends. + // Therefore, we just reuse CreateScaledFontForNativeFont's implementation. + RefPtr<ScaledFont> font = CreateScaledFontForNativeFont(aNativeFont, aSize); + static_cast<ScaledFontBase*>(font.get())->SetCairoScaledFont(aScaledFont); + return font.forget(); +#else + return nullptr; +#endif +} + +#ifdef MOZ_WIDGET_GTK +already_AddRefed<ScaledFont> +Factory::CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize) +{ + return MakeAndAddRef<ScaledFontFontconfig>(aScaledFont, aPattern, aSize); +} +#endif + +already_AddRefed<DrawTarget> +Factory::CreateDualDrawTarget(DrawTarget *targetA, DrawTarget *targetB) +{ + MOZ_ASSERT(targetA && targetB); + + RefPtr<DrawTarget> newTarget = + new DrawTargetDual(targetA, targetB); + + RefPtr<DrawTarget> retVal = newTarget; + + if (mRecorder) { + retVal = new DrawTargetRecording(mRecorder, retVal); + } + + return retVal.forget(); +} + + +#ifdef WIN32 +already_AddRefed<DrawTarget> +Factory::CreateDrawTargetForD3D11Texture(ID3D11Texture2D *aTexture, SurfaceFormat aFormat) +{ + MOZ_ASSERT(aTexture); + + RefPtr<DrawTargetD2D1> newTarget; + + newTarget = new DrawTargetD2D1(); + if (newTarget->Init(aTexture, aFormat)) { + RefPtr<DrawTarget> retVal = newTarget; + + if (mRecorder) { + retVal = new DrawTargetRecording(mRecorder, retVal, true); + } + + return retVal.forget(); + } + + gfxWarning() << "Failed to create draw target for D3D11 texture."; + + // Failed + return nullptr; +} + +bool +Factory::SetDWriteFactory(IDWriteFactory *aFactory) +{ + mDWriteFactory = aFactory; + return true; +} + +bool +Factory::SetDirect3D11Device(ID3D11Device *aDevice) +{ + mD3D11Device = aDevice; + + if (mD2D1Device) { + mD2D1Device->Release(); + mD2D1Device = nullptr; + } + + if (!aDevice) { + return true; + } + + RefPtr<ID2D1Factory1> factory = D2DFactory1(); + + RefPtr<IDXGIDevice> device; + aDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(device)); + HRESULT hr = factory->CreateDevice(device, &mD2D1Device); + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1] Failed to create gfx factory's D2D1 device, code: " << hexa(hr); + + mD3D11Device = nullptr; + return false; + } + + return true; +} + +ID3D11Device* +Factory::GetDirect3D11Device() +{ + return mD3D11Device; +} + +ID2D1Device* +Factory::GetD2D1Device() +{ + return mD2D1Device; +} + +IDWriteFactory* +Factory::GetDWriteFactory() +{ + return mDWriteFactory; +} + +bool +Factory::SupportsD2D1() +{ + return !!D2DFactory1(); +} + +already_AddRefed<GlyphRenderingOptions> +Factory::CreateDWriteGlyphRenderingOptions(IDWriteRenderingParams *aParams) +{ + return MakeAndAddRef<GlyphRenderingOptionsDWrite>(aParams); +} + +uint64_t +Factory::GetD2DVRAMUsageDrawTarget() +{ + return DrawTargetD2D1::mVRAMUsageDT; +} + +uint64_t +Factory::GetD2DVRAMUsageSourceSurface() +{ + return DrawTargetD2D1::mVRAMUsageSS; +} + +void +Factory::D2DCleanup() +{ + if (mD2D1Device) { + mD2D1Device->Release(); + mD2D1Device = nullptr; + } + DrawTargetD2D1::CleanupD2D(); +} + +already_AddRefed<ScaledFont> +Factory::CreateScaledFontForDWriteFont(IDWriteFontFace* aFontFace, + const gfxFontStyle* aStyle, + float aSize, + bool aUseEmbeddedBitmap, + bool aForceGDIMode) +{ + return MakeAndAddRef<ScaledFontDWrite>(aFontFace, aSize, + aUseEmbeddedBitmap, aForceGDIMode, + aStyle); +} + +#endif // XP_WIN + +#ifdef USE_SKIA_GPU +already_AddRefed<DrawTarget> +Factory::CreateDrawTargetSkiaWithGrContext(GrContext* aGrContext, + const IntSize &aSize, + SurfaceFormat aFormat) +{ + RefPtr<DrawTarget> newTarget = new DrawTargetSkia(); + if (!newTarget->InitWithGrContext(aGrContext, aSize, aFormat)) { + return nullptr; + } + return newTarget.forget(); +} + +#endif // USE_SKIA_GPU + +#ifdef USE_SKIA +already_AddRefed<DrawTarget> +Factory::CreateDrawTargetWithSkCanvas(SkCanvas* aCanvas) +{ + RefPtr<DrawTargetSkia> newTarget = new DrawTargetSkia(); + if (!newTarget->Init(aCanvas)) { + return nullptr; + } + return newTarget.forget(); +} +#endif + +void +Factory::PurgeAllCaches() +{ +} + +already_AddRefed<DrawTarget> +Factory::CreateDrawTargetForCairoSurface(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat) +{ + if (!AllowedSurfaceSize(aSize)) { + gfxWarning() << "Allowing surface with invalid size (Cairo) " << aSize; + } + + RefPtr<DrawTarget> retVal; + +#ifdef USE_CAIRO + RefPtr<DrawTargetCairo> newTarget = new DrawTargetCairo(); + + if (newTarget->Init(aSurface, aSize, aFormat)) { + retVal = newTarget; + } + + if (mRecorder && retVal) { + return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal, true); + } +#endif + return retVal.forget(); +} + +already_AddRefed<SourceSurface> +Factory::CreateSourceSurfaceForCairoSurface(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat aFormat) +{ + if (aSize.width <= 0 || aSize.height <= 0) { + gfxWarning() << "Can't create a SourceSurface without a valid size"; + return nullptr; + } + +#ifdef USE_CAIRO + return MakeAndAddRef<SourceSurfaceCairo>(aSurface, aSize, aFormat); +#else + return nullptr; +#endif +} + +already_AddRefed<DataSourceSurface> +Factory::CreateWrappingDataSourceSurface(uint8_t *aData, + int32_t aStride, + const IntSize &aSize, + SurfaceFormat aFormat, + SourceSurfaceDeallocator aDeallocator /* = nullptr */, + void* aClosure /* = nullptr */) +{ + // Just check for negative/zero size instead of the full AllowedSurfaceSize() - since + // the data is already allocated we do not need to check for a possible overflow - it + // already worked. + if (aSize.width <= 0 || aSize.height <= 0) { + return nullptr; + } + if (!aDeallocator && aClosure) { + return nullptr; + } + + MOZ_ASSERT(aData); + + RefPtr<SourceSurfaceRawData> newSurf = new SourceSurfaceRawData(); + newSurf->InitWrappingData(aData, aSize, aStride, aFormat, aDeallocator, aClosure); + + return newSurf.forget(); +} + +#ifdef XP_DARWIN +already_AddRefed<GlyphRenderingOptions> +Factory::CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor) +{ + return MakeAndAddRef<GlyphRenderingOptionsCG>(aFontSmoothingBackgroundColor); +} +#endif + +already_AddRefed<DataSourceSurface> +Factory::CreateDataSourceSurface(const IntSize &aSize, + SurfaceFormat aFormat, + bool aZero) +{ + if (!AllowedSurfaceSize(aSize)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "Failed to allocate a surface due to invalid size (DSS) " << aSize; + return nullptr; + } + + // Skia doesn't support RGBX, so memset RGBX to 0xFF + bool clearSurface = aZero || aFormat == SurfaceFormat::B8G8R8X8; + uint8_t clearValue = aFormat == SurfaceFormat::B8G8R8X8 ? 0xFF : 0; + + RefPtr<SourceSurfaceAlignedRawData> newSurf = new SourceSurfaceAlignedRawData(); + if (newSurf->Init(aSize, aFormat, clearSurface, clearValue)) { + return newSurf.forget(); + } + + gfxWarning() << "CreateDataSourceSurface failed in init"; + return nullptr; +} + +already_AddRefed<DataSourceSurface> +Factory::CreateDataSourceSurfaceWithStride(const IntSize &aSize, + SurfaceFormat aFormat, + int32_t aStride, + bool aZero) +{ + if (!AllowedSurfaceSize(aSize) || + aStride < aSize.width * BytesPerPixel(aFormat)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "CreateDataSourceSurfaceWithStride failed with bad stride " << aStride << ", " << aSize << ", " << aFormat; + return nullptr; + } + + // Skia doesn't support RGBX, so memset RGBX to 0xFF + bool clearSurface = aZero || aFormat == SurfaceFormat::B8G8R8X8; + uint8_t clearValue = aFormat == SurfaceFormat::B8G8R8X8 ? 0xFF : 0; + + RefPtr<SourceSurfaceAlignedRawData> newSurf = new SourceSurfaceAlignedRawData(); + if (newSurf->Init(aSize, aFormat, clearSurface, clearValue, aStride)) { + return newSurf.forget(); + } + + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "CreateDataSourceSurfaceWithStride failed to initialize " << aSize << ", " << aFormat << ", " << aStride << ", " << aZero; + return nullptr; +} + +static uint16_t +PackRGB565(uint8_t r, uint8_t g, uint8_t b) +{ + uint16_t pixel = ((r << 11) & 0xf800) | + ((g << 5) & 0x07e0) | + ((b ) & 0x001f); + + return pixel; +} + +void +Factory::CopyDataSourceSurface(DataSourceSurface* aSource, + DataSourceSurface* aDest) +{ + // Don't worry too much about speed. + MOZ_ASSERT(aSource->GetSize() == aDest->GetSize()); + MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 || + aSource->GetFormat() == SurfaceFormat::R8G8B8X8 || + aSource->GetFormat() == SurfaceFormat::B8G8R8A8 || + aSource->GetFormat() == SurfaceFormat::B8G8R8X8); + MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 || + aDest->GetFormat() == SurfaceFormat::R8G8B8X8 || + aDest->GetFormat() == SurfaceFormat::B8G8R8A8 || + aDest->GetFormat() == SurfaceFormat::B8G8R8X8 || + aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16); + + const bool isSrcBGR = aSource->GetFormat() == SurfaceFormat::B8G8R8A8 || + aSource->GetFormat() == SurfaceFormat::B8G8R8X8; + const bool isDestBGR = aDest->GetFormat() == SurfaceFormat::B8G8R8A8 || + aDest->GetFormat() == SurfaceFormat::B8G8R8X8; + const bool needsSwap02 = isSrcBGR != isDestBGR; + + const bool srcHasAlpha = aSource->GetFormat() == SurfaceFormat::R8G8B8A8 || + aSource->GetFormat() == SurfaceFormat::B8G8R8A8; + const bool destHasAlpha = aDest->GetFormat() == SurfaceFormat::R8G8B8A8 || + aDest->GetFormat() == SurfaceFormat::B8G8R8A8; + const bool needsAlphaMask = !srcHasAlpha && destHasAlpha; + + const bool needsConvertTo16Bits = aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16; + + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!aSource->Map(DataSourceSurface::MapType::READ, &srcMap) || + !aDest->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + MOZ_ASSERT(false, "CopyDataSourceSurface: Failed to map surface."); + return; + } + + MOZ_ASSERT(srcMap.mStride >= 0); + MOZ_ASSERT(destMap.mStride >= 0); + + const size_t srcBPP = BytesPerPixel(aSource->GetFormat()); + const size_t srcRowBytes = aSource->GetSize().width * srcBPP; + const size_t srcRowHole = srcMap.mStride - srcRowBytes; + + const size_t destBPP = BytesPerPixel(aDest->GetFormat()); + const size_t destRowBytes = aDest->GetSize().width * destBPP; + const size_t destRowHole = destMap.mStride - destRowBytes; + + uint8_t* srcRow = srcMap.mData; + uint8_t* destRow = destMap.mData; + const size_t rows = aSource->GetSize().height; + for (size_t i = 0; i < rows; i++) { + const uint8_t* srcRowEnd = srcRow + srcRowBytes; + + while (srcRow != srcRowEnd) { + uint8_t d0 = needsSwap02 ? srcRow[2] : srcRow[0]; + uint8_t d1 = srcRow[1]; + uint8_t d2 = needsSwap02 ? srcRow[0] : srcRow[2]; + uint8_t d3 = needsAlphaMask ? 0xff : srcRow[3]; + + if (needsConvertTo16Bits) { + *(uint16_t*)destRow = PackRGB565(d0, d1, d2); + } else { + destRow[0] = d0; + destRow[1] = d1; + destRow[2] = d2; + destRow[3] = d3; + } + srcRow += srcBPP; + destRow += destBPP; + } + + srcRow += srcRowHole; + destRow += destRowHole; + } + + aSource->Unmap(); + aDest->Unmap(); +} + +already_AddRefed<DrawEventRecorder> +Factory::CreateEventRecorderForFile(const char *aFilename) +{ + return MakeAndAddRef<DrawEventRecorderFile>(aFilename); +} + +void +Factory::SetGlobalEventRecorder(DrawEventRecorder *aRecorder) +{ + mRecorder = aRecorder; +} + +// static +void +CriticalLogger::OutputMessage(const std::string &aString, + int aLevel, bool aNoNewline) +{ + if (Factory::GetLogForwarder()) { + Factory::GetLogForwarder()->Log(aString); + } + + BasicLogger::OutputMessage(aString, aLevel, aNoNewline); +} + +void +CriticalLogger::CrashAction(LogReason aReason) +{ + if (Factory::GetLogForwarder()) { + Factory::GetLogForwarder()->CrashAction(aReason); + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterNodeD2D1.cpp b/gfx/2d/FilterNodeD2D1.cpp new file mode 100644 index 000000000..9f4ded23c --- /dev/null +++ b/gfx/2d/FilterNodeD2D1.cpp @@ -0,0 +1,1102 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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; + } + + 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() || aDT->IsDualDrawTarget()) { + gfxDevCrash(LogReason::FilterNodeD2D1Target) << "Incompatible draw target type! " << (int)aDT->IsTiledDrawTarget() << " " << (int)aDT->IsDualDrawTarget(); + 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; + } + + 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; + } +} + +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; + } + + 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; + } + return false; +} + +static inline REFCLSID GetCLDIDForFilterType(FilterType aType) +{ + switch (aType) { + 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; + } + 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); + } + + 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::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(); + MOZ_ASSERT(input < effect->GetInputCount()); + + 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); + } + } + + MOZ_ASSERT(input < effect->GetInputCount()); + + mInputSurfaces.resize(effect->GetInputCount()); + mInputFilters.resize(effect->GetInputCount()); + + // 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); + } + } + + MOZ_ASSERT(input < effect->GetInputCount()); + + 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(effect->GetInputCount()); + mInputFilters.resize(effect->GetInputCount()); + + // 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 Color &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)); +} + +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()); +} + +} +} diff --git a/gfx/2d/FilterNodeD2D1.h b/gfx/2d/FilterNodeD2D1.h new file mode 100644 index 000000000..8c4c6df30 --- /dev/null +++ b/gfx/2d/FilterNodeD2D1.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FILTERNODED2D1_H_ +#define MOZILLA_GFX_FILTERNODED2D1_H_ + +#include "2D.h" +#include "Filters.h" +#include <vector> +#include <d2d1_1.h> +#include <cguid.h> + +namespace mozilla { +namespace gfx { + +class FilterNodeD2D1 : public FilterNode +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeD2D1) + static already_AddRefed<FilterNode> Create(ID2D1DeviceContext *aDC, FilterType aType); + + FilterNodeD2D1(ID2D1Effect *aEffect, FilterType aType) + : mEffect(aEffect) + , mType(aType) + { + InitUnmappedProperties(); + } + + virtual FilterBackend GetBackendType() { return FILTER_BACKEND_DIRECT2D1_1; } + + virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface); + virtual void SetInput(uint32_t aIndex, FilterNode *aFilter); + + virtual void SetAttribute(uint32_t aIndex, uint32_t aValue); + virtual void SetAttribute(uint32_t aIndex, Float aValue); + virtual void SetAttribute(uint32_t aIndex, const Point &aValue); + virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &aValue); + virtual void SetAttribute(uint32_t aIndex, const Point3D &aValue); + virtual void SetAttribute(uint32_t aIndex, const Size &aValue); + virtual void SetAttribute(uint32_t aIndex, const IntSize &aValue); + virtual void SetAttribute(uint32_t aIndex, const Color &aValue); + virtual void SetAttribute(uint32_t aIndex, const Rect &aValue); + virtual void SetAttribute(uint32_t aIndex, const IntRect &aValue); + virtual void SetAttribute(uint32_t aIndex, bool aValue); + virtual void SetAttribute(uint32_t aIndex, const Float *aValues, uint32_t aSize); + virtual void SetAttribute(uint32_t aIndex, const IntPoint &aValue); + virtual void SetAttribute(uint32_t aIndex, const Matrix &aValue); + + // Called by DrawTarget before it draws our OutputEffect, and recursively + // by the filter nodes that have this filter as one of their inputs. This + // gives us a chance to convert any input surfaces to the target format for + // the DrawTarget that we will draw to. + virtual void WillDraw(DrawTarget *aDT); + + virtual ID2D1Effect* MainEffect() { return mEffect.get(); } + virtual ID2D1Effect* InputEffect() { return mEffect.get(); } + virtual ID2D1Effect* OutputEffect() { return mEffect.get(); } + +protected: + friend class DrawTargetD2D1; + friend class DrawTargetD2D; + friend class FilterNodeConvolveD2D1; + + void InitUnmappedProperties(); + + RefPtr<ID2D1Effect> mEffect; + std::vector<RefPtr<FilterNodeD2D1>> mInputFilters; + std::vector<RefPtr<SourceSurface>> mInputSurfaces; + FilterType mType; +}; + +class FilterNodeConvolveD2D1 : public FilterNodeD2D1 +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveD2D1, override) + FilterNodeConvolveD2D1(ID2D1DeviceContext *aDC); + + virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override; + + virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override; + virtual void SetAttribute(uint32_t aIndex, const IntSize &aValue) override; + virtual void SetAttribute(uint32_t aIndex, const IntPoint &aValue) override; + virtual void SetAttribute(uint32_t aIndex, const IntRect &aValue) override; + + virtual ID2D1Effect* InputEffect() override; + +private: + void UpdateChain(); + void UpdateOffset(); + void UpdateSourceRect(); + + RefPtr<ID2D1Effect> mExtendInputEffect; + RefPtr<ID2D1Effect> mBorderEffect; + ConvolveMatrixEdgeMode mEdgeMode; + IntPoint mTarget; + IntSize mKernelSize; + IntRect mSourceRect; +}; + +class FilterNodeExtendInputAdapterD2D1 : public FilterNodeD2D1 +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeExtendInputAdapterD2D1, override) + FilterNodeExtendInputAdapterD2D1(ID2D1DeviceContext *aDC, FilterNodeD2D1 *aFilterNode, FilterType aType); + + virtual ID2D1Effect* InputEffect() override { return mExtendInputEffect.get(); } + virtual ID2D1Effect* OutputEffect() override { return mWrappedFilterNode->OutputEffect(); } + +private: + RefPtr<FilterNodeD2D1> mWrappedFilterNode; + RefPtr<ID2D1Effect> mExtendInputEffect; +}; + +class FilterNodePremultiplyAdapterD2D1 : public FilterNodeD2D1 +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplyAdapterD2D1, override) + FilterNodePremultiplyAdapterD2D1(ID2D1DeviceContext *aDC, FilterNodeD2D1 *aFilterNode, FilterType aType); + + virtual ID2D1Effect* InputEffect() override { return mPrePremultiplyEffect.get(); } + virtual ID2D1Effect* OutputEffect() override { return mPostUnpremultiplyEffect.get(); } + +private: + RefPtr<ID2D1Effect> mPrePremultiplyEffect; + RefPtr<ID2D1Effect> mPostUnpremultiplyEffect; +}; + +} +} + +#endif diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp new file mode 100644 index 000000000..3abdb7a02 --- /dev/null +++ b/gfx/2d/FilterNodeSoftware.cpp @@ -0,0 +1,3689 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <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); +} + +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.x = 0; + fill.width = aFromRect.x; + sampleRect.x = fill.XMost(); + sampleRect.width = 1; + break; + case 1: + fill.x = aFromRect.x; + fill.width = aFromRect.width; + sampleRect.x = fill.x; + sampleRect.width = fill.width; + break; + case 2: + fill.x = aFromRect.XMost(); + fill.width = size.width - fill.x; + sampleRect.x = fill.x - 1; + sampleRect.width = 1; + break; + } + if (fill.width <= 0) { + continue; + } + bool xIsMiddle = (ix == 1); + for (int32_t iy = 0; iy < 3; iy++) { + switch (iy) { + case 0: + fill.y = 0; + fill.height = aFromRect.y; + sampleRect.y = fill.YMost(); + sampleRect.height = 1; + break; + case 1: + fill.y = aFromRect.y; + fill.height = aFromRect.height; + sampleRect.y = fill.y; + sampleRect.height = fill.height; + break; + case 2: + fill.y = aFromRect.YMost(); + fill.height = size.height - fill.y; + sampleRect.y = fill.y - 1; + sampleRect.height = 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); + 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::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(Color(0.0, 0.0, 0.0, 1.0)), + 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; + } + + if (!mCachedRect.Contains(aRect)) { + RequestRect(aRect); + mCachedOutput = Render(mRequestedRect); + if (!mCachedOutput) { + mCachedRect = IntRect(); + mRequestedRect = IntRect(); + return nullptr; + } + mCachedRect = mRequestedRect; + mRequestedRect = IntRect(); + } else { + MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?"); + } + return GetDataSurfaceInRect(mCachedOutput, mCachedRect, 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); +} + +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 = IntRect(IntPoint(0, 0), surface->GetSize()); + } 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); + surfaceRect = srcRect; + } + + 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(IntRect(IntPoint(0, 0), + mInputSurfaces[inputIndex]->GetSize())); + } + 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() +{ + MOZ_ASSERT(!mInvalidationListeners.size(), + "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; + default: + return CompositionOp::OP_OVER; + } + + 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::CAIRO, + 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::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); +} + +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::CAIRO, + 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(); +} + +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::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 Color &aColor) +{ + MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR); + mColor = aColor; + Invalidate(); +} + +static uint32_t +ColorToBGRA(const Color& 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(Color 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::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 = IntRect(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) { + static uint8_t sIdentityLookupTable[256]; + static bool sInitializedIdentityLookupTable = false; + if (!sInitializedIdentityLookupTable) { + for (int32_t i = 0; i < 256; i++) { + sIdentityLookupTable[i] = i; + } + sInitializedIdentityLookupTable = true; + } + memcpy(aTables[aComponent], sIdentityLookupTable, 256); + } 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); + } +} + +bool +IsAllZero(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::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) +{} + +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 bool sColorSamplingAccessControlEnabled = false; +static uint8_t* sColorSamplingAccessControlStart = nullptr; +static uint8_t* sColorSamplingAccessControlEnd = nullptr; + +struct DebugOnlyAutoColorSamplingAccessControl +{ + explicit DebugOnlyAutoColorSamplingAccessControl(DataSourceSurface* aSurface) + { + sColorSamplingAccessControlStart = aSurface->GetData(); + sColorSamplingAccessControlEnd = sColorSamplingAccessControlStart + + aSurface->Stride() * aSurface->GetSize().height; + sColorSamplingAccessControlEnabled = true; + } + + ~DebugOnlyAutoColorSamplingAccessControl() + { + sColorSamplingAccessControlEnabled = false; + } +}; + +static inline void +DebugOnlyCheckColorSamplingAccess(const uint8_t* aSampleAddress) +{ + if (sColorSamplingAccessControlEnabled) { + MOZ_ASSERT(aSampleAddress >= sColorSamplingAccessControlStart, "accessing before start"); + MOZ_ASSERT(aSampleAddress < sColorSamplingAccessControlEnd, "accessing after end"); + } +} +#else +typedef DebugOnly<DataSourceSurface*> DebugOnlyAutoColorSamplingAccessControl; +#define DebugOnlyCheckColorSamplingAccess(address) +#endif + +static inline uint8_t +ColorComponentAtPoint(const uint8_t *aData, int32_t aStride, int32_t x, int32_t y, size_t bpp, ptrdiff_t c) +{ + DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c]); + return aData[y * aStride + bpp * x + c]; +} + +static inline int32_t +ColorAtPoint(const uint8_t *aData, int32_t aStride, int32_t x, int32_t y) +{ + DebugOnlyCheckColorSamplingAccess(aData + y * aStride + 4 * x); + 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, int32_t aStride, Float x, Float y, size_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, lx, ly, bpp, c); + const uint8_t &cul = ColorComponentAtPoint(aData, aStride, lx + 1, ly, bpp, c); + const uint8_t &clu = ColorComponentAtPoint(aData, aStride, lx, ly + 1, bpp, c); + const uint8_t &cuu = ColorComponentAtPoint(aData, aStride, 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, + 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, + 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; + } + + DebugOnlyAutoColorSamplingAccessControl accessControl(input); + + 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* 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, + 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::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* 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, 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::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); +} + +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_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::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: + // 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::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::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() + : 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::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::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::GetOutputRectInRect(const IntRect& aRect) +{ + return GetInputRectInRect(IN_UNPREMULTIPLY_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(DEBUG) || defined(FORCE_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 Color &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, + 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, -dx, -dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, dx, -dy, 1, 0) + + -2 * ColorComponentAtPoint(index, stride, -dx, zero, 1, 0) + + 2 * ColorComponentAtPoint(index, stride, dx, zero, 1, 0) + + -1 * ColorComponentAtPoint(index, stride, -dx, dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, dx, dy, 1, 0); + + int16_t normalY = + -1 * ColorComponentAtPoint(index, stride, -dx, -dy, 1, 0) + + -2 * ColorComponentAtPoint(index, stride, zero, -dy, 1, 0) + + -1 * ColorComponentAtPoint(index, stride, dx, -dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, -dx, dy, 1, 0) + + 2 * ColorComponentAtPoint(index, stride, zero, dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, 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> 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); + } + + DebugOnlyAutoColorSamplingAccessControl accessControl(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* 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, + 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 = Normalized(aVectorToLight + vectorToEye); + 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 000000000..7ad27ec0f --- /dev/null +++ b/gfx/2d/FilterNodeSoftware.h @@ -0,0 +1,724 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_FILTERNODESOFTWARE_H_ +#define _MOZILLA_GFX_FILTERNODESOFTWARE_H_ + +#include "Filters.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +class DataSourceSurface; +class DrawTarget; +struct DrawOptions; +class FilterNodeSoftware; + +/** + * Can be attached to FilterNodeSoftware instances using + * AddInvalidationListener. FilterInvalidated is called whenever the output of + * the observed filter may have changed; that is, whenever cached GetOutput() + * results (and results derived from them) need to discarded. + */ +class FilterInvalidationListener +{ +public: + virtual void FilterInvalidated(FilterNodeSoftware* aFilter) = 0; +}; + +/** + * This is the base class for the software (i.e. pure CPU, non-accelerated) + * FilterNode implementation. The software implementation is backend-agnostic, + * so it can be used as a fallback for all DrawTarget implementations. + */ +class FilterNodeSoftware : public FilterNode, + public FilterInvalidationListener +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeSoftware, override) + virtual ~FilterNodeSoftware(); + + // Factory method, intended to be called from DrawTarget*::CreateFilter. + static already_AddRefed<FilterNode> Create(FilterType aType); + + // Draw the filter, intended to be called by DrawTarget*::DrawFilter. + void Draw(DrawTarget* aDrawTarget, const Rect &aSourceRect, + const Point &aDestPoint, const DrawOptions &aOptions); + + virtual FilterBackend GetBackendType() override { return FILTER_BACKEND_SOFTWARE; } + virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) override; + virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override; + + virtual const char* GetName() { return "Unknown"; } + + virtual void AddInvalidationListener(FilterInvalidationListener* aListener); + virtual void RemoveInvalidationListener(FilterInvalidationListener* aListener); + + // FilterInvalidationListener implementation + virtual void FilterInvalidated(FilterNodeSoftware* aFilter) override; + +protected: + + // The following methods are intended to be overriden by subclasses. + + /** + * Translates a *FilterInputs enum value into an index for the + * mInputFilters / mInputSurfaces arrays. Returns -1 for invalid inputs. + * If somebody calls SetInput(enumValue, input) with an enumValue for which + * InputIndex(enumValue) is -1, we abort. + */ + virtual int32_t InputIndex(uint32_t aInputEnumIndex) { return -1; } + + /** + * Every filter node has an output rect, which can also be infinite. The + * output rect can depend on the values of any set attributes and on the + * output rects of any input filters or surfaces. + * This method returns the intersection of the filter's output rect with + * aInRect. Filters with unconstrained output always return aInRect. + */ + virtual IntRect GetOutputRectInRect(const IntRect& aInRect) = 0; + + /** + * Return a surface with the rendered output which is of size aRect.Size(). + * aRect is required to be a subrect of this filter's output rect; in other + * words, aRect == GetOutputRectInRect(aRect) must always be true. + * May return nullptr in error conditions or for an empty aRect. + * Implementations are not required to allocate a new surface and may even + * pass through input surfaces unchanged. + * Callers need to treat the returned surface as immutable. + */ + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) = 0; + + /** + * Call RequestRect (see below) on any input filters with the desired input + * rect, so that the input filter knows what to cache the next time it + * renders. + */ + virtual void RequestFromInputsForRect(const IntRect &aRect) {} + + /** + * This method provides a caching default implementation but can be overriden + * by subclasses that don't want to cache their output. Those classes should + * call Render(aRect) directly from here. + */ + virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect &aRect); + + // The following methods are non-virtual helper methods. + + /** + * Format hints for GetInputDataSourceSurface. Some callers of + * GetInputDataSourceSurface can handle both B8G8R8A8 and A8 surfaces, these + * should pass CAN_HANDLE_A8 in order to avoid unnecessary conversions. + * Callers that can only handle B8G8R8A8 surfaces pass NEED_COLOR_CHANNELS. + */ + enum FormatHint { + CAN_HANDLE_A8, + NEED_COLOR_CHANNELS + }; + + /** + * Returns SurfaceFormat::B8G8R8A8 or SurfaceFormat::A8, depending on the current surface + * format and the format hint. + */ + SurfaceFormat DesiredFormat(SurfaceFormat aCurrentFormat, + FormatHint aFormatHint); + + /** + * Intended to be called by FilterNodeSoftware::Render implementations. + * Returns a surface of size aRect.Size() or nullptr in error conditions. The + * returned surface contains the output of the specified input filter or + * input surface in aRect. If aRect extends beyond the input filter's output + * rect (or the input surface's dimensions), the remaining area is filled + * according to aEdgeMode: The default, EDGE_MODE_NONE, simply pads with + * transparent black. + * If non-null, the returned surface is guaranteed to be of SurfaceFormat::A8 or + * SurfaceFormat::B8G8R8A8. If aFormatHint is NEED_COLOR_CHANNELS, the returned + * surface is guaranteed to be of SurfaceFormat::B8G8R8A8 always. + * Each pixel row of the returned surface is guaranteed to be 16-byte aligned. + */ + already_AddRefed<DataSourceSurface> + GetInputDataSourceSurface(uint32_t aInputEnumIndex, const IntRect& aRect, + FormatHint aFormatHint = CAN_HANDLE_A8, + ConvolveMatrixEdgeMode aEdgeMode = EDGE_MODE_NONE, + const IntRect *aTransparencyPaddedSourceRect = nullptr); + + /** + * Returns the intersection of the input filter's or surface's output rect + * with aInRect. + */ + IntRect GetInputRectInRect(uint32_t aInputEnumIndex, const IntRect& aInRect); + + /** + * Calls RequestRect on the specified input, if it's a filter. + */ + void RequestInputRect(uint32_t aInputEnumIndex, const IntRect& aRect); + + /** + * Returns the number of set input filters or surfaces. Needed for filters + * which can have an arbitrary number of inputs. + */ + size_t NumberOfSetInputs(); + + /** + * Discard the cached surface that was stored in the GetOutput default + * implementation. Needs to be called whenever attributes or inputs are set + * that might change the result of a Render() call. + */ + void Invalidate(); + + /** + * Called in order to let this filter know what to cache during the next + * GetOutput call. Expected to call RequestRect on this filter's input + * filters. + */ + void RequestRect(const IntRect &aRect); + + /** + * Set input filter and clear input surface for this input index, or set + * input surface and clear input filter. One of aSurface and aFilter should + * be null. + */ + void SetInput(uint32_t aIndex, SourceSurface *aSurface, + FilterNodeSoftware *aFilter); + +protected: + /** + * mInputSurfaces / mInputFilters: For each input index, either a surface or + * a filter is set, and the other is null. + */ + std::vector<RefPtr<SourceSurface> > mInputSurfaces; + std::vector<RefPtr<FilterNodeSoftware> > mInputFilters; + + /** + * Weak pointers to our invalidation listeners, i.e. to those filters who + * have this filter as an input. Invalidation listeners are required to + * unsubscribe themselves from us when they let go of their reference to us. + * This ensures that the pointers in this array are never stale. + */ + std::vector<FilterInvalidationListener*> mInvalidationListeners; + + /** + * Stores the rect which we want to render and cache on the next call to + * GetOutput. + */ + IntRect mRequestedRect; + + /** + * Stores our cached output. + */ + IntRect mCachedRect; + RefPtr<DataSourceSurface> mCachedOutput; +}; + +// Subclasses for specific filters. + +class FilterNodeTransformSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTransformSoftware, override) + FilterNodeTransformSoftware(); + virtual const char* GetName() override { return "Transform"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, uint32_t aGraphicsFilter) override; + virtual void SetAttribute(uint32_t aIndex, const Matrix &aMatrix) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + IntRect SourceRectForOutputRect(const IntRect &aRect); + +private: + Matrix mMatrix; + SamplingFilter mSamplingFilter; +}; + +class FilterNodeBlendSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlendSoftware, override) + FilterNodeBlendSoftware(); + virtual const char* GetName() override { return "Blend"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, uint32_t aBlendMode) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + BlendMode mBlendMode; +}; + +class FilterNodeMorphologySoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeMorphologySoftware, override) + FilterNodeMorphologySoftware(); + virtual const char* GetName() override { return "Morphology"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const IntSize &aRadii) override; + virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + IntSize mRadii; + MorphologyOperator mOperator; +}; + +class FilterNodeColorMatrixSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeColorMatrixSoftware, override) + virtual const char* GetName() override { return "ColorMatrix"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &aMatrix) override; + virtual void SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + Matrix5x4 mMatrix; + AlphaMode mAlphaMode; +}; + +class FilterNodeFloodSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeFloodSoftware, override) + virtual const char* GetName() override { return "Flood"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Color &aColor) override; + +protected: + virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect &aRect) override; + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + +private: + Color mColor; +}; + +class FilterNodeTileSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTileSoftware, override) + virtual const char* GetName() override { return "Tile"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + IntRect mSourceRect; +}; + +/** + * Baseclass for the four different component transfer filters. + */ +class FilterNodeComponentTransferSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeComponentTransferSoftware, override) + FilterNodeComponentTransferSoftware(); + + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, bool aDisable) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + virtual void GenerateLookupTable(ptrdiff_t aComponent, uint8_t aTables[4][256], + bool aDisabled); + virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0; + + bool mDisableR; + bool mDisableG; + bool mDisableB; + bool mDisableA; +}; + +class FilterNodeTableTransferSoftware : public FilterNodeComponentTransferSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTableTransferSoftware, override) + virtual const char* GetName() override { return "TableTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override; + +protected: + virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + +private: + void FillLookupTableImpl(std::vector<Float>& aTableValues, uint8_t aTable[256]); + + std::vector<Float> mTableR; + std::vector<Float> mTableG; + std::vector<Float> mTableB; + std::vector<Float> mTableA; +}; + +class FilterNodeDiscreteTransferSoftware : public FilterNodeComponentTransferSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDiscreteTransferSoftware, override) + virtual const char* GetName() override { return "DiscreteTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override; + +protected: + virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + +private: + void FillLookupTableImpl(std::vector<Float>& aTableValues, uint8_t aTable[256]); + + std::vector<Float> mTableR; + std::vector<Float> mTableG; + std::vector<Float> mTableB; + std::vector<Float> mTableA; +}; + +class FilterNodeLinearTransferSoftware : public FilterNodeComponentTransferSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeLinearTransformSoftware, override) + FilterNodeLinearTransferSoftware(); + virtual const char* GetName() override { return "LinearTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, Float aValue) override; + +protected: + virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + +private: + void FillLookupTableImpl(Float aSlope, Float aIntercept, uint8_t aTable[256]); + + Float mSlopeR; + Float mSlopeG; + Float mSlopeB; + Float mSlopeA; + Float mInterceptR; + Float mInterceptG; + Float mInterceptB; + Float mInterceptA; +}; + +class FilterNodeGammaTransferSoftware : public FilterNodeComponentTransferSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGammaTransferSoftware, override) + FilterNodeGammaTransferSoftware(); + virtual const char* GetName() override { return "GammaTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, Float aValue) override; + +protected: + virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + +private: + void FillLookupTableImpl(Float aAmplitude, Float aExponent, Float aOffset, uint8_t aTable[256]); + + Float mAmplitudeR; + Float mAmplitudeG; + Float mAmplitudeB; + Float mAmplitudeA; + Float mExponentR; + Float mExponentG; + Float mExponentB; + Float mExponentA; + Float mOffsetR; + Float mOffsetG; + Float mOffsetB; + Float mOffsetA; +}; + +class FilterNodeConvolveMatrixSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveMatrixSoftware, override) + FilterNodeConvolveMatrixSoftware(); + virtual const char* GetName() override { return "ConvolveMatrix"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const IntSize &aKernelSize) override; + virtual void SetAttribute(uint32_t aIndex, const Float* aMatrix, uint32_t aSize) override; + virtual void SetAttribute(uint32_t aIndex, Float aValue) override; + virtual void SetAttribute(uint32_t aIndex, const Size &aKernelUnitLength) override; + virtual void SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) override; + virtual void SetAttribute(uint32_t aIndex, const IntPoint &aTarget) override; + virtual void SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) override; + virtual void SetAttribute(uint32_t aIndex, bool aPreserveAlpha) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + template<typename CoordType> + already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect, + CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY); + + IntRect InflatedSourceRect(const IntRect &aDestRect); + IntRect InflatedDestRect(const IntRect &aSourceRect); + + IntSize mKernelSize; + std::vector<Float> mKernelMatrix; + Float mDivisor; + Float mBias; + IntPoint mTarget; + IntRect mSourceRect; + ConvolveMatrixEdgeMode mEdgeMode; + Size mKernelUnitLength; + bool mPreserveAlpha; +}; + +class FilterNodeDisplacementMapSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDisplacementMapSoftware, override) + FilterNodeDisplacementMapSoftware(); + virtual const char* GetName() override { return "DisplacementMap"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, Float aScale) override; + virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + IntRect InflatedSourceOrDestRect(const IntRect &aDestOrSourceRect); + + Float mScale; + ColorChannel mChannelX; + ColorChannel mChannelY; +}; + +class FilterNodeTurbulenceSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTurbulenceSoftware, override) + FilterNodeTurbulenceSoftware(); + virtual const char* GetName() override { return "Turbulence"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Size &aSize) override; + virtual void SetAttribute(uint32_t aIndex, const IntRect &aRenderRect) override; + virtual void SetAttribute(uint32_t aIndex, bool aStitchable) override; + virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + +private: + IntRect mRenderRect; + Size mBaseFrequency; + uint32_t mNumOctaves; + uint32_t mSeed; + bool mStitchable; + TurbulenceType mType; +}; + +class FilterNodeArithmeticCombineSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeArithmeticCombineSoftware, override) + FilterNodeArithmeticCombineSoftware(); + virtual const char* GetName() override { return "ArithmeticCombine"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + Float mK1; + Float mK2; + Float mK3; + Float mK4; +}; + +class FilterNodeCompositeSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCompositeSoftware, override) + FilterNodeCompositeSoftware(); + virtual const char* GetName() override { return "Composite"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + CompositeOperator mOperator; +}; + +// Base class for FilterNodeGaussianBlurSoftware and +// FilterNodeDirectionalBlurSoftware. +class FilterNodeBlurXYSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlurXYSoftware, override) +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + IntRect InflatedSourceOrDestRect(const IntRect &aDestRect); + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + + // Implemented by subclasses. + virtual Size StdDeviationXY() = 0; +}; + +class FilterNodeGaussianBlurSoftware : public FilterNodeBlurXYSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGaussianBlurSoftware, override) + FilterNodeGaussianBlurSoftware(); + virtual const char* GetName() override { return "GaussianBlur"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, Float aStdDeviation) override; + +protected: + virtual Size StdDeviationXY() override; + +private: + Float mStdDeviation; +}; + +class FilterNodeDirectionalBlurSoftware : public FilterNodeBlurXYSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDirectionalBlurSoftware, override) + FilterNodeDirectionalBlurSoftware(); + virtual const char* GetName() override { return "DirectionalBlur"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, Float aStdDeviation) override; + virtual void SetAttribute(uint32_t aIndex, uint32_t aBlurDirection) override; + +protected: + virtual Size StdDeviationXY() override; + +private: + Float mStdDeviation; + BlurDirection mBlurDirection; +}; + +class FilterNodeCropSoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCropSoftware, override) + virtual const char* GetName() override { return "Crop"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, const Rect &aSourceRect) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + IntRect mCropRect; +}; + +class FilterNodePremultiplySoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplySoftware, override) + virtual const char* GetName() override { return "Premultiply"; } +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; +}; + +class FilterNodeUnpremultiplySoftware : public FilterNodeSoftware +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeUnpremultiplySoftware, override) + virtual const char* GetName() override { return "Unpremultiply"; } +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; +}; + +template<typename LightType, typename LightingType> +class FilterNodeLightingSoftware : public FilterNodeSoftware +{ +public: +#if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) + // Helpers for refcounted + virtual const char* typeName() const override { return mTypeName; } + virtual size_t typeSize() const override { return sizeof(*this); } +#endif + explicit FilterNodeLightingSoftware(const char* aTypeName); + virtual const char* GetName() override { return "Lighting"; } + using FilterNodeSoftware::SetAttribute; + virtual void SetAttribute(uint32_t aIndex, Float) override; + virtual void SetAttribute(uint32_t aIndex, const Size &) override; + virtual void SetAttribute(uint32_t aIndex, const Point3D &) override; + virtual void SetAttribute(uint32_t aIndex, const Color &) override; + +protected: + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + virtual IntRect GetOutputRectInRect(const IntRect& aRect) override; + virtual int32_t InputIndex(uint32_t aInputEnumIndex) override; + virtual void RequestFromInputsForRect(const IntRect &aRect) override; + +private: + template<typename CoordType> + already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect, + CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY); + + LightType mLight; + LightingType mLighting; + Float mSurfaceScale; + Size mKernelUnitLength; + Color mColor; +#if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) + const char* mTypeName; +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_FILTERNODESOFTWARE_H_ diff --git a/gfx/2d/FilterProcessing.cpp b/gfx/2d/FilterProcessing.cpp new file mode 100644 index 000000000..da734c01a --- /dev/null +++ b/gfx/2d/FilterProcessing.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilterProcessing.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<DataSourceSurface> +FilterProcessing::ExtractAlpha(DataSourceSurface* aSource) +{ + IntSize size = aSource->GetSize(); + RefPtr<DataSourceSurface> alpha = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + if (MOZ2D_WARN_IF(!alpha)) { + return nullptr; + } + + DataSourceSurface::ScopedMap sourceMap(aSource, DataSourceSurface::READ); + DataSourceSurface::ScopedMap alphaMap(alpha, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !alphaMap.IsMapped())) { + return nullptr; + } + + uint8_t* sourceData = sourceMap.GetData(); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* alphaData = alphaMap.GetData(); + int32_t alphaStride = alphaMap.GetStride(); + + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ExtractAlpha_SSE2(size, sourceData, sourceStride, alphaData, alphaStride); +#endif + } else { + ExtractAlpha_Scalar(size, sourceData, sourceStride, alphaData, alphaStride); + } + + return alpha.forget(); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ConvertToB8G8R8A8(SourceSurface* aSurface) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ConvertToB8G8R8A8_SSE2(aSurface); +#endif + } + return ConvertToB8G8R8A8_Scalar(aSurface); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyBlending(DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ApplyBlending_SSE2(aInput1, aInput2, aBlendMode); +#endif + } + return nullptr; +} + +void +FilterProcessing::ApplyMorphologyHorizontal(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ApplyMorphologyHorizontal_SSE2( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp); +#endif + } else { + ApplyMorphologyHorizontal_Scalar( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp); + } +} + +void +FilterProcessing::ApplyMorphologyVertical(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ApplyMorphologyVertical_SSE2( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp); +#endif + } else { + ApplyMorphologyVertical_Scalar( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp); + } +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyColorMatrix(DataSourceSurface* aInput, const Matrix5x4 &aMatrix) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ApplyColorMatrix_SSE2(aInput, aMatrix); +#endif + } + return ApplyColorMatrix_Scalar(aInput, aMatrix); +} + +void +FilterProcessing::ApplyComposition(DataSourceSurface* aSource, DataSourceSurface* aDest, + CompositeOperator aOperator) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ApplyComposition_SSE2(aSource, aDest, aOperator); +#endif + } else { + ApplyComposition_Scalar(aSource, aDest, aOperator); + } +} + +void +FilterProcessing::SeparateColorChannels(DataSourceSurface* aSource, + RefPtr<DataSourceSurface>& aChannel0, + RefPtr<DataSourceSurface>& aChannel1, + RefPtr<DataSourceSurface>& aChannel2, + RefPtr<DataSourceSurface>& aChannel3) +{ + IntSize size = aSource->GetSize(); + aChannel0 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + aChannel1 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + aChannel2 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + aChannel3 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + if (MOZ2D_WARN_IF(!(aChannel0 && aChannel1 && aChannel2 && aChannel3))) { + return; + } + + DataSourceSurface::ScopedMap sourceMap(aSource, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel0Map(aChannel0, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel1Map(aChannel1, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel2Map(aChannel2, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel3Map(aChannel3, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!(sourceMap.IsMapped() && + channel0Map.IsMapped() && channel1Map.IsMapped() && + channel2Map.IsMapped() && channel3Map.IsMapped()))) { + return; + } + uint8_t* sourceData = sourceMap.GetData(); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* channel0Data = channel0Map.GetData(); + uint8_t* channel1Data = channel1Map.GetData(); + uint8_t* channel2Data = channel2Map.GetData(); + uint8_t* channel3Data = channel3Map.GetData(); + int32_t channelStride = channel0Map.GetStride(); + + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + SeparateColorChannels_SSE2(size, sourceData, sourceStride, channel0Data, channel1Data, channel2Data, channel3Data, channelStride); +#endif + } else { + SeparateColorChannels_Scalar(size, sourceData, sourceStride, channel0Data, channel1Data, channel2Data, channel3Data, channelStride); + } +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::CombineColorChannels(DataSourceSurface* aChannel0, DataSourceSurface* aChannel1, + DataSourceSurface* aChannel2, DataSourceSurface* aChannel3) +{ + IntSize size = aChannel0->GetSize(); + RefPtr<DataSourceSurface> result = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!result)) { + return nullptr; + } + DataSourceSurface::ScopedMap resultMap(result, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel0Map(aChannel0, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel1Map(aChannel1, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel2Map(aChannel2, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel3Map(aChannel3, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!(resultMap.IsMapped() && + channel0Map.IsMapped() && channel1Map.IsMapped() && + channel2Map.IsMapped() && channel3Map.IsMapped()))) { + return nullptr; + } + int32_t resultStride = resultMap.GetStride(); + uint8_t* resultData = resultMap.GetData(); + int32_t channelStride = channel0Map.GetStride(); + uint8_t* channel0Data = channel0Map.GetData(); + uint8_t* channel1Data = channel1Map.GetData(); + uint8_t* channel2Data = channel2Map.GetData(); + uint8_t* channel3Data = channel3Map.GetData(); + + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + CombineColorChannels_SSE2(size, resultStride, resultData, channelStride, channel0Data, channel1Data, channel2Data, channel3Data); +#endif + } else { + CombineColorChannels_Scalar(size, resultStride, resultData, channelStride, channel0Data, channel1Data, channel2Data, channel3Data); + } + + return result.forget(); +} + +void +FilterProcessing::DoPremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + DoPremultiplicationCalculation_SSE2( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); +#endif + } else { + DoPremultiplicationCalculation_Scalar( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); + } +} + +void +FilterProcessing::DoUnpremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + DoUnpremultiplicationCalculation_SSE2( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); +#endif + } else { + DoUnpremultiplicationCalculation_Scalar( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); + } +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::RenderTurbulence(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return RenderTurbulence_SSE2(aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect); +#endif + } + return RenderTurbulence_Scalar(aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyArithmeticCombine(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4) +{ + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ApplyArithmeticCombine_SSE2(aInput1, aInput2, aK1, aK2, aK3, aK4); +#endif + } + return ApplyArithmeticCombine_Scalar(aInput1, aInput2, aK1, aK2, aK3, aK4); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterProcessing.h b/gfx/2d/FilterProcessing.h new file mode 100644 index 000000000..802d791a0 --- /dev/null +++ b/gfx/2d/FilterProcessing.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_FILTERPROCESSING_H_ +#define _MOZILLA_GFX_FILTERPROCESSING_H_ + +#include "2D.h" +#include "Filters.h" + +namespace mozilla { +namespace gfx { + +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_B = 0; +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_G = 1; +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_R = 2; +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_A = 3; + +class FilterProcessing +{ +public: + + // Fast approximate division by 255. It has the property that + // for all 0 <= v <= 255*255, FastDivideBy255(v) == v/255. + // But it only uses two adds and two shifts instead of an + // integer division (which is expensive on many processors). + template<class B, class A> + static B FastDivideBy255(A v) + { + return ((v << 8) + v + 255) >> 16; + } + + static already_AddRefed<DataSourceSurface> ExtractAlpha(DataSourceSurface* aSource); + static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8(SourceSurface* aSurface); + static already_AddRefed<DataSourceSurface> ApplyBlending(DataSourceSurface* aInput1, DataSourceSurface* aInput2, BlendMode aBlendMode); + static void ApplyMorphologyHorizontal(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static void ApplyMorphologyVertical(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static already_AddRefed<DataSourceSurface> ApplyColorMatrix(DataSourceSurface* aInput, const Matrix5x4 &aMatrix); + static void ApplyComposition(DataSourceSurface* aSource, DataSourceSurface* aDest, CompositeOperator aOperator); + static void SeparateColorChannels(DataSourceSurface* aSource, + RefPtr<DataSourceSurface>& aChannel0, + RefPtr<DataSourceSurface>& aChannel1, + RefPtr<DataSourceSurface>& aChannel2, + RefPtr<DataSourceSurface>& aChannel3); + static already_AddRefed<DataSourceSurface> + CombineColorChannels(DataSourceSurface* aChannel0, DataSourceSurface* aChannel1, + DataSourceSurface* aChannel2, DataSourceSurface* aChannel3); + static void DoPremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride); + static void DoUnpremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride); + static already_AddRefed<DataSourceSurface> + RenderTurbulence(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect); + static already_AddRefed<DataSourceSurface> + ApplyArithmeticCombine(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4); + +protected: + static void ExtractAlpha_Scalar(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride); + static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_Scalar(SourceSurface* aSurface); + static void ApplyMorphologyHorizontal_Scalar(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static void ApplyMorphologyVertical_Scalar(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static already_AddRefed<DataSourceSurface> ApplyColorMatrix_Scalar(DataSourceSurface* aInput, const Matrix5x4 &aMatrix); + static void ApplyComposition_Scalar(DataSourceSurface* aSource, DataSourceSurface* aDest, CompositeOperator aOperator); + + static void SeparateColorChannels_Scalar(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride); + static void CombineColorChannels_Scalar(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data); + static void DoPremultiplicationCalculation_Scalar(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride); + static void DoUnpremultiplicationCalculation_Scalar(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride); + static already_AddRefed<DataSourceSurface> + RenderTurbulence_Scalar(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect); + static already_AddRefed<DataSourceSurface> + ApplyArithmeticCombine_Scalar(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4); + +#ifdef USE_SSE2 + static void ExtractAlpha_SSE2(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride); + static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_SSE2(SourceSurface* aSurface); + static already_AddRefed<DataSourceSurface> ApplyBlending_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, BlendMode aBlendMode); + static void ApplyMorphologyHorizontal_SSE2(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static void ApplyMorphologyVertical_SSE2(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static already_AddRefed<DataSourceSurface> ApplyColorMatrix_SSE2(DataSourceSurface* aInput, const Matrix5x4 &aMatrix); + static void ApplyComposition_SSE2(DataSourceSurface* aSource, DataSourceSurface* aDest, CompositeOperator aOperator); + static void SeparateColorChannels_SSE2(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride); + static void CombineColorChannels_SSE2(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data); + static void DoPremultiplicationCalculation_SSE2(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride); + static void DoUnpremultiplicationCalculation_SSE2(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride); + static already_AddRefed<DataSourceSurface> + RenderTurbulence_SSE2(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect); + static already_AddRefed<DataSourceSurface> + ApplyArithmeticCombine_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4); +#endif +}; + +// Constant-time max and min functions for unsigned arguments +static inline unsigned +umax(unsigned a, unsigned b) +{ + return a - ((a - b) & -(a < b)); +} + +static inline unsigned +umin(unsigned a, unsigned b) +{ + return a - ((a - b) & -(a > b)); +} + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_FILTERPROCESSING_H_ diff --git a/gfx/2d/FilterProcessingSIMD-inl.h b/gfx/2d/FilterProcessingSIMD-inl.h new file mode 100644 index 000000000..3d21a4751 --- /dev/null +++ b/gfx/2d/FilterProcessingSIMD-inl.h @@ -0,0 +1,1081 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilterProcessing.h" + +#include "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> input = aSurface->GetDataSurface(); + RefPtr<DataSourceSurface> output = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + uint8_t *inputData = input->GetData(); + uint8_t *outputData = output->GetData(); + int32_t inputStride = input->Stride(); + int32_t outputStride = output->Stride(); + 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 bgrabgrabgrabgra1 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra2 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra3 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra4 = simd::FromZero8<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) +{ + // 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] + 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 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; + } + + uint8_t* source1Data = aInput1->GetData(); + uint8_t* source2Data = aInput2->GetData(); + uint8_t* targetData = target->GetData(); + int32_t targetStride = target->Stride(); + int32_t source1Stride = aInput1->Stride(); + int32_t source2Stride = aInput2->Stride(); + + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.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); + } + } + + 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; + } + + uint8_t* sourceData = aInput->GetData(); + uint8_t* targetData = target->GetData(); + int32_t sourceStride = aInput->Stride(); + int32_t targetStride = target->Stride(); + + 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)); + } + + 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(); + + uint8_t* sourceData = aSource->GetData(); + uint8_t* destData = aDest->GetData(); + uint32_t sourceStride = aSource->Stride(); + uint32_t destStride = aDest->Stride(); + + 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; + 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 bgrabgrabgrabgra1 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra2 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra3 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra4 = simd::FromZero8<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 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 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; + } + + uint8_t* source1Data = aInput1->GetData(); + uint8_t* source2Data = aInput2->GetData(); + uint8_t* targetData = target->GetData(); + uint32_t source1Stride = aInput1->Stride(); + uint32_t source2Stride = aInput2->Stride(); + uint32_t targetStride = target->Stride(); + + // 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 < size.height; y++) { + for (int32_t x = 0; x < size.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)); + } + } + + return target.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterProcessingSSE2.cpp b/gfx/2d/FilterProcessingSSE2.cpp new file mode 100644 index 000000000..6f14fc3ef --- /dev/null +++ b/gfx/2d/FilterProcessingSSE2.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define SIMD_COMPILE_SSE2 + +#include "FilterProcessingSIMD-inl.h" + +#ifndef USE_SSE2 +static_assert(false, "If this file is built, FilterProcessing.h should know about it!"); +#endif + +namespace mozilla { +namespace gfx { + +void +FilterProcessing::ExtractAlpha_SSE2(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride) +{ + ExtractAlpha_SIMD<__m128i>(size, sourceData, sourceStride, alphaData, alphaStride); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ConvertToB8G8R8A8_SSE2(SourceSurface* aSurface) +{ + return ConvertToB8G8R8A8_SIMD<__m128i>(aSurface); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyBlending_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode) +{ + return ApplyBlending_SIMD<__m128i,__m128i,__m128i>(aInput1, aInput2, aBlendMode); +} + +void +FilterProcessing::ApplyMorphologyHorizontal_SSE2(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) +{ + ApplyMorphologyHorizontal_SIMD<__m128i,__m128i>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp); +} + +void +FilterProcessing::ApplyMorphologyVertical_SSE2(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) +{ + ApplyMorphologyVertical_SIMD<__m128i,__m128i>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius, aOp); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyColorMatrix_SSE2(DataSourceSurface* aInput, const Matrix5x4 &aMatrix) +{ + return ApplyColorMatrix_SIMD<__m128i,__m128i,__m128i>(aInput, aMatrix); +} + +void +FilterProcessing::ApplyComposition_SSE2(DataSourceSurface* aSource, DataSourceSurface* aDest, + CompositeOperator aOperator) +{ + return ApplyComposition_SIMD<__m128i,__m128i,__m128i>(aSource, aDest, aOperator); +} + +void +FilterProcessing::SeparateColorChannels_SSE2(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride) +{ + SeparateColorChannels_SIMD<__m128i>(size, sourceData, sourceStride, channel0Data, channel1Data, channel2Data, channel3Data, channelStride); +} + +void +FilterProcessing::CombineColorChannels_SSE2(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data) +{ + CombineColorChannels_SIMD<__m128i>(size, resultStride, resultData, channelStride, channel0Data, channel1Data, channel2Data, channel3Data); +} + +void +FilterProcessing::DoPremultiplicationCalculation_SSE2(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) +{ + DoPremultiplicationCalculation_SIMD<__m128i,__m128i,__m128i>(aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); +} + +void +FilterProcessing::DoUnpremultiplicationCalculation_SSE2( + const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) +{ + DoUnpremultiplicationCalculation_SIMD<__m128i,__m128i>(aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::RenderTurbulence_SSE2(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect) +{ + return RenderTurbulence_SIMD<__m128,__m128i,__m128i>(aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyArithmeticCombine_SSE2(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4) +{ + return ApplyArithmeticCombine_SIMD<__m128i,__m128i,__m128i>(aInput1, aInput2, aK1, aK2, aK3, aK4); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterProcessingScalar.cpp b/gfx/2d/FilterProcessingScalar.cpp new file mode 100644 index 000000000..9e88c563e --- /dev/null +++ b/gfx/2d/FilterProcessingScalar.cpp @@ -0,0 +1,244 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define FILTER_PROCESSING_SCALAR + +#include "FilterProcessingSIMD-inl.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +void +FilterProcessing::ExtractAlpha_Scalar(const IntSize& size, uint8_t* sourceData, int32_t sourceStride, uint8_t* alphaData, int32_t alphaStride) +{ + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * alphaStride + x; + alphaData[targetIndex] = sourceData[sourceIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + } + } +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ConvertToB8G8R8A8_Scalar(SourceSurface* aSurface) +{ + return ConvertToB8G8R8A8_SIMD<simd::Scalaru8x16_t>(aSurface); +} + +template<MorphologyOperator Operator> +static void +ApplyMorphologyHorizontal_Scalar(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius) +{ + static_assert(Operator == MORPHOLOGY_OPERATOR_ERODE || + Operator == MORPHOLOGY_OPERATOR_DILATE, + "unexpected morphology operator"); + + for (int32_t y = aDestRect.y; y < aDestRect.YMost(); y++) { + int32_t startX = aDestRect.x - aRadius; + int32_t endX = aDestRect.x + aRadius; + for (int32_t x = aDestRect.x; x < aDestRect.XMost(); x++, startX++, endX++) { + int32_t sourceIndex = y * aSourceStride + 4 * startX; + uint8_t u[4]; + for (size_t i = 0; i < 4; i++) { + u[i] = aSourceData[sourceIndex + i]; + } + sourceIndex += 4; + for (int32_t ix = startX + 1; ix <= endX; ix++, sourceIndex += 4) { + for (size_t i = 0; i < 4; i++) { + if (Operator == MORPHOLOGY_OPERATOR_ERODE) { + u[i] = umin(u[i], aSourceData[sourceIndex + i]); + } else { + u[i] = umax(u[i], aSourceData[sourceIndex + i]); + } + } + } + + int32_t destIndex = y * aDestStride + 4 * x; + for (size_t i = 0; i < 4; i++) { + aDestData[destIndex+i] = u[i]; + } + } + } +} + +void +FilterProcessing::ApplyMorphologyHorizontal_Scalar(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) +{ + if (aOp == MORPHOLOGY_OPERATOR_ERODE) { + gfx::ApplyMorphologyHorizontal_Scalar<MORPHOLOGY_OPERATOR_ERODE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } else { + gfx::ApplyMorphologyHorizontal_Scalar<MORPHOLOGY_OPERATOR_DILATE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } +} + +template<MorphologyOperator Operator> +static void ApplyMorphologyVertical_Scalar(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius) +{ + static_assert(Operator == MORPHOLOGY_OPERATOR_ERODE || + Operator == MORPHOLOGY_OPERATOR_DILATE, + "unexpected morphology operator"); + + int32_t startY = aDestRect.y - aRadius; + int32_t endY = aDestRect.y + aRadius; + for (int32_t y = aDestRect.y; y < aDestRect.YMost(); y++, startY++, endY++) { + for (int32_t x = aDestRect.x; x < aDestRect.XMost(); x++) { + int32_t sourceIndex = startY * aSourceStride + 4 * x; + uint8_t u[4]; + for (size_t i = 0; i < 4; i++) { + u[i] = aSourceData[sourceIndex + i]; + } + sourceIndex += aSourceStride; + for (int32_t iy = startY + 1; iy <= endY; iy++, sourceIndex += aSourceStride) { + for (size_t i = 0; i < 4; i++) { + if (Operator == MORPHOLOGY_OPERATOR_ERODE) { + u[i] = umin(u[i], aSourceData[sourceIndex + i]); + } else { + u[i] = umax(u[i], aSourceData[sourceIndex + i]); + } + } + } + + int32_t destIndex = y * aDestStride + 4 * x; + for (size_t i = 0; i < 4; i++) { + aDestData[destIndex+i] = u[i]; + } + } + } +} + +void +FilterProcessing::ApplyMorphologyVertical_Scalar(uint8_t* aSourceData, int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) +{ + if (aOp == MORPHOLOGY_OPERATOR_ERODE) { + gfx::ApplyMorphologyVertical_Scalar<MORPHOLOGY_OPERATOR_ERODE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } else { + gfx::ApplyMorphologyVertical_Scalar<MORPHOLOGY_OPERATOR_DILATE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyColorMatrix_Scalar(DataSourceSurface* aInput, const Matrix5x4 &aMatrix) +{ + return ApplyColorMatrix_SIMD<simd::Scalari32x4_t,simd::Scalari16x8_t,simd::Scalaru8x16_t>(aInput, aMatrix); +} + +void +FilterProcessing::ApplyComposition_Scalar(DataSourceSurface* aSource, DataSourceSurface* aDest, + CompositeOperator aOperator) +{ + return ApplyComposition_SIMD<simd::Scalari32x4_t,simd::Scalaru16x8_t,simd::Scalaru8x16_t>(aSource, aDest, aOperator); +} + +void +FilterProcessing::SeparateColorChannels_Scalar(const IntSize &size, uint8_t* sourceData, int32_t sourceStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data, int32_t channelStride) +{ + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * channelStride + x; + channel0Data[targetIndex] = sourceData[sourceIndex]; + channel1Data[targetIndex] = sourceData[sourceIndex+1]; + channel2Data[targetIndex] = sourceData[sourceIndex+2]; + channel3Data[targetIndex] = sourceData[sourceIndex+3]; + } + } +} + +void +FilterProcessing::CombineColorChannels_Scalar(const IntSize &size, int32_t resultStride, uint8_t* resultData, int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, uint8_t* channel3Data) +{ + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t resultIndex = y * resultStride + 4 * x; + int32_t channelIndex = y * channelStride + x; + resultData[resultIndex] = channel0Data[channelIndex]; + resultData[resultIndex+1] = channel1Data[channelIndex]; + resultData[resultIndex+2] = channel2Data[channelIndex]; + resultData[resultIndex+3] = channel3Data[channelIndex]; + } + } +} + +void +FilterProcessing::DoPremultiplicationCalculation_Scalar(const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) +{ + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + uint8_t alpha = aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + FastDivideBy255<uint8_t>(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * alpha); + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + FastDivideBy255<uint8_t>(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * alpha); + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + FastDivideBy255<uint8_t>(aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * alpha); + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = alpha; + } + } +} + +void +FilterProcessing::DoUnpremultiplicationCalculation_Scalar( + const IntSize& aSize, + uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) +{ + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + uint8_t alpha = aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + uint16_t alphaFactor = sAlphaFactors[alpha]; + // inputColor * alphaFactor + 128 is guaranteed to fit into uint16_t + // because the input is premultiplied and thus inputColor <= inputAlpha. + // The maximum value this can attain is 65520 (which is less than 65535) + // for color == alpha == 244: + // 244 * sAlphaFactors[244] + 128 == 244 * 268 + 128 == 65520 + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * alphaFactor + 128) >> 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * alphaFactor + 128) >> 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * alphaFactor + 128) >> 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = alpha; + } + } +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::RenderTurbulence_Scalar(const IntSize &aSize, const Point &aOffset, const Size &aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, const Rect &aTileRect) +{ + return RenderTurbulence_SIMD<simd::Scalarf32x4_t,simd::Scalari32x4_t,simd::Scalaru8x16_t>( + aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, aTileRect); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyArithmeticCombine_Scalar(DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, Float aK2, Float aK3, Float aK4) +{ + return ApplyArithmeticCombine_SIMD<simd::Scalari32x4_t,simd::Scalari16x8_t,simd::Scalaru8x16_t>(aInput1, aInput2, aK1, aK2, aK3, aK4); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Filters.h b/gfx/2d/Filters.h new file mode 100644 index 000000000..12eadcebe --- /dev/null +++ b/gfx/2d/Filters.h @@ -0,0 +1,512 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FILTERS_H_ +#define MOZILLA_GFX_FILTERS_H_ + +#include "Types.h" +#include "mozilla/RefPtr.h" + +#include "Point.h" +#include "Matrix.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +class SourceSurface; + +enum FilterBackend { + FILTER_BACKEND_SOFTWARE = 0, + FILTER_BACKEND_DIRECT2D1_1, + FILTER_BACKEND_RECORDING +}; + +enum TransformFilterAtts +{ + ATT_TRANSFORM_MATRIX = 0, // Matrix + ATT_TRANSFORM_FILTER // Filter +}; + +enum TransformFilterInputs +{ + IN_TRANSFORM_IN = 0 +}; + +enum BlendFilterAtts +{ + ATT_BLEND_BLENDMODE = 0 // uint32_t +}; + +enum BlendMode +{ + BLEND_MODE_MULTIPLY = 0, + BLEND_MODE_SCREEN, + BLEND_MODE_DARKEN, + BLEND_MODE_LIGHTEN, + BLEND_MODE_OVERLAY, + BLEND_MODE_COLOR_DODGE, + BLEND_MODE_COLOR_BURN, + BLEND_MODE_HARD_LIGHT, + BLEND_MODE_SOFT_LIGHT, + BLEND_MODE_DIFFERENCE, + BLEND_MODE_EXCLUSION, + BLEND_MODE_HUE, + BLEND_MODE_SATURATION, + BLEND_MODE_COLOR, + BLEND_MODE_LUMINOSITY +}; + +enum BlendFilterInputs +{ + IN_BLEND_IN = 0, + IN_BLEND_IN2 +}; + +enum MorphologyFilterAtts +{ + ATT_MORPHOLOGY_RADII = 0, // IntSize + ATT_MORPHOLOGY_OPERATOR // MorphologyOperator +}; + +enum MorphologyOperator +{ + MORPHOLOGY_OPERATOR_ERODE = 0, + MORPHOLOGY_OPERATOR_DILATE +}; + +enum MorphologyFilterInputs +{ + IN_MORPHOLOGY_IN = 0 +}; + +enum AlphaMode +{ + ALPHA_MODE_PREMULTIPLIED = 0, + ALPHA_MODE_STRAIGHT +}; + +enum ColorMatrixFilterAtts +{ + ATT_COLOR_MATRIX_MATRIX = 0, // Matrix5x4 + ATT_COLOR_MATRIX_ALPHA_MODE // AlphaMode +}; + +enum ColorMatrixFilterInputs +{ + IN_COLOR_MATRIX_IN = 0 +}; + +enum FloodFilterAtts +{ + ATT_FLOOD_COLOR = 0 // Color +}; + +enum FloodFilterInputs +{ + IN_FLOOD_IN = 0 +}; + +enum TileFilterAtts +{ + ATT_TILE_SOURCE_RECT = 0 // IntRect +}; + +enum TileFilterInputs +{ + IN_TILE_IN = 0 +}; + +enum TransferAtts +{ + ATT_TRANSFER_DISABLE_R = 0, // bool + ATT_TRANSFER_DISABLE_G, // bool + ATT_TRANSFER_DISABLE_B, // bool + ATT_TRANSFER_DISABLE_A // bool +}; + +enum TransferInputs +{ + IN_TRANSFER_IN = 0 +}; + +enum TableTransferAtts +{ + ATT_TABLE_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_TABLE_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_TABLE_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_TABLE_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_TABLE_TRANSFER_TABLE_R, // Float[] + ATT_TABLE_TRANSFER_TABLE_G, // Float[] + ATT_TABLE_TRANSFER_TABLE_B, // Float[] + ATT_TABLE_TRANSFER_TABLE_A // Float[] +}; + +enum TableTransferInputs +{ + IN_TABLE_TRANSFER_IN = IN_TRANSFER_IN +}; + +enum DiscreteTransferAtts +{ + ATT_DISCRETE_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_DISCRETE_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_DISCRETE_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_DISCRETE_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_DISCRETE_TRANSFER_TABLE_R, // Float[] + ATT_DISCRETE_TRANSFER_TABLE_G, // Float[] + ATT_DISCRETE_TRANSFER_TABLE_B, // Float[] + ATT_DISCRETE_TRANSFER_TABLE_A // Float[] +}; + +enum DiscreteTransferInputs +{ + IN_DISCRETE_TRANSFER_IN = IN_TRANSFER_IN +}; + +enum LinearTransferAtts +{ + ATT_LINEAR_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_LINEAR_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_LINEAR_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_LINEAR_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_LINEAR_TRANSFER_SLOPE_R, // Float + ATT_LINEAR_TRANSFER_SLOPE_G, // Float + ATT_LINEAR_TRANSFER_SLOPE_B, // Float + ATT_LINEAR_TRANSFER_SLOPE_A, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_R, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_G, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_B, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_A // Float +}; + +enum LinearTransferInputs +{ + IN_LINEAR_TRANSFER_IN = IN_TRANSFER_IN +}; + +enum GammaTransferAtts +{ + ATT_GAMMA_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_GAMMA_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_GAMMA_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_GAMMA_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_GAMMA_TRANSFER_AMPLITUDE_R, // Float + ATT_GAMMA_TRANSFER_AMPLITUDE_G, // Float + ATT_GAMMA_TRANSFER_AMPLITUDE_B, // Float + ATT_GAMMA_TRANSFER_AMPLITUDE_A, // Float + ATT_GAMMA_TRANSFER_EXPONENT_R, // Float + ATT_GAMMA_TRANSFER_EXPONENT_G, // Float + ATT_GAMMA_TRANSFER_EXPONENT_B, // Float + ATT_GAMMA_TRANSFER_EXPONENT_A, // Float + ATT_GAMMA_TRANSFER_OFFSET_R, // Float + ATT_GAMMA_TRANSFER_OFFSET_G, // Float + ATT_GAMMA_TRANSFER_OFFSET_B, // Float + ATT_GAMMA_TRANSFER_OFFSET_A // Float +}; + +enum GammaTransferInputs +{ + IN_GAMMA_TRANSFER_IN = IN_TRANSFER_IN +}; + +enum ConvolveMatrixAtts +{ + ATT_CONVOLVE_MATRIX_KERNEL_SIZE = 0, // IntSize + ATT_CONVOLVE_MATRIX_KERNEL_MATRIX, // Float[] + ATT_CONVOLVE_MATRIX_DIVISOR, // Float + ATT_CONVOLVE_MATRIX_BIAS, // Float + ATT_CONVOLVE_MATRIX_TARGET, // IntPoint + ATT_CONVOLVE_MATRIX_SOURCE_RECT, // IntRect + ATT_CONVOLVE_MATRIX_EDGE_MODE, // ConvolveMatrixEdgeMode + ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH, // Size + ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA, // bool +}; + +enum ConvolveMatrixEdgeMode +{ + EDGE_MODE_DUPLICATE = 0, + EDGE_MODE_WRAP, + EDGE_MODE_NONE +}; + +enum ConvolveMatrixInputs +{ + IN_CONVOLVE_MATRIX_IN = 0 +}; + +enum DisplacementMapAtts +{ + ATT_DISPLACEMENT_MAP_SCALE = 0, // Float + ATT_DISPLACEMENT_MAP_X_CHANNEL, // ColorChannel + ATT_DISPLACEMENT_MAP_Y_CHANNEL // ColorChannel +}; + +enum ColorChannel +{ + COLOR_CHANNEL_R = 0, + COLOR_CHANNEL_G, + COLOR_CHANNEL_B, + COLOR_CHANNEL_A +}; + +enum DisplacementMapInputs +{ + IN_DISPLACEMENT_MAP_IN = 0, + IN_DISPLACEMENT_MAP_IN2 +}; + +enum TurbulenceAtts +{ + ATT_TURBULENCE_BASE_FREQUENCY = 0, // Size + ATT_TURBULENCE_NUM_OCTAVES, // uint32_t + ATT_TURBULENCE_SEED, // uint32_t + ATT_TURBULENCE_STITCHABLE, // bool + ATT_TURBULENCE_TYPE, // TurbulenceType + ATT_TURBULENCE_RECT // IntRect +}; + +enum TurbulenceType +{ + TURBULENCE_TYPE_TURBULENCE = 0, + TURBULENCE_TYPE_FRACTAL_NOISE +}; + +enum ArithmeticCombineAtts +{ + ATT_ARITHMETIC_COMBINE_COEFFICIENTS = 0 // Float[4] +}; + +enum ArithmeticCombineInputs +{ + IN_ARITHMETIC_COMBINE_IN = 0, + IN_ARITHMETIC_COMBINE_IN2 +}; + +enum CompositeAtts +{ + ATT_COMPOSITE_OPERATOR = 0 // CompositeOperator +}; + +enum CompositeOperator +{ + COMPOSITE_OPERATOR_OVER = 0, + COMPOSITE_OPERATOR_IN, + COMPOSITE_OPERATOR_OUT, + COMPOSITE_OPERATOR_ATOP, + COMPOSITE_OPERATOR_XOR +}; + +enum CompositeInputs +{ + // arbitrary number of inputs + IN_COMPOSITE_IN_START = 0 +}; + +enum GaussianBlurAtts +{ + ATT_GAUSSIAN_BLUR_STD_DEVIATION = 0 // Float +}; + +enum GaussianBlurInputs +{ + IN_GAUSSIAN_BLUR_IN = 0 +}; + +enum DirectionalBlurAtts +{ + ATT_DIRECTIONAL_BLUR_STD_DEVIATION = 0, // Float + ATT_DIRECTIONAL_BLUR_DIRECTION // BlurDirection +}; + +enum BlurDirection +{ + BLUR_DIRECTION_X = 0, + BLUR_DIRECTION_Y +}; + +enum DirectionalBlurInputs +{ + IN_DIRECTIONAL_BLUR_IN = 0 +}; + +enum LightingAtts +{ + ATT_POINT_LIGHT_POSITION = 0, // Point3D + + ATT_SPOT_LIGHT_POSITION, // Point3D + ATT_SPOT_LIGHT_POINTS_AT, // Point3D + ATT_SPOT_LIGHT_FOCUS, // Float + ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, // Float + + ATT_DISTANT_LIGHT_AZIMUTH, // Float + ATT_DISTANT_LIGHT_ELEVATION, // Float + + ATT_LIGHTING_COLOR, // Color + ATT_LIGHTING_SURFACE_SCALE, // Float + ATT_LIGHTING_KERNEL_UNIT_LENGTH, // Size + + ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT, // Float + + ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, // Float + ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT // Float +}; + +enum LightingInputs +{ + IN_LIGHTING_IN = 0 +}; + +enum PointDiffuseAtts +{ + ATT_POINT_DIFFUSE_POSITION = ATT_POINT_LIGHT_POSITION, + ATT_POINT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR, + ATT_POINT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_POINT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_POINT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT +}; + +enum PointDiffuseInputs +{ + IN_POINT_DIFFUSE_IN = IN_LIGHTING_IN +}; + +enum SpotDiffuseAtts +{ + ATT_SPOT_DIFFUSE_POSITION = ATT_SPOT_LIGHT_POSITION, + ATT_SPOT_DIFFUSE_POINTS_AT = ATT_SPOT_LIGHT_POINTS_AT, + ATT_SPOT_DIFFUSE_FOCUS = ATT_SPOT_LIGHT_FOCUS, + ATT_SPOT_DIFFUSE_LIMITING_CONE_ANGLE = ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, + ATT_SPOT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR, + ATT_SPOT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_SPOT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_SPOT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT +}; + +enum SpotDiffuseInputs +{ + IN_SPOT_DIFFUSE_IN = IN_LIGHTING_IN +}; + +enum DistantDiffuseAtts +{ + ATT_DISTANT_DIFFUSE_AZIMUTH = ATT_DISTANT_LIGHT_AZIMUTH, + ATT_DISTANT_DIFFUSE_ELEVATION = ATT_DISTANT_LIGHT_ELEVATION, + ATT_DISTANT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR, + ATT_DISTANT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_DISTANT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT +}; + +enum DistantDiffuseInputs +{ + IN_DISTANT_DIFFUSE_IN = IN_LIGHTING_IN +}; + +enum PointSpecularAtts +{ + ATT_POINT_SPECULAR_POSITION = ATT_POINT_LIGHT_POSITION, + ATT_POINT_SPECULAR_COLOR = ATT_LIGHTING_COLOR, + ATT_POINT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_POINT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_POINT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + ATT_POINT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT +}; + +enum PointSpecularInputs +{ + IN_POINT_SPECULAR_IN = IN_LIGHTING_IN +}; + +enum SpotSpecularAtts +{ + ATT_SPOT_SPECULAR_POSITION = ATT_SPOT_LIGHT_POSITION, + ATT_SPOT_SPECULAR_POINTS_AT = ATT_SPOT_LIGHT_POINTS_AT, + ATT_SPOT_SPECULAR_FOCUS = ATT_SPOT_LIGHT_FOCUS, + ATT_SPOT_SPECULAR_LIMITING_CONE_ANGLE = ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, + ATT_SPOT_SPECULAR_COLOR = ATT_LIGHTING_COLOR, + ATT_SPOT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_SPOT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_SPOT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + ATT_SPOT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT +}; + +enum SpotSpecularInputs +{ + IN_SPOT_SPECULAR_IN = IN_LIGHTING_IN +}; + +enum DistantSpecularAtts +{ + ATT_DISTANT_SPECULAR_AZIMUTH = ATT_DISTANT_LIGHT_AZIMUTH, + ATT_DISTANT_SPECULAR_ELEVATION = ATT_DISTANT_LIGHT_ELEVATION, + ATT_DISTANT_SPECULAR_COLOR = ATT_LIGHTING_COLOR, + ATT_DISTANT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_DISTANT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_DISTANT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + ATT_DISTANT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT +}; + +enum DistantSpecularInputs +{ + IN_DISTANT_SPECULAR_IN = IN_LIGHTING_IN +}; + +enum CropAtts +{ + ATT_CROP_RECT = 0 // Rect +}; + +enum CropInputs +{ + IN_CROP_IN = 0 +}; + +enum PremultiplyInputs +{ + IN_PREMULTIPLY_IN = 0 +}; + +enum UnpremultiplyInputs +{ + IN_UNPREMULTIPLY_IN = 0 +}; + +class FilterNode : public RefCounted<FilterNode> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNode) + virtual ~FilterNode() {} + + virtual FilterBackend GetBackendType() = 0; + + virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) { MOZ_CRASH("GFX: FilterNode"); } + + virtual void SetAttribute(uint32_t aIndex, bool) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, uint32_t) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, Float) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Size &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const IntSize &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const IntPoint &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Rect &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const IntRect &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Point &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Matrix &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Point3D &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Color &) { MOZ_CRASH("GFX: FilterNode"); } + virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) { MOZ_CRASH("GFX: FilterNode"); } + +protected: + friend class Factory; + + FilterNode() {} +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/GenericRefCounted.h b/gfx/2d/GenericRefCounted.h new file mode 100644 index 000000000..bee792b4d --- /dev/null +++ b/gfx/2d/GenericRefCounted.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This header provides virtual, non-templated alternatives to MFBT's RefCounted<T>. +// It intentionally uses MFBT coding style with the intention of moving there +// should there be other use cases for it. + +#ifndef MOZILLA_GENERICREFCOUNTED_H_ +#define MOZILLA_GENERICREFCOUNTED_H_ + +#include "mozilla/RefPtr.h" +#include "mozilla/RefCounted.h" + +namespace mozilla { + +/** + * Common base class for GenericRefCounted and GenericAtomicRefCounted. + * + * Having this shared base class, common to both the atomic and non-atomic + * cases, allows to have RefPtr's that don't care about whether the + * objects they're managing have atomic refcounts or not. + */ +class GenericRefCountedBase +{ + protected: + virtual ~GenericRefCountedBase() {}; + + public: + // AddRef() and Release() method names are for compatibility with nsRefPtr. + virtual void AddRef() = 0; + + virtual void Release() = 0; + + // ref() and deref() method names are for compatibility with wtf::RefPtr. + // No virtual keywords here: if a subclass wants to override the refcounting + // mechanism, it is welcome to do so by overriding AddRef() and Release(). + void ref() { AddRef(); } + void deref() { Release(); } + +#ifdef MOZ_REFCOUNTED_LEAK_CHECKING + virtual const char* typeName() const = 0; + virtual size_t typeSize() const = 0; +#endif +}; + +namespace detail { + +template<RefCountAtomicity Atomicity> +class GenericRefCounted : public GenericRefCountedBase +{ + protected: + GenericRefCounted() : refCnt(0) { } + + virtual ~GenericRefCounted() { + MOZ_ASSERT(refCnt == detail::DEAD); + } + + public: + virtual void AddRef() override { + // Note: this method must be thread safe for GenericAtomicRefCounted. + MOZ_ASSERT(int32_t(refCnt) >= 0); +#ifndef MOZ_REFCOUNTED_LEAK_CHECKING + ++refCnt; +#else + const char* type = typeName(); + uint32_t size = typeSize(); + const void* ptr = this; + MozRefCountType cnt = ++refCnt; + detail::RefCountLogger::logAddRef(ptr, cnt, type, size); +#endif + } + + virtual void Release() override { + // Note: this method must be thread safe for GenericAtomicRefCounted. + MOZ_ASSERT(int32_t(refCnt) > 0); +#ifndef MOZ_REFCOUNTED_LEAK_CHECKING + MozRefCountType cnt = --refCnt; +#else + const char* type = typeName(); + const void* ptr = this; + MozRefCountType cnt = --refCnt; + // Note: it's not safe to touch |this| after decrementing the refcount, + // except for below. + detail::RefCountLogger::logRelease(ptr, cnt, type); +#endif + if (0 == cnt) { + // Because we have atomically decremented the refcount above, only + // one thread can get a 0 count here, so as long as we can assume that + // everything else in the system is accessing this object through + // RefPtrs, it's safe to access |this| here. +#ifdef DEBUG + refCnt = detail::DEAD; +#endif + delete this; + } + } + + MozRefCountType refCount() const { return refCnt; } + bool hasOneRef() const { + MOZ_ASSERT(refCnt > 0); + return refCnt == 1; + } + + private: + typename Conditional<Atomicity == AtomicRefCount, Atomic<MozRefCountType>, MozRefCountType>::Type refCnt; +}; + +} // namespace detail + +/** + * This reference-counting base class is virtual instead of + * being templated, which is useful in cases where one needs + * genericity at binary code level, but comes at the cost + * of a moderate performance and size overhead, like anything virtual. + */ +class GenericRefCounted : public detail::GenericRefCounted<detail::NonAtomicRefCount> +{ +}; + +/** + * GenericAtomicRefCounted is like GenericRefCounted, with an atomically updated + * reference counter. + */ +class GenericAtomicRefCounted : public detail::GenericRefCounted<detail::AtomicRefCount> +{ +}; + +} // namespace mozilla + +#endif diff --git a/gfx/2d/GradientStopsD2D.h b/gfx/2d/GradientStopsD2D.h new file mode 100644 index 000000000..de6e448de --- /dev/null +++ b/gfx/2d/GradientStopsD2D.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_GRADIENTSTOPSD2D_H_ +#define MOZILLA_GFX_GRADIENTSTOPSD2D_H_ + +#include "2D.h" + +#include <d2d1.h> + +namespace mozilla { +namespace gfx { + +class GradientStopsD2D : public GradientStops +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsD2D) + GradientStopsD2D(ID2D1GradientStopCollection *aStopCollection, ID3D11Device *aDevice) + : mStopCollection(aStopCollection) + , mDevice(aDevice) + {} + + virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; } + + virtual bool IsValid() const final{ return mDevice == Factory::GetDirect3D11Device(); } + +private: + friend class DrawTargetD2D; + friend class DrawTargetD2D1; + + mutable RefPtr<ID2D1GradientStopCollection> mStopCollection; + RefPtr<ID3D11Device> mDevice; +}; + +} +} + +#endif /* MOZILLA_GFX_GRADIENTSTOPSD2D_H_ */ diff --git a/gfx/2d/Helpers.h b/gfx/2d/Helpers.h new file mode 100644 index 000000000..11c7eec5d --- /dev/null +++ b/gfx/2d/Helpers.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_2D_HELPERS_H_ +#define MOZILLA_GFX_2D_HELPERS_H_ + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class AutoRestoreTransform +{ + public: + AutoRestoreTransform() + { + } + + explicit AutoRestoreTransform(DrawTarget *aTarget) + : mDrawTarget(aTarget), + mOldTransform(aTarget->GetTransform()) + { + } + + void Init(DrawTarget *aTarget) + { + MOZ_ASSERT(!mDrawTarget || aTarget == mDrawTarget); + if (!mDrawTarget) { + mDrawTarget = aTarget; + mOldTransform = aTarget->GetTransform(); + } + } + + ~AutoRestoreTransform() + { + if (mDrawTarget) { + mDrawTarget->SetTransform(mOldTransform); + } + } + + private: + RefPtr<DrawTarget> mDrawTarget; + Matrix mOldTransform; +}; + +class AutoPopClips +{ +public: + explicit AutoPopClips(DrawTarget *aTarget) + : mDrawTarget(aTarget) + , mPushCount(0) + { + MOZ_ASSERT(mDrawTarget); + } + + ~AutoPopClips() + { + PopAll(); + } + + void PushClip(const Path *aPath) + { + mDrawTarget->PushClip(aPath); + ++mPushCount; + } + + void PushClipRect(const Rect &aRect) + { + mDrawTarget->PushClipRect(aRect); + ++mPushCount; + } + + void PopClip() + { + MOZ_ASSERT(mPushCount > 0); + mDrawTarget->PopClip(); + --mPushCount; + } + + void PopAll() + { + while (mPushCount-- > 0) { + mDrawTarget->PopClip(); + } + } + +private: + RefPtr<DrawTarget> mDrawTarget; + int32_t mPushCount; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_2D_HELPERS_H_ diff --git a/gfx/2d/HelpersCairo.h b/gfx/2d/HelpersCairo.h new file mode 100644 index 000000000..bbcce274e --- /dev/null +++ b/gfx/2d/HelpersCairo.h @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_HELPERSCAIRO_H_ +#define MOZILLA_GFX_HELPERSCAIRO_H_ + +#include "2D.h" +#include "cairo.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +static inline cairo_operator_t +GfxOpToCairoOp(CompositionOp op) +{ + switch (op) + { + case CompositionOp::OP_OVER: + return CAIRO_OPERATOR_OVER; + case CompositionOp::OP_ADD: + return CAIRO_OPERATOR_ADD; + case CompositionOp::OP_ATOP: + return CAIRO_OPERATOR_ATOP; + case CompositionOp::OP_OUT: + return CAIRO_OPERATOR_OUT; + case CompositionOp::OP_IN: + return CAIRO_OPERATOR_IN; + case CompositionOp::OP_SOURCE: + return CAIRO_OPERATOR_SOURCE; + case CompositionOp::OP_DEST_IN: + return CAIRO_OPERATOR_DEST_IN; + case CompositionOp::OP_DEST_OUT: + return CAIRO_OPERATOR_DEST_OUT; + case CompositionOp::OP_DEST_OVER: + return CAIRO_OPERATOR_DEST_OVER; + case CompositionOp::OP_DEST_ATOP: + return CAIRO_OPERATOR_DEST_ATOP; + case CompositionOp::OP_XOR: + return CAIRO_OPERATOR_XOR; + case CompositionOp::OP_MULTIPLY: + return CAIRO_OPERATOR_MULTIPLY; + case CompositionOp::OP_SCREEN: + return CAIRO_OPERATOR_SCREEN; + case CompositionOp::OP_OVERLAY: + return CAIRO_OPERATOR_OVERLAY; + case CompositionOp::OP_DARKEN: + return CAIRO_OPERATOR_DARKEN; + case CompositionOp::OP_LIGHTEN: + return CAIRO_OPERATOR_LIGHTEN; + case CompositionOp::OP_COLOR_DODGE: + return CAIRO_OPERATOR_COLOR_DODGE; + case CompositionOp::OP_COLOR_BURN: + return CAIRO_OPERATOR_COLOR_BURN; + case CompositionOp::OP_HARD_LIGHT: + return CAIRO_OPERATOR_HARD_LIGHT; + case CompositionOp::OP_SOFT_LIGHT: + return CAIRO_OPERATOR_SOFT_LIGHT; + case CompositionOp::OP_DIFFERENCE: + return CAIRO_OPERATOR_DIFFERENCE; + case CompositionOp::OP_EXCLUSION: + return CAIRO_OPERATOR_EXCLUSION; + case CompositionOp::OP_HUE: + return CAIRO_OPERATOR_HSL_HUE; + case CompositionOp::OP_SATURATION: + return CAIRO_OPERATOR_HSL_SATURATION; + case CompositionOp::OP_COLOR: + return CAIRO_OPERATOR_HSL_COLOR; + case CompositionOp::OP_LUMINOSITY: + return CAIRO_OPERATOR_HSL_LUMINOSITY; + case CompositionOp::OP_COUNT: + break; + } + + return CAIRO_OPERATOR_OVER; +} + +static inline cairo_antialias_t +GfxAntialiasToCairoAntialias(AntialiasMode antialias) +{ + switch (antialias) + { + case AntialiasMode::NONE: + return CAIRO_ANTIALIAS_NONE; + case AntialiasMode::GRAY: + return CAIRO_ANTIALIAS_GRAY; + case AntialiasMode::SUBPIXEL: + return CAIRO_ANTIALIAS_SUBPIXEL; + default: + return CAIRO_ANTIALIAS_DEFAULT; + } +} + +static inline AntialiasMode +CairoAntialiasToGfxAntialias(cairo_antialias_t aAntialias) +{ + switch(aAntialias) { + case CAIRO_ANTIALIAS_NONE: + return AntialiasMode::NONE; + case CAIRO_ANTIALIAS_GRAY: + return AntialiasMode::GRAY; + case CAIRO_ANTIALIAS_SUBPIXEL: + return AntialiasMode::SUBPIXEL; + default: + return AntialiasMode::DEFAULT; + } +} + +static inline cairo_filter_t +GfxSamplingFilterToCairoFilter(SamplingFilter filter) +{ + switch (filter) + { + case SamplingFilter::GOOD: + return CAIRO_FILTER_GOOD; + case SamplingFilter::LINEAR: + return CAIRO_FILTER_BILINEAR; + case SamplingFilter::POINT: + return CAIRO_FILTER_NEAREST; + default: + MOZ_CRASH("GFX: bad Cairo filter"); + } + + return CAIRO_FILTER_BILINEAR; +} + +static inline cairo_extend_t +GfxExtendToCairoExtend(ExtendMode extend) +{ + switch (extend) + { + case ExtendMode::CLAMP: + return CAIRO_EXTEND_PAD; + // Cairo doesn't support tiling in only 1 direction, + // So we have to fallback and tile in both. + case ExtendMode::REPEAT_X: + case ExtendMode::REPEAT_Y: + case ExtendMode::REPEAT: + return CAIRO_EXTEND_REPEAT; + case ExtendMode::REFLECT: + return CAIRO_EXTEND_REFLECT; + } + + return CAIRO_EXTEND_PAD; +} + +static inline cairo_format_t +GfxFormatToCairoFormat(SurfaceFormat format) +{ + switch (format) + { + case SurfaceFormat::A8R8G8B8_UINT32: + return CAIRO_FORMAT_ARGB32; + case SurfaceFormat::X8R8G8B8_UINT32: + return CAIRO_FORMAT_RGB24; + case SurfaceFormat::A8: + return CAIRO_FORMAT_A8; + case SurfaceFormat::R5G6B5_UINT16: + return CAIRO_FORMAT_RGB16_565; + default: + gfxCriticalError() << "Unknown image format " << (int)format; + return CAIRO_FORMAT_ARGB32; + } +} + +static inline cairo_content_t +GfxFormatToCairoContent(SurfaceFormat format) +{ + switch (format) + { + case SurfaceFormat::A8R8G8B8_UINT32: + return CAIRO_CONTENT_COLOR_ALPHA; + case SurfaceFormat::X8R8G8B8_UINT32: + case SurfaceFormat::R5G6B5_UINT16: //fall through + return CAIRO_CONTENT_COLOR; + case SurfaceFormat::A8: + return CAIRO_CONTENT_ALPHA; + default: + gfxCriticalError() << "Unknown image content format " << (int)format; + return CAIRO_CONTENT_COLOR_ALPHA; + } +} + +static inline cairo_line_join_t +GfxLineJoinToCairoLineJoin(JoinStyle style) +{ + switch (style) + { + case JoinStyle::BEVEL: + return CAIRO_LINE_JOIN_BEVEL; + case JoinStyle::ROUND: + return CAIRO_LINE_JOIN_ROUND; + case JoinStyle::MITER: + return CAIRO_LINE_JOIN_MITER; + case JoinStyle::MITER_OR_BEVEL: + return CAIRO_LINE_JOIN_MITER; + } + + return CAIRO_LINE_JOIN_MITER; +} + +static inline cairo_line_cap_t +GfxLineCapToCairoLineCap(CapStyle style) +{ + switch (style) + { + case CapStyle::BUTT: + return CAIRO_LINE_CAP_BUTT; + case CapStyle::ROUND: + return CAIRO_LINE_CAP_ROUND; + case CapStyle::SQUARE: + return CAIRO_LINE_CAP_SQUARE; + } + + return CAIRO_LINE_CAP_BUTT; +} + +static inline SurfaceFormat +CairoContentToGfxFormat(cairo_content_t content) +{ + switch (content) + { + case CAIRO_CONTENT_COLOR_ALPHA: + return SurfaceFormat::A8R8G8B8_UINT32; + case CAIRO_CONTENT_COLOR: + // BEWARE! format may be 565 + return SurfaceFormat::X8R8G8B8_UINT32; + case CAIRO_CONTENT_ALPHA: + return SurfaceFormat::A8; + } + + return SurfaceFormat::B8G8R8A8; +} + +static inline SurfaceFormat +CairoFormatToGfxFormat(cairo_format_t format) +{ + switch (format) { + case CAIRO_FORMAT_ARGB32: + return SurfaceFormat::A8R8G8B8_UINT32; + case CAIRO_FORMAT_RGB24: + return SurfaceFormat::X8R8G8B8_UINT32; + case CAIRO_FORMAT_A8: + return SurfaceFormat::A8; + case CAIRO_FORMAT_RGB16_565: + return SurfaceFormat::R5G6B5_UINT16; + default: + gfxCriticalError() << "Unknown cairo format " << format; + return SurfaceFormat::UNKNOWN; + } +} + +static inline FontHinting +CairoHintingToGfxHinting(cairo_hint_style_t aHintStyle) +{ + switch (aHintStyle) { + case CAIRO_HINT_STYLE_NONE: + return FontHinting::NONE; + case CAIRO_HINT_STYLE_SLIGHT: + return FontHinting::LIGHT; + case CAIRO_HINT_STYLE_MEDIUM: + return FontHinting::NORMAL; + case CAIRO_HINT_STYLE_FULL: + return FontHinting::FULL; + default: + return FontHinting::NORMAL; + } +} + +SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface); + +static inline void +GfxMatrixToCairoMatrix(const Matrix& mat, cairo_matrix_t& retval) +{ + cairo_matrix_init(&retval, mat._11, mat._12, mat._21, mat._22, mat._31, mat._32); +} + +static inline void +SetCairoStrokeOptions(cairo_t* aCtx, const StrokeOptions& aStrokeOptions) +{ + cairo_set_line_width(aCtx, aStrokeOptions.mLineWidth); + + cairo_set_miter_limit(aCtx, aStrokeOptions.mMiterLimit); + + if (aStrokeOptions.mDashPattern) { + // Convert array of floats to array of doubles + std::vector<double> dashes(aStrokeOptions.mDashLength); + bool nonZero = false; + for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) { + if (aStrokeOptions.mDashPattern[i] != 0) { + nonZero = true; + } + dashes[i] = aStrokeOptions.mDashPattern[i]; + } + // Avoid all-zero patterns that would trigger the CAIRO_STATUS_INVALID_DASH context error state. + if (nonZero) { + cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength, + aStrokeOptions.mDashOffset); + } + } + + cairo_set_line_join(aCtx, GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin)); + + cairo_set_line_cap(aCtx, GfxLineCapToCairoLineCap(aStrokeOptions.mLineCap)); +} + +static inline cairo_fill_rule_t +GfxFillRuleToCairoFillRule(FillRule rule) +{ + switch (rule) + { + case FillRule::FILL_WINDING: + return CAIRO_FILL_RULE_WINDING; + case FillRule::FILL_EVEN_ODD: + return CAIRO_FILL_RULE_EVEN_ODD; + } + + return CAIRO_FILL_RULE_WINDING; +} + +// RAII class for temporarily changing the cairo matrix transform. It will use +// the given matrix transform while it is in scope. When it goes out of scope +// it will put the cairo context back the way it was. + +class CairoTempMatrix +{ +public: + CairoTempMatrix(cairo_t* aCtx, const Matrix& aMatrix) + : mCtx(aCtx) + { + cairo_get_matrix(aCtx, &mSaveMatrix); + cairo_matrix_t matrix; + GfxMatrixToCairoMatrix(aMatrix, matrix); + cairo_set_matrix(aCtx, &matrix); + } + + ~CairoTempMatrix() + { + cairo_set_matrix(mCtx, &mSaveMatrix); + } + +private: + cairo_t* mCtx; + cairo_matrix_t mSaveMatrix; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_HELPERSCAIRO_H_ */ diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h new file mode 100644 index 000000000..48aec2cb5 --- /dev/null +++ b/gfx/2d/HelpersD2D.h @@ -0,0 +1,967 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_HELPERSD2D_H_ +#define MOZILLA_GFX_HELPERSD2D_H_ + +#include <d2d1_1.h> + +#include <vector> + +#include <dwrite.h> +#include <versionhelpers.h> +#include "2D.h" +#include "Logging.h" +#include "Tools.h" +#include "ImageScaling.h" + +#include "ScaledFontDWrite.h" + +#undef min +#undef max + +namespace mozilla { +namespace gfx { + +ID2D1Factory1* D2DFactory1(); +static ID2D1Factory* D2DFactory() { return D2DFactory1(); } + +static inline D2D1_POINT_2F D2DPoint(const Point &aPoint) +{ + return D2D1::Point2F(aPoint.x, aPoint.y); +} + +static inline D2D1_SIZE_U D2DIntSize(const IntSize &aSize) +{ + return D2D1::SizeU(aSize.width, aSize.height); +} + +template <typename T> +static inline D2D1_RECT_F D2DRect(const T &aRect) +{ + return D2D1::RectF(aRect.x, aRect.y, aRect.XMost(), aRect.YMost()); +} + +static inline D2D1_EXTEND_MODE D2DExtend(ExtendMode aExtendMode, Axis aAxis) +{ + D2D1_EXTEND_MODE extend; + switch (aExtendMode) { + case ExtendMode::REPEAT: + extend = D2D1_EXTEND_MODE_WRAP; + break; + case ExtendMode::REPEAT_X: + { + extend = aAxis == Axis::X_AXIS + ? D2D1_EXTEND_MODE_WRAP + : D2D1_EXTEND_MODE_CLAMP; + break; + } + case ExtendMode::REPEAT_Y: + { + extend = aAxis == Axis::Y_AXIS + ? D2D1_EXTEND_MODE_WRAP + : D2D1_EXTEND_MODE_CLAMP; + break; + } + case ExtendMode::REFLECT: + extend = D2D1_EXTEND_MODE_MIRROR; + break; + default: + extend = D2D1_EXTEND_MODE_CLAMP; + } + + return extend; +} + +static inline D2D1_BITMAP_INTERPOLATION_MODE D2DFilter(const SamplingFilter aSamplingFilter) +{ + switch (aSamplingFilter) { + case SamplingFilter::POINT: + return D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + default: + return D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; + } +} + +static inline D2D1_INTERPOLATION_MODE D2DInterpolationMode(const SamplingFilter aSamplingFilter) +{ + switch (aSamplingFilter) { + case SamplingFilter::POINT: + return D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + default: + return D2D1_INTERPOLATION_MODE_LINEAR; + } +} + +static inline D2D1_MATRIX_5X4_F D2DMatrix5x4(const Matrix5x4 &aMatrix) +{ + return D2D1::Matrix5x4F(aMatrix._11, aMatrix._12, aMatrix._13, aMatrix._14, + aMatrix._21, aMatrix._22, aMatrix._23, aMatrix._24, + aMatrix._31, aMatrix._32, aMatrix._33, aMatrix._34, + aMatrix._41, aMatrix._42, aMatrix._43, aMatrix._44, + aMatrix._51, aMatrix._52, aMatrix._53, aMatrix._54); +} + +static inline D2D1_VECTOR_3F D2DVector3D(const Point3D &aPoint) +{ + return D2D1::Vector3F(aPoint.x, aPoint.y, aPoint.z); +} + +static inline D2D1_ANTIALIAS_MODE D2DAAMode(AntialiasMode aMode) +{ + switch (aMode) { + case AntialiasMode::NONE: + return D2D1_ANTIALIAS_MODE_ALIASED; + default: + return D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + } +} + +static inline D2D1_MATRIX_3X2_F D2DMatrix(const Matrix &aTransform) +{ + return D2D1::Matrix3x2F(aTransform._11, aTransform._12, + aTransform._21, aTransform._22, + aTransform._31, aTransform._32); +} + +static inline D2D1_COLOR_F D2DColor(const Color &aColor) +{ + return D2D1::ColorF(aColor.r, aColor.g, aColor.b, aColor.a); +} + +static inline IntSize ToIntSize(const D2D1_SIZE_U &aSize) +{ + return IntSize(aSize.width, aSize.height); +} + +static inline SurfaceFormat ToPixelFormat(const D2D1_PIXEL_FORMAT &aFormat) +{ + switch(aFormat.format) { + case DXGI_FORMAT_A8_UNORM: + case DXGI_FORMAT_R8_UNORM: + return SurfaceFormat::A8; + case DXGI_FORMAT_B8G8R8A8_UNORM: + if (aFormat.alphaMode == D2D1_ALPHA_MODE_IGNORE) { + return SurfaceFormat::B8G8R8X8; + } else { + return SurfaceFormat::B8G8R8A8; + } + default: + return SurfaceFormat::B8G8R8A8; + } +} + +static inline Rect ToRect(const D2D1_RECT_F &aRect) +{ + return Rect(aRect.left, aRect.top, aRect.right - aRect.left, aRect.bottom - aRect.top); +} + +static inline Matrix ToMatrix(const D2D1_MATRIX_3X2_F &aTransform) +{ + return Matrix(aTransform._11, aTransform._12, + aTransform._21, aTransform._22, + aTransform._31, aTransform._32); +} + +static inline Point ToPoint(const D2D1_POINT_2F &aPoint) +{ + return Point(aPoint.x, aPoint.y); +} + +static inline DXGI_FORMAT DXGIFormat(SurfaceFormat aFormat) +{ + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::B8G8R8X8: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::A8: + return DXGI_FORMAT_A8_UNORM; + default: + return DXGI_FORMAT_UNKNOWN; + } +} + +static inline D2D1_ALPHA_MODE D2DAlphaModeForFormat(SurfaceFormat aFormat) +{ + switch (aFormat) { + case SurfaceFormat::B8G8R8X8: + return D2D1_ALPHA_MODE_IGNORE; + default: + return D2D1_ALPHA_MODE_PREMULTIPLIED; + } +} + +static inline D2D1_PIXEL_FORMAT D2DPixelFormat(SurfaceFormat aFormat) +{ + return D2D1::PixelFormat(DXGIFormat(aFormat), D2DAlphaModeForFormat(aFormat)); +} + +static inline bool D2DSupportsCompositeMode(CompositionOp aOp) +{ + switch(aOp) { + case CompositionOp::OP_OVER: + case CompositionOp::OP_ADD: + case CompositionOp::OP_ATOP: + case CompositionOp::OP_OUT: + case CompositionOp::OP_IN: + case CompositionOp::OP_SOURCE: + case CompositionOp::OP_DEST_IN: + case CompositionOp::OP_DEST_OUT: + case CompositionOp::OP_DEST_OVER: + case CompositionOp::OP_DEST_ATOP: + case CompositionOp::OP_XOR: + return true; + default: + return false; + } +} + +static inline D2D1_COMPOSITE_MODE D2DCompositionMode(CompositionOp aOp) +{ + switch(aOp) { + case CompositionOp::OP_OVER: + return D2D1_COMPOSITE_MODE_SOURCE_OVER; + case CompositionOp::OP_ADD: + return D2D1_COMPOSITE_MODE_PLUS; + case CompositionOp::OP_ATOP: + return D2D1_COMPOSITE_MODE_SOURCE_ATOP; + case CompositionOp::OP_OUT: + return D2D1_COMPOSITE_MODE_SOURCE_OUT; + case CompositionOp::OP_IN: + return D2D1_COMPOSITE_MODE_SOURCE_IN; + case CompositionOp::OP_SOURCE: + return D2D1_COMPOSITE_MODE_SOURCE_COPY; + case CompositionOp::OP_DEST_IN: + return D2D1_COMPOSITE_MODE_DESTINATION_IN; + case CompositionOp::OP_DEST_OUT: + return D2D1_COMPOSITE_MODE_DESTINATION_OUT; + case CompositionOp::OP_DEST_OVER: + return D2D1_COMPOSITE_MODE_DESTINATION_OVER; + case CompositionOp::OP_DEST_ATOP: + return D2D1_COMPOSITE_MODE_DESTINATION_ATOP; + case CompositionOp::OP_XOR: + return D2D1_COMPOSITE_MODE_XOR; + default: + return D2D1_COMPOSITE_MODE_SOURCE_OVER; + } +} + +static inline D2D1_BLEND_MODE D2DBlendMode(CompositionOp aOp) +{ + switch (aOp) { + case CompositionOp::OP_MULTIPLY: + return D2D1_BLEND_MODE_MULTIPLY; + case CompositionOp::OP_SCREEN: + return D2D1_BLEND_MODE_SCREEN; + case CompositionOp::OP_OVERLAY: + return D2D1_BLEND_MODE_OVERLAY; + case CompositionOp::OP_DARKEN: + return D2D1_BLEND_MODE_DARKEN; + case CompositionOp::OP_LIGHTEN: + return D2D1_BLEND_MODE_LIGHTEN; + case CompositionOp::OP_COLOR_DODGE: + return D2D1_BLEND_MODE_COLOR_DODGE; + case CompositionOp::OP_COLOR_BURN: + return D2D1_BLEND_MODE_COLOR_BURN; + case CompositionOp::OP_HARD_LIGHT: + return D2D1_BLEND_MODE_HARD_LIGHT; + case CompositionOp::OP_SOFT_LIGHT: + return D2D1_BLEND_MODE_SOFT_LIGHT; + case CompositionOp::OP_DIFFERENCE: + return D2D1_BLEND_MODE_DIFFERENCE; + case CompositionOp::OP_EXCLUSION: + return D2D1_BLEND_MODE_EXCLUSION; + case CompositionOp::OP_HUE: + return D2D1_BLEND_MODE_HUE; + case CompositionOp::OP_SATURATION: + return D2D1_BLEND_MODE_SATURATION; + case CompositionOp::OP_COLOR: + return D2D1_BLEND_MODE_COLOR; + case CompositionOp::OP_LUMINOSITY: + return D2D1_BLEND_MODE_LUMINOSITY; + default: + return D2D1_BLEND_MODE_MULTIPLY; + } +} + +static inline bool D2DSupportsPrimitiveBlendMode(CompositionOp aOp) +{ + switch (aOp) { + case CompositionOp::OP_OVER: +// case CompositionOp::OP_SOURCE: + return true; +// case CompositionOp::OP_DARKEN: + case CompositionOp::OP_ADD: + return IsWindows8Point1OrGreater(); + default: + return false; + } +} + +static inline D2D1_PRIMITIVE_BLEND D2DPrimitiveBlendMode(CompositionOp aOp) +{ + switch (aOp) { + case CompositionOp::OP_OVER: + return D2D1_PRIMITIVE_BLEND_SOURCE_OVER; + // D2D1_PRIMITIVE_BLEND_COPY should leave pixels out of the source's + // bounds unchanged, but doesn't- breaking unbounded ops. + // D2D1_PRIMITIVE_BLEND_MIN doesn't quite work like darken either, as it + // accounts for the source alpha. + // + // case CompositionOp::OP_SOURCE: + // return D2D1_PRIMITIVE_BLEND_COPY; + // case CompositionOp::OP_DARKEN: + // return D2D1_PRIMITIVE_BLEND_MIN; + case CompositionOp::OP_ADD: + return D2D1_PRIMITIVE_BLEND_ADD; + default: + return D2D1_PRIMITIVE_BLEND_SOURCE_OVER; + } +} + +static inline bool IsPatternSupportedByD2D(const Pattern &aPattern) +{ + if (aPattern.GetType() != PatternType::RADIAL_GRADIENT) { + return true; + } + + const RadialGradientPattern *pat = + static_cast<const RadialGradientPattern*>(&aPattern); + + if (pat->mRadius1 != 0) { + return false; + } + + Point diff = pat->mCenter2 - pat->mCenter1; + + if (sqrt(diff.x * diff.x + diff.y * diff.y) >= pat->mRadius2) { + // Inner point lies outside the circle. + return false; + } + + return true; +} + +/** + * This structure is used to pass rectangles to our shader constant. We can use + * this for passing rectangular areas to SetVertexShaderConstant. In the format + * of a 4 component float(x,y,width,height). Our vertex shader can then use + * this to construct rectangular positions from the 0,0-1,1 quad that we source + * it with. + */ +struct ShaderConstantRectD3D10 +{ + float mX, mY, mWidth, mHeight; + ShaderConstantRectD3D10(float aX, float aY, float aWidth, float aHeight) + : mX(aX), mY(aY), mWidth(aWidth), mHeight(aHeight) + { } + + // For easy passing to SetVertexShaderConstantF. + operator float* () { return &mX; } +}; + +static inline DWRITE_MATRIX +DWriteMatrixFromMatrix(Matrix &aMatrix) +{ + DWRITE_MATRIX mat; + mat.m11 = aMatrix._11; + mat.m12 = aMatrix._12; + mat.m21 = aMatrix._21; + mat.m22 = aMatrix._22; + mat.dx = aMatrix._31; + mat.dy = aMatrix._32; + return mat; +} + +class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN +{ + static const unsigned kNumAutoGlyphs = 256; + +public: + AutoDWriteGlyphRun() { + glyphCount = 0; + } + + ~AutoDWriteGlyphRun() { + if (glyphCount > kNumAutoGlyphs) { + delete[] glyphIndices; + delete[] glyphAdvances; + delete[] glyphOffsets; + } + } + + void allocate(unsigned aNumGlyphs) { + glyphCount = aNumGlyphs; + if (aNumGlyphs <= kNumAutoGlyphs) { + glyphIndices = &mAutoIndices[0]; + glyphAdvances = &mAutoAdvances[0]; + glyphOffsets = &mAutoOffsets[0]; + } else { + glyphIndices = new UINT16[aNumGlyphs]; + glyphAdvances = new FLOAT[aNumGlyphs]; + glyphOffsets = new DWRITE_GLYPH_OFFSET[aNumGlyphs]; + } + } + +private: + DWRITE_GLYPH_OFFSET mAutoOffsets[kNumAutoGlyphs]; + FLOAT mAutoAdvances[kNumAutoGlyphs]; + UINT16 mAutoIndices[kNumAutoGlyphs]; +}; + +static inline void +DWriteGlyphRunFromGlyphs(const GlyphBuffer &aGlyphs, ScaledFontDWrite *aFont, AutoDWriteGlyphRun *run) +{ + run->allocate(aGlyphs.mNumGlyphs); + + FLOAT *advances = const_cast<FLOAT*>(run->glyphAdvances); + UINT16 *indices = const_cast<UINT16*>(run->glyphIndices); + DWRITE_GLYPH_OFFSET *offsets = const_cast<DWRITE_GLYPH_OFFSET*>(run->glyphOffsets); + + memset(advances, 0, sizeof(FLOAT) * aGlyphs.mNumGlyphs); + for (unsigned int i = 0; i < aGlyphs.mNumGlyphs; i++) { + indices[i] = aGlyphs.mGlyphs[i].mIndex; + offsets[i].advanceOffset = aGlyphs.mGlyphs[i].mPosition.x; + offsets[i].ascenderOffset = -aGlyphs.mGlyphs[i].mPosition.y; + } + + run->bidiLevel = 0; + run->fontFace = aFont->mFontFace; + run->fontEmSize = aFont->GetSize(); + run->glyphCount = aGlyphs.mNumGlyphs; + run->isSideways = FALSE; +} + +static inline already_AddRefed<ID2D1Geometry> +ConvertRectToGeometry(const D2D1_RECT_F& aRect) +{ + RefPtr<ID2D1RectangleGeometry> rectGeom; + D2DFactory()->CreateRectangleGeometry(&aRect, getter_AddRefs(rectGeom)); + return rectGeom.forget(); +} + +static inline already_AddRefed<ID2D1Geometry> +GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform) +{ + RefPtr<ID2D1PathGeometry> tmpGeometry; + D2DFactory()->CreatePathGeometry(getter_AddRefs(tmpGeometry)); + RefPtr<ID2D1GeometrySink> currentSink; + tmpGeometry->Open(getter_AddRefs(currentSink)); + aGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + aTransform, currentSink); + currentSink->Close(); + return tmpGeometry.forget(); +} + +static inline already_AddRefed<ID2D1Geometry> +IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB) +{ + RefPtr<ID2D1PathGeometry> pathGeom; + D2DFactory()->CreatePathGeometry(getter_AddRefs(pathGeom)); + RefPtr<ID2D1GeometrySink> sink; + pathGeom->Open(getter_AddRefs(sink)); + aGeometryA->CombineWithGeometry(aGeometryB, D2D1_COMBINE_MODE_INTERSECT, nullptr, sink); + sink->Close(); + + return pathGeom.forget(); +} + +static inline already_AddRefed<ID2D1StrokeStyle> +CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions) +{ + RefPtr<ID2D1StrokeStyle> style; + + D2D1_CAP_STYLE capStyle; + D2D1_LINE_JOIN joinStyle; + + switch (aStrokeOptions.mLineCap) { + case CapStyle::BUTT: + capStyle = D2D1_CAP_STYLE_FLAT; + break; + case CapStyle::ROUND: + capStyle = D2D1_CAP_STYLE_ROUND; + break; + case CapStyle::SQUARE: + capStyle = D2D1_CAP_STYLE_SQUARE; + break; + } + + switch (aStrokeOptions.mLineJoin) { + case JoinStyle::MITER: + joinStyle = D2D1_LINE_JOIN_MITER; + break; + case JoinStyle::MITER_OR_BEVEL: + joinStyle = D2D1_LINE_JOIN_MITER_OR_BEVEL; + break; + case JoinStyle::ROUND: + joinStyle = D2D1_LINE_JOIN_ROUND; + break; + case JoinStyle::BEVEL: + joinStyle = D2D1_LINE_JOIN_BEVEL; + break; + } + + + HRESULT hr; + // We need to check mDashLength in addition to mDashPattern here since if + // mDashPattern is set but mDashLength is zero then the stroke will fail to + // paint. + if (aStrokeOptions.mDashLength > 0 && aStrokeOptions.mDashPattern) { + typedef std::vector<Float> FloatVector; + // D2D "helpfully" multiplies the dash pattern by the line width. + // That's not what cairo does, or is what <canvas>'s dash wants. + // So fix the multiplication in advance. + Float lineWidth = aStrokeOptions.mLineWidth; + FloatVector dash(aStrokeOptions.mDashPattern, + aStrokeOptions.mDashPattern + aStrokeOptions.mDashLength); + for (FloatVector::iterator it = dash.begin(); it != dash.end(); ++it) { + *it /= lineWidth; + } + + hr = D2DFactory()->CreateStrokeStyle( + D2D1::StrokeStyleProperties(capStyle, capStyle, + capStyle, joinStyle, + aStrokeOptions.mMiterLimit, + D2D1_DASH_STYLE_CUSTOM, + aStrokeOptions.mDashOffset / lineWidth), + &dash[0], // data() is not C++98, although it's in recent gcc + // and VC10's STL + dash.size(), + getter_AddRefs(style)); + } else { + hr = D2DFactory()->CreateStrokeStyle( + D2D1::StrokeStyleProperties(capStyle, capStyle, + capStyle, joinStyle, + aStrokeOptions.mMiterLimit), + nullptr, 0, getter_AddRefs(style)); + } + + if (FAILED(hr)) { + gfxWarning() << "Failed to create Direct2D stroke style."; + } + + return style.forget(); +} + +// This creates a (partially) uploaded bitmap for a DataSourceSurface. It +// uploads the minimum requirement and possibly downscales. It adjusts the +// input Matrix to compensate. +static inline already_AddRefed<ID2D1Bitmap> +CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestinationTransform, + const IntSize &aDestinationSize, ExtendMode aExtendMode, + Matrix &aSourceTransform, ID2D1RenderTarget *aRT, + const IntRect* aSourceRect = nullptr) +{ + RefPtr<ID2D1Bitmap> bitmap; + + // This is where things get complicated. The source surface was + // created for a surface that was too large to fit in a texture. + // We'll need to figure out if we can work with a partial upload + // or downsample in software. + + Matrix transform = aDestinationTransform; + Matrix invTransform = transform = aSourceTransform * transform; + if (!invTransform.Invert()) { + // Singular transform, nothing to be drawn. + return nullptr; + } + + Rect rect(0, 0, Float(aDestinationSize.width), Float(aDestinationSize.height)); + + // Calculate the rectangle of the source mapped to our surface. + rect = invTransform.TransformBounds(rect); + rect.RoundOut(); + + IntSize size = aSurface->GetSize(); + + Rect uploadRect(0, 0, Float(size.width), Float(size.height)); + if (aSourceRect) { + uploadRect = Rect(aSourceRect->x, aSourceRect->y, aSourceRect->width, aSourceRect->height); + } + + // Limit the uploadRect as much as possible without supporting discontiguous uploads + // + // region we will paint from + // uploadRect + // .---------------. .---------------. resulting uploadRect + // | |rect | | + // | .---------. .----. .----. .---------------. + // | | | ----> | | | | ----> | | + // | '---------' '----' '----' '---------------' + // '---------------' '---------------' + // + // + + if (uploadRect.Contains(rect)) { + // Extend mode is irrelevant, the displayed rect is completely contained + // by the source bitmap. + uploadRect = rect; + } else if (aExtendMode == ExtendMode::CLAMP && uploadRect.Intersects(rect)) { + // Calculate the rectangle on the source bitmap that touches our + // surface, and upload that, for ExtendMode::CLAMP we can actually guarantee + // correct behaviour in this case. + uploadRect = uploadRect.Intersect(rect); + + // We now proceed to check if we can limit at least one dimension of the + // upload rect safely without looking at extend mode. + } else if (rect.x >= 0 && rect.XMost() < size.width) { + uploadRect.x = rect.x; + uploadRect.width = rect.width; + } else if (rect.y >= 0 && rect.YMost() < size.height) { + uploadRect.y = rect.y; + uploadRect.height = rect.height; + } + + if (uploadRect.IsEmpty()) { + // Nothing to be drawn. + return nullptr; + } + + if (uploadRect.width <= aRT->GetMaximumBitmapSize() && + uploadRect.height <= aRT->GetMaximumBitmapSize()) { + { + // Scope to auto-Unmap() |mapping|. + DataSourceSurface::ScopedMap mapping(aSurface, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + + // A partial upload will suffice. + aRT->CreateBitmap(D2D1::SizeU(uint32_t(uploadRect.width), uint32_t(uploadRect.height)), + mapping.GetData() + int(uploadRect.x) * 4 + int(uploadRect.y) * mapping.GetStride(), + mapping.GetStride(), + D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), + getter_AddRefs(bitmap)); + } + + aSourceTransform.PreTranslate(uploadRect.x, uploadRect.y); + + return bitmap.forget(); + } else { + int Bpp = BytesPerPixel(aSurface->GetFormat()); + + if (Bpp != 4) { + // This shouldn't actually happen in practice! + MOZ_ASSERT(false); + return nullptr; + } + + { + // Scope to auto-Unmap() |mapping|. + DataSourceSurface::ScopedMap mapping(aSurface, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + ImageHalfScaler scaler(mapping.GetData(), mapping.GetStride(), size); + + // Calculate the maximum width/height of the image post transform. + Point topRight = transform.TransformPoint(Point(Float(size.width), 0)); + Point topLeft = transform.TransformPoint(Point(0, 0)); + Point bottomRight = transform.TransformPoint(Point(Float(size.width), Float(size.height))); + Point bottomLeft = transform.TransformPoint(Point(0, Float(size.height))); + + IntSize scaleSize; + + scaleSize.width = int32_t(std::max(Distance(topRight, topLeft), + Distance(bottomRight, bottomLeft))); + scaleSize.height = int32_t(std::max(Distance(topRight, bottomRight), + Distance(topLeft, bottomLeft))); + + if (unsigned(scaleSize.width) > aRT->GetMaximumBitmapSize()) { + // Ok, in this case we'd really want a downscale of a part of the bitmap, + // perhaps we can do this later but for simplicity let's do something + // different here and assume it's good enough, this should be rare! + scaleSize.width = 4095; + } + if (unsigned(scaleSize.height) > aRT->GetMaximumBitmapSize()) { + scaleSize.height = 4095; + } + + scaler.ScaleForSize(scaleSize); + + IntSize newSize = scaler.GetSize(); + + if (newSize.IsEmpty()) { + return nullptr; + } + + aRT->CreateBitmap(D2D1::SizeU(newSize.width, newSize.height), + scaler.GetScaledData(), scaler.GetStride(), + D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), + getter_AddRefs(bitmap)); + + aSourceTransform.PreScale(Float(size.width) / newSize.width, + Float(size.height) / newSize.height); + } + return bitmap.forget(); + } +} + +static inline void AddRectToSink(ID2D1GeometrySink* aSink, const D2D1_RECT_F& aRect) +{ + aSink->BeginFigure(D2D1::Point2F(aRect.left, aRect.top), D2D1_FIGURE_BEGIN_FILLED); + aSink->AddLine(D2D1::Point2F(aRect.right, aRect.top)); + aSink->AddLine(D2D1::Point2F(aRect.right, aRect.bottom)); + aSink->AddLine(D2D1::Point2F(aRect.left, aRect.bottom)); + aSink->EndFigure(D2D1_FIGURE_END_CLOSED); +} + +class DCCommandSink : public ID2D1CommandSink +{ +public: + DCCommandSink(ID2D1DeviceContext* aCtx) : mCtx(aCtx) + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) + { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1CommandSink) { + *aPtr = static_cast<ID2D1CommandSink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + STDMETHODIMP BeginDraw() + { + // We don't want to do anything here! + return S_OK; + } + STDMETHODIMP EndDraw() + { + // We don't want to do anything here! + return S_OK; + } + + STDMETHODIMP SetAntialiasMode( + D2D1_ANTIALIAS_MODE antialiasMode + ) + { + mCtx->SetAntialiasMode(antialiasMode); + return S_OK; + } + + STDMETHODIMP SetTags(D2D1_TAG tag1, D2D1_TAG tag2) + { + mCtx->SetTags(tag1, tag2); + return S_OK; + } + + STDMETHODIMP SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE textAntialiasMode) + { + mCtx->SetTextAntialiasMode(textAntialiasMode); + return S_OK; + } + + STDMETHODIMP SetTextRenderingParams(_In_opt_ IDWriteRenderingParams *textRenderingParams) + { + mCtx->SetTextRenderingParams(textRenderingParams); + return S_OK; + } + + STDMETHODIMP SetTransform(_In_ CONST D2D1_MATRIX_3X2_F *transform) + { + mCtx->SetTransform(transform); + return S_OK; + } + + STDMETHODIMP SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND primitiveBlend) + { + mCtx->SetPrimitiveBlend(primitiveBlend); + return S_OK; + } + + STDMETHODIMP SetUnitMode(D2D1_UNIT_MODE unitMode) + { + mCtx->SetUnitMode(unitMode); + return S_OK; + } + + STDMETHODIMP Clear(_In_opt_ CONST D2D1_COLOR_F *color) + { + mCtx->Clear(color); + return S_OK; + } + + STDMETHODIMP DrawGlyphRun( + D2D1_POINT_2F baselineOrigin, + _In_ CONST DWRITE_GLYPH_RUN *glyphRun, + _In_opt_ CONST DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription, + _In_ ID2D1Brush *foregroundBrush, + DWRITE_MEASURING_MODE measuringMode + ) + { + mCtx->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, + foregroundBrush, measuringMode); + return S_OK; + } + + STDMETHODIMP DrawLine( + D2D1_POINT_2F point0, + D2D1_POINT_2F point1, + _In_ ID2D1Brush *brush, + FLOAT strokeWidth, + _In_opt_ ID2D1StrokeStyle *strokeStyle + ) + { + mCtx->DrawLine(point0, point1, brush, strokeWidth, strokeStyle); + return S_OK; + } + + STDMETHODIMP DrawGeometry( + _In_ ID2D1Geometry *geometry, + _In_ ID2D1Brush *brush, + FLOAT strokeWidth, + _In_opt_ ID2D1StrokeStyle *strokeStyle + ) + { + mCtx->DrawGeometry(geometry, brush, strokeWidth, strokeStyle); + return S_OK; + } + + STDMETHODIMP DrawRectangle( + _In_ CONST D2D1_RECT_F *rect, + _In_ ID2D1Brush *brush, + FLOAT strokeWidth, + _In_opt_ ID2D1StrokeStyle *strokeStyle + ) + { + mCtx->DrawRectangle(rect, brush, strokeWidth, strokeStyle); + return S_OK; + } + + STDMETHODIMP DrawBitmap( + _In_ ID2D1Bitmap *bitmap, + _In_opt_ CONST D2D1_RECT_F *destinationRectangle, + FLOAT opacity, + D2D1_INTERPOLATION_MODE interpolationMode, + _In_opt_ CONST D2D1_RECT_F *sourceRectangle, + _In_opt_ CONST D2D1_MATRIX_4X4_F *perspectiveTransform + ) + { + mCtx->DrawBitmap(bitmap, destinationRectangle, opacity, + interpolationMode, sourceRectangle, + perspectiveTransform); + return S_OK; + } + + STDMETHODIMP DrawImage( + _In_ ID2D1Image *image, + _In_opt_ CONST D2D1_POINT_2F *targetOffset, + _In_opt_ CONST D2D1_RECT_F *imageRectangle, + D2D1_INTERPOLATION_MODE interpolationMode, + D2D1_COMPOSITE_MODE compositeMode + ) + { + mCtx->DrawImage(image, targetOffset, imageRectangle, + interpolationMode, compositeMode); + return S_OK; + } + + STDMETHODIMP DrawGdiMetafile( + _In_ ID2D1GdiMetafile *gdiMetafile, + _In_opt_ CONST D2D1_POINT_2F *targetOffset + ) + { + mCtx->DrawGdiMetafile(gdiMetafile, targetOffset); + return S_OK; + } + + STDMETHODIMP FillMesh( + _In_ ID2D1Mesh *mesh, + _In_ ID2D1Brush *brush + ) + { + mCtx->FillMesh(mesh, brush); + return S_OK; + } + + STDMETHODIMP FillOpacityMask( + _In_ ID2D1Bitmap *opacityMask, + _In_ ID2D1Brush *brush, + _In_opt_ CONST D2D1_RECT_F *destinationRectangle, + _In_opt_ CONST D2D1_RECT_F *sourceRectangle + ) + { + mCtx->FillOpacityMask(opacityMask, brush, destinationRectangle, + sourceRectangle); + return S_OK; + } + + STDMETHODIMP FillGeometry( + _In_ ID2D1Geometry *geometry, + _In_ ID2D1Brush *brush, + _In_opt_ ID2D1Brush *opacityBrush + ) + { + mCtx->FillGeometry(geometry, brush, opacityBrush); + return S_OK; + } + + STDMETHODIMP FillRectangle( + _In_ CONST D2D1_RECT_F *rect, + _In_ ID2D1Brush *brush + ) + { + mCtx->FillRectangle(rect, brush); + return S_OK; + } + + STDMETHODIMP PushAxisAlignedClip( + _In_ CONST D2D1_RECT_F *clipRect, + D2D1_ANTIALIAS_MODE antialiasMode + ) + { + mCtx->PushAxisAlignedClip(clipRect, antialiasMode); + return S_OK; + } + + STDMETHODIMP PushLayer( + _In_ CONST D2D1_LAYER_PARAMETERS1 *layerParameters1, + _In_opt_ ID2D1Layer *layer + ) + { + mCtx->PushLayer(layerParameters1, layer); + return S_OK; + } + + STDMETHODIMP PopAxisAlignedClip() + { + mCtx->PopAxisAlignedClip(); + return S_OK; + } + + STDMETHODIMP PopLayer() + { + mCtx->PopLayer(); + return S_OK; + } + + ID2D1DeviceContext* mCtx; +}; + +} +} + +#endif /* MOZILLA_GFX_HELPERSD2D_H_ */ diff --git a/gfx/2d/HelpersSkia.h b/gfx/2d/HelpersSkia.h new file mode 100644 index 000000000..95c67ad05 --- /dev/null +++ b/gfx/2d/HelpersSkia.h @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_HELPERSSKIA_H_ +#define MOZILLA_GFX_HELPERSSKIA_H_ + +#include "2D.h" +#include "skia/include/core/SkCanvas.h" +#include "skia/include/effects/SkDashPathEffect.h" +#include "skia/include/core/SkShader.h" +#ifdef USE_SKIA_GPU +#include "skia/include/gpu/GrTypes.h" +#endif +#include "mozilla/Assertions.h" +#include <vector> +#include "nsDebug.h" + +namespace mozilla { +namespace gfx { + +static inline SkColorType +GfxFormatToSkiaColorType(SurfaceFormat format) +{ + switch (format) + { + case SurfaceFormat::B8G8R8A8: + return kBGRA_8888_SkColorType; + case SurfaceFormat::B8G8R8X8: + // We probably need to do something here. + return kBGRA_8888_SkColorType; + case SurfaceFormat::R5G6B5_UINT16: + return kRGB_565_SkColorType; + case SurfaceFormat::A8: + return kAlpha_8_SkColorType; + default: + return kRGBA_8888_SkColorType; + } +} + +static inline SurfaceFormat +SkiaColorTypeToGfxFormat(SkColorType aColorType, SkAlphaType aAlphaType = kPremul_SkAlphaType) +{ + switch (aColorType) + { + case kBGRA_8888_SkColorType: + return aAlphaType == kOpaque_SkAlphaType ? + SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; + case kRGB_565_SkColorType: + return SurfaceFormat::R5G6B5_UINT16; + case kAlpha_8_SkColorType: + return SurfaceFormat::A8; + default: + return SurfaceFormat::B8G8R8A8; + } +} + +static inline SkAlphaType +GfxFormatToSkiaAlphaType(SurfaceFormat format) +{ + switch (format) + { + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R5G6B5_UINT16: + return kOpaque_SkAlphaType; + default: + return kPremul_SkAlphaType; + } +} + +static inline SkImageInfo +MakeSkiaImageInfo(const IntSize& aSize, SurfaceFormat aFormat) +{ + return SkImageInfo::Make(aSize.width, aSize.height, + GfxFormatToSkiaColorType(aFormat), + GfxFormatToSkiaAlphaType(aFormat)); +} + +#ifdef USE_SKIA_GPU +static inline GrPixelConfig +GfxFormatToGrConfig(SurfaceFormat format) +{ + switch (format) + { + case SurfaceFormat::B8G8R8A8: + return kBGRA_8888_GrPixelConfig; + case SurfaceFormat::B8G8R8X8: + // We probably need to do something here. + return kBGRA_8888_GrPixelConfig; + case SurfaceFormat::R5G6B5_UINT16: + return kRGB_565_GrPixelConfig; + case SurfaceFormat::A8: + return kAlpha_8_GrPixelConfig; + default: + return kRGBA_8888_GrPixelConfig; + } + +} +#endif +static inline void +GfxMatrixToSkiaMatrix(const Matrix& mat, SkMatrix& retval) +{ + retval.setAll(SkFloatToScalar(mat._11), SkFloatToScalar(mat._21), SkFloatToScalar(mat._31), + SkFloatToScalar(mat._12), SkFloatToScalar(mat._22), SkFloatToScalar(mat._32), + 0, 0, SK_Scalar1); +} + +static inline void +GfxMatrixToSkiaMatrix(const Matrix4x4& aMatrix, SkMatrix& aResult) +{ + aResult.setAll(SkFloatToScalar(aMatrix._11), SkFloatToScalar(aMatrix._21), SkFloatToScalar(aMatrix._41), + SkFloatToScalar(aMatrix._12), SkFloatToScalar(aMatrix._22), SkFloatToScalar(aMatrix._42), + SkFloatToScalar(aMatrix._14), SkFloatToScalar(aMatrix._24), SkFloatToScalar(aMatrix._44)); +} + +static inline SkPaint::Cap +CapStyleToSkiaCap(CapStyle aCap) +{ + switch (aCap) + { + case CapStyle::BUTT: + return SkPaint::kButt_Cap; + case CapStyle::ROUND: + return SkPaint::kRound_Cap; + case CapStyle::SQUARE: + return SkPaint::kSquare_Cap; + } + return SkPaint::kDefault_Cap; +} + +static inline SkPaint::Join +JoinStyleToSkiaJoin(JoinStyle aJoin) +{ + switch (aJoin) + { + case JoinStyle::BEVEL: + return SkPaint::kBevel_Join; + case JoinStyle::ROUND: + return SkPaint::kRound_Join; + case JoinStyle::MITER: + case JoinStyle::MITER_OR_BEVEL: + return SkPaint::kMiter_Join; + } + return SkPaint::kDefault_Join; +} + +static inline bool +StrokeOptionsToPaint(SkPaint& aPaint, const StrokeOptions &aOptions) +{ + // Skia renders 0 width strokes with a width of 1 (and in black), + // so we should just skip the draw call entirely. + // Skia does not handle non-finite line widths. + if (!aOptions.mLineWidth || !IsFinite(aOptions.mLineWidth)) { + return false; + } + aPaint.setStrokeWidth(SkFloatToScalar(aOptions.mLineWidth)); + aPaint.setStrokeMiter(SkFloatToScalar(aOptions.mMiterLimit)); + aPaint.setStrokeCap(CapStyleToSkiaCap(aOptions.mLineCap)); + aPaint.setStrokeJoin(JoinStyleToSkiaJoin(aOptions.mLineJoin)); + + if (aOptions.mDashLength > 0) { + // Skia only supports dash arrays that are multiples of 2. + uint32_t dashCount; + + if (aOptions.mDashLength % 2 == 0) { + dashCount = aOptions.mDashLength; + } else { + dashCount = aOptions.mDashLength * 2; + } + + std::vector<SkScalar> pattern; + pattern.resize(dashCount); + + for (uint32_t i = 0; i < dashCount; i++) { + pattern[i] = SkFloatToScalar(aOptions.mDashPattern[i % aOptions.mDashLength]); + } + + sk_sp<SkPathEffect> dash = SkDashPathEffect::Make(&pattern.front(), + dashCount, + SkFloatToScalar(aOptions.mDashOffset)); + aPaint.setPathEffect(dash); + } + + aPaint.setStyle(SkPaint::kStroke_Style); + return true; +} + +static inline SkBlendMode +GfxOpToSkiaOp(CompositionOp op) +{ + switch (op) + { + case CompositionOp::OP_OVER: + return SkBlendMode::kSrcOver; + case CompositionOp::OP_ADD: + return SkBlendMode::kPlus; + case CompositionOp::OP_ATOP: + return SkBlendMode::kSrcATop; + case CompositionOp::OP_OUT: + return SkBlendMode::kSrcOut; + case CompositionOp::OP_IN: + return SkBlendMode::kSrcIn; + case CompositionOp::OP_SOURCE: + return SkBlendMode::kSrc; + case CompositionOp::OP_DEST_IN: + return SkBlendMode::kDstIn; + case CompositionOp::OP_DEST_OUT: + return SkBlendMode::kDstOut; + case CompositionOp::OP_DEST_OVER: + return SkBlendMode::kDstOver; + case CompositionOp::OP_DEST_ATOP: + return SkBlendMode::kDstATop; + case CompositionOp::OP_XOR: + return SkBlendMode::kXor; + case CompositionOp::OP_MULTIPLY: + return SkBlendMode::kMultiply; + case CompositionOp::OP_SCREEN: + return SkBlendMode::kScreen; + case CompositionOp::OP_OVERLAY: + return SkBlendMode::kOverlay; + case CompositionOp::OP_DARKEN: + return SkBlendMode::kDarken; + case CompositionOp::OP_LIGHTEN: + return SkBlendMode::kLighten; + case CompositionOp::OP_COLOR_DODGE: + return SkBlendMode::kColorDodge; + case CompositionOp::OP_COLOR_BURN: + return SkBlendMode::kColorBurn; + case CompositionOp::OP_HARD_LIGHT: + return SkBlendMode::kHardLight; + case CompositionOp::OP_SOFT_LIGHT: + return SkBlendMode::kSoftLight; + case CompositionOp::OP_DIFFERENCE: + return SkBlendMode::kDifference; + case CompositionOp::OP_EXCLUSION: + return SkBlendMode::kExclusion; + case CompositionOp::OP_HUE: + return SkBlendMode::kHue; + case CompositionOp::OP_SATURATION: + return SkBlendMode::kSaturation; + case CompositionOp::OP_COLOR: + return SkBlendMode::kColor; + case CompositionOp::OP_LUMINOSITY: + return SkBlendMode::kLuminosity; + default: + return SkBlendMode::kSrcOver; + } +} + +/* There's quite a bit of inconsistency about + * whether float colors should be rounded with .5f. + * We choose to do it to match cairo which also + * happens to match the Direct3D specs */ +static inline U8CPU ColorFloatToByte(Float color) +{ + //XXX: do a better job converting to int + return U8CPU(color*255.f + .5f); +}; + +static inline SkColor ColorToSkColor(const Color &color, Float aAlpha) +{ + return SkColorSetARGB(ColorFloatToByte(color.a*aAlpha), ColorFloatToByte(color.r), + ColorFloatToByte(color.g), ColorFloatToByte(color.b)); +} + +static inline SkPoint +PointToSkPoint(const Point &aPoint) +{ + return SkPoint::Make(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y)); +} + +static inline SkRect +RectToSkRect(const Rect& aRect) +{ + return SkRect::MakeXYWH(SkFloatToScalar(aRect.x), SkFloatToScalar(aRect.y), + SkFloatToScalar(aRect.width), SkFloatToScalar(aRect.height)); +} + +static inline SkRect +IntRectToSkRect(const IntRect& aRect) +{ + return SkRect::MakeXYWH(SkIntToScalar(aRect.x), SkIntToScalar(aRect.y), + SkIntToScalar(aRect.width), SkIntToScalar(aRect.height)); +} + +static inline SkIRect +RectToSkIRect(const Rect& aRect) +{ + return SkIRect::MakeXYWH(int32_t(aRect.x), int32_t(aRect.y), + int32_t(aRect.width), int32_t(aRect.height)); +} + +static inline SkIRect +IntRectToSkIRect(const IntRect& aRect) +{ + return SkIRect::MakeXYWH(aRect.x, aRect.y, aRect.width, aRect.height); +} + +static inline Point +SkPointToPoint(const SkPoint &aPoint) +{ + return Point(SkScalarToFloat(aPoint.x()), SkScalarToFloat(aPoint.y())); +} + +static inline Rect +SkRectToRect(const SkRect &aRect) +{ + return Rect(SkScalarToFloat(aRect.x()), SkScalarToFloat(aRect.y()), + SkScalarToFloat(aRect.width()), SkScalarToFloat(aRect.height())); +} + +static inline SkShader::TileMode +ExtendModeToTileMode(ExtendMode aMode, Axis aAxis) +{ + switch (aMode) + { + case ExtendMode::CLAMP: + return SkShader::kClamp_TileMode; + case ExtendMode::REPEAT: + return SkShader::kRepeat_TileMode; + case ExtendMode::REFLECT: + return SkShader::kMirror_TileMode; + case ExtendMode::REPEAT_X: + { + return aAxis == Axis::X_AXIS + ? SkShader::kRepeat_TileMode + : SkShader::kClamp_TileMode; + } + case ExtendMode::REPEAT_Y: + { + return aAxis == Axis::Y_AXIS + ? SkShader::kRepeat_TileMode + : SkShader::kClamp_TileMode; + } + } + return SkShader::kClamp_TileMode; +} + +static inline SkPaint::Hinting +GfxHintingToSkiaHinting(FontHinting aHinting) +{ + switch (aHinting) { + case FontHinting::NONE: + return SkPaint::kNo_Hinting; + case FontHinting::LIGHT: + return SkPaint::kSlight_Hinting; + case FontHinting::NORMAL: + return SkPaint::kNormal_Hinting; + case FontHinting::FULL: + return SkPaint::kFull_Hinting; + } + return SkPaint::kNormal_Hinting; +} + +static inline FillRule GetFillRule(SkPath::FillType aFillType) +{ + switch (aFillType) + { + case SkPath::kWinding_FillType: + return FillRule::FILL_WINDING; + case SkPath::kEvenOdd_FillType: + return FillRule::FILL_EVEN_ODD; + case SkPath::kInverseWinding_FillType: + case SkPath::kInverseEvenOdd_FillType: + default: + NS_WARNING("Unsupported fill type\n"); + break; + } + + return FillRule::FILL_EVEN_ODD; +} + +/** + * Returns true if the canvas is backed by pixels. Returns false if the canvas + * wraps an SkPDFDocument, for example. + * + * Note: It is not clear whether the test used to implement this function may + * result in it returning false in some circumstances even when the canvas + * _is_ pixel backed. In other words maybe it is possible for such a canvas to + * have kUnknown_SkPixelGeometry? + */ +static inline bool IsBackedByPixels(const SkCanvas* aCanvas) +{ + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + if (!aCanvas->getProps(&props) || + props.pixelGeometry() == kUnknown_SkPixelGeometry) { + return false; + } + return true; +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_HELPERSSKIA_H_ */ diff --git a/gfx/2d/HelpersWinFonts.h b/gfx/2d/HelpersWinFonts.h new file mode 100644 index 000000000..cd84070d4 --- /dev/null +++ b/gfx/2d/HelpersWinFonts.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace mozilla { +namespace gfx { + +// Cleartype can be dynamically enabled/disabled, so we have to check it +// everytime we want to render some text. +static BYTE +GetSystemTextQuality() +{ + BOOL font_smoothing; + UINT smoothing_type; + + if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) { + return DEFAULT_QUALITY; + } + + if (font_smoothing) { + if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, + 0, &smoothing_type, 0)) { + return DEFAULT_QUALITY; + } + + if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) { + return CLEARTYPE_QUALITY; + } + + return ANTIALIASED_QUALITY; + } + + return DEFAULT_QUALITY; +} + +static AntialiasMode +GetSystemDefaultAAMode() +{ + AntialiasMode defaultMode = AntialiasMode::SUBPIXEL; + if (gfxPrefs::DisableAllTextAA()) { + return AntialiasMode::NONE; + } + + switch (GetSystemTextQuality()) { + case CLEARTYPE_QUALITY: + defaultMode = AntialiasMode::SUBPIXEL; + break; + case ANTIALIASED_QUALITY: + defaultMode = AntialiasMode::GRAY; + break; + case DEFAULT_QUALITY: + defaultMode = AntialiasMode::NONE; + break; + } + + return defaultMode; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ImageScaling.cpp b/gfx/2d/ImageScaling.cpp new file mode 100644 index 000000000..190b7a7b9 --- /dev/null +++ b/gfx/2d/ImageScaling.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageScaling.h" +#include "2D.h" +#include "DataSurfaceHelpers.h" + +#include <math.h> +#include <algorithm> + +using namespace std; + +namespace mozilla { +namespace gfx { + +inline uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d) +{ + // Prepare half-adder work + uint32_t sum = a ^ b ^ c; + uint32_t carry = (a & b) | (a & c) | (b & c); + + // Before shifting, mask lower order bits of each byte to avoid underflow. + uint32_t mask = 0xfefefefe; + + // Add d to sum and divide by 2. + sum = (((sum ^ d) & mask) >> 1) + (sum & d); + + // Sum is now shifted into place relative to carry, add them together. + return (((sum ^ carry) & mask) >> 1) + (sum & carry); +} + +inline uint32_t Avg2(uint32_t a, uint32_t b) +{ + // Prepare half-adder work + uint32_t sum = a ^ b; + uint32_t carry = (a & b); + + // Before shifting, mask lower order bits of each byte to avoid underflow. + uint32_t mask = 0xfefefefe; + + // Add d to sum and divide by 2. + return ((sum & mask) >> 1) + carry; +} + +void +ImageHalfScaler::ScaleForSize(const IntSize &aSize) +{ + uint32_t horizontalDownscales = 0; + uint32_t verticalDownscales = 0; + + IntSize scaleSize = mOrigSize; + while ((scaleSize.height / 2) > aSize.height) { + verticalDownscales++; + scaleSize.height /= 2; + } + + while ((scaleSize.width / 2) > aSize.width) { + horizontalDownscales++; + scaleSize.width /= 2; + } + + if (scaleSize == mOrigSize) { + return; + } + + delete [] mDataStorage; + + IntSize internalSurfSize; + internalSurfSize.width = max(scaleSize.width, mOrigSize.width / 2); + internalSurfSize.height = max(scaleSize.height, mOrigSize.height / 2); + + size_t bufLen = 0; + mStride = GetAlignedStride<16>(internalSurfSize.width, 4); + if (mStride > 0) { + // Allocate 15 bytes extra to make sure we can get 16 byte alignment. We + // should add tools for this, see bug 751696. + bufLen = BufferSizeFromStrideAndHeight(mStride, internalSurfSize.height, 15); + } + + if (bufLen == 0) { + mSize.SizeTo(0, 0); + mDataStorage = nullptr; + return; + } + mDataStorage = new uint8_t[bufLen]; + + if (uintptr_t(mDataStorage) % 16) { + // Our storage does not start at a 16-byte boundary. Make sure mData does! + mData = (uint8_t*)(uintptr_t(mDataStorage) + + (16 - (uintptr_t(mDataStorage) % 16))); + } else { + mData = mDataStorage; + } + + mSize = scaleSize; + + /* The surface we sample from might not be even sized, if it's not we will + * ignore the last row/column. This means we lose some data but it keeps the + * code very simple. There's also no perfect answer that provides a better + * solution. + */ + IntSize currentSampledSize = mOrigSize; + uint32_t currentSampledStride = mOrigStride; + uint8_t *currentSampledData = mOrigData; + + while (verticalDownscales && horizontalDownscales) { + if (currentSampledSize.width % 2) { + currentSampledSize.width -= 1; + } + if (currentSampledSize.height % 2) { + currentSampledSize.height -= 1; + } + + HalfImage2D(currentSampledData, currentSampledStride, currentSampledSize, + mData, mStride); + + verticalDownscales--; + horizontalDownscales--; + currentSampledSize.width /= 2; + currentSampledSize.height /= 2; + currentSampledData = mData; + currentSampledStride = mStride; + } + + while (verticalDownscales) { + if (currentSampledSize.height % 2) { + currentSampledSize.height -= 1; + } + + HalfImageVertical(currentSampledData, currentSampledStride, currentSampledSize, + mData, mStride); + + verticalDownscales--; + currentSampledSize.height /= 2; + currentSampledData = mData; + currentSampledStride = mStride; + } + + + while (horizontalDownscales) { + if (currentSampledSize.width % 2) { + currentSampledSize.width -= 1; + } + + HalfImageHorizontal(currentSampledData, currentSampledStride, currentSampledSize, + mData, mStride); + + horizontalDownscales--; + currentSampledSize.width /= 2; + currentSampledData = mData; + currentSampledStride = mStride; + } +} + +void +ImageHalfScaler::HalfImage2D(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + HalfImage2D_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } else +#endif + { + HalfImage2D_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } +} + +void +ImageHalfScaler::HalfImageVertical(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + HalfImageVertical_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } else +#endif + { + HalfImageVertical_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } +} + +void +ImageHalfScaler::HalfImageHorizontal(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + HalfImageHorizontal_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } else +#endif + { + HalfImageHorizontal_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } +} + +void +ImageHalfScaler::HalfImage2D_C(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ + for (int y = 0; y < aSourceSize.height; y += 2) { + uint32_t *storage = (uint32_t*)(aDest + (y / 2) * aDestStride); + for (int x = 0; x < aSourceSize.width; x += 2) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + *storage++ = Avg2x2(*(uint32_t*)upperRow, *((uint32_t*)upperRow + 1), + *(uint32_t*)lowerRow, *((uint32_t*)lowerRow + 1)); + } + } +} + +void +ImageHalfScaler::HalfImageVertical_C(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ + for (int y = 0; y < aSourceSize.height; y += 2) { + uint32_t *storage = (uint32_t*)(aDest + (y / 2) * aDestStride); + for (int x = 0; x < aSourceSize.width; x++) { + uint32_t *upperRow = (uint32_t*)(aSource + (y * aSourceStride + x * 4)); + uint32_t *lowerRow = (uint32_t*)(aSource + ((y + 1) * aSourceStride + x * 4)); + + *storage++ = Avg2(*upperRow, *lowerRow); + } + } +} + +void +ImageHalfScaler::HalfImageHorizontal_C(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ + for (int y = 0; y < aSourceSize.height; y++) { + uint32_t *storage = (uint32_t*)(aDest + y * aDestStride); + for (int x = 0; x < aSourceSize.width; x+= 2) { + uint32_t *pixels = (uint32_t*)(aSource + (y * aSourceStride + x * 4)); + + *storage++ = Avg2(*pixels, *(pixels + 1)); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ImageScaling.h b/gfx/2d/ImageScaling.h new file mode 100644 index 000000000..56173b644 --- /dev/null +++ b/gfx/2d/ImageScaling.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_IMAGESCALING_H +#define _MOZILLA_GFX_IMAGESCALING_H + +#include "Types.h" + +#include <vector> +#include "Point.h" + +namespace mozilla { +namespace gfx { + +class ImageHalfScaler +{ +public: + ImageHalfScaler(uint8_t *aData, int32_t aStride, const IntSize &aSize) + : mOrigData(aData), mOrigStride(aStride), mOrigSize(aSize) + , mDataStorage(nullptr) + { + } + + ~ImageHalfScaler() + { + delete [] mDataStorage; + } + + void ScaleForSize(const IntSize &aSize); + + uint8_t *GetScaledData() const { return mData; } + IntSize GetSize() const { return mSize; } + uint32_t GetStride() const { return mStride; } + +private: + void HalfImage2D(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + void HalfImageVertical(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + void HalfImageHorizontal(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + + // This is our SSE2 scaling function. Our destination must always be 16-byte + // aligned and use a 16-byte aligned stride. + void HalfImage2D_SSE2(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + void HalfImageVertical_SSE2(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + void HalfImageHorizontal_SSE2(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + + void HalfImage2D_C(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + void HalfImageVertical_C(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + void HalfImageHorizontal_C(uint8_t *aSource, int32_t aSourceStride, const IntSize &aSourceSize, + uint8_t *aDest, uint32_t aDestStride); + + uint8_t *mOrigData; + int32_t mOrigStride; + IntSize mOrigSize; + + uint8_t *mDataStorage; + // Guaranteed 16-byte aligned + uint8_t *mData; + IntSize mSize; + // Guaranteed 16-byte aligned + uint32_t mStride; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/ImageScalingSSE2.cpp b/gfx/2d/ImageScalingSSE2.cpp new file mode 100644 index 000000000..1f4d4778e --- /dev/null +++ b/gfx/2d/ImageScalingSSE2.cpp @@ -0,0 +1,330 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageScaling.h" +#include "mozilla/Attributes.h" + +#include "SSEHelpers.h" + +/* The functions below use the following system for averaging 4 pixels: + * + * The first observation is that a half-adder is implemented as follows: + * R = S + 2C or in the case of a and b (a ^ b) + ((a & b) << 1); + * + * This can be trivially extended to three pixels by observaring that when + * doing (a ^ b ^ c) as the sum, the carry is simply the bitwise-or of the + * carries of the individual numbers, since the sum of 3 bits can only ever + * have a carry of one. + * + * We then observe that the average is then ((carry << 1) + sum) >> 1, or, + * assuming eliminating overflows and underflows, carry + (sum >> 1). + * + * We now average our existing sum with the fourth number, so we get: + * sum2 = (sum + d) >> 1 or (sum >> 1) + (d >> 1). + * + * We now observe that our sum has been moved into place relative to the + * carry, so we can now average with the carry to get the final 4 input + * average: avg = (sum2 + carry) >> 1; + * + * Or to reverse the proof: + * avg = ((sum >> 1) + carry + d >> 1) >> 1 + * avg = ((a + b + c) >> 1 + d >> 1) >> 1 + * avg = ((a + b + c + d) >> 2) + * + * An additional fact used in the SSE versions is the concept that we can + * trivially convert a rounded average to a truncated average: + * + * We have: + * f(a, b) = (a + b + 1) >> 1 + * + * And want: + * g(a, b) = (a + b) >> 1 + * + * Observe: + * ~f(~a, ~b) == ~((~a + ~b + 1) >> 1) + * == ~((-a - 1 + -b - 1 + 1) >> 1) + * == ~((-a - 1 + -b) >> 1) + * == ~((-(a + b) - 1) >> 1) + * == ~((~(a + b)) >> 1) + * == (a + b) >> 1 + * == g(a, b) + */ + +MOZ_ALWAYS_INLINE __m128i _mm_not_si128(__m128i arg) +{ + __m128i minusone = _mm_set1_epi32(0xffffffff); + return _mm_xor_si128(arg, minusone); +} + +/* We have to pass pointers here, MSVC does not allow passing more than 3 + * __m128i arguments on the stack. And it does not allow 16-byte aligned + * stack variables. This inlines properly on MSVC 2010. It does -not- inline + * with just the inline directive. + */ +MOZ_ALWAYS_INLINE __m128i avg_sse2_8x2(__m128i *a, __m128i *b, __m128i *c, __m128i *d) +{ +#define shuf1 _MM_SHUFFLE(2, 0, 2, 0) +#define shuf2 _MM_SHUFFLE(3, 1, 3, 1) + +// This cannot be an inline function as the __Imm argument to _mm_shuffle_ps +// needs to be a compile time constant. +#define shuffle_si128(arga, argb, imm) \ + _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps((arga)), _mm_castsi128_ps((argb)), (imm))); + + __m128i t = shuffle_si128(*a, *b, shuf1); + *b = shuffle_si128(*a, *b, shuf2); + *a = t; + t = shuffle_si128(*c, *d, shuf1); + *d = shuffle_si128(*c, *d, shuf2); + *c = t; + +#undef shuf1 +#undef shuf2 +#undef shuffle_si128 + + __m128i sum = _mm_xor_si128(*a, _mm_xor_si128(*b, *c)); + + __m128i carry = _mm_or_si128(_mm_and_si128(*a, *b), _mm_or_si128(_mm_and_si128(*a, *c), _mm_and_si128(*b, *c))); + + sum = _mm_avg_epu8(_mm_not_si128(sum), _mm_not_si128(*d)); + + return _mm_not_si128(_mm_avg_epu8(sum, _mm_not_si128(carry))); +} + +MOZ_ALWAYS_INLINE __m128i avg_sse2_4x2_4x1(__m128i a, __m128i b) +{ + return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); +} + +MOZ_ALWAYS_INLINE __m128i avg_sse2_8x1_4x1(__m128i a, __m128i b) +{ + __m128i t = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(3, 1, 3, 1))); + b = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(2, 0, 2, 0))); + a = t; + + return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); +} + +MOZ_ALWAYS_INLINE uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d) +{ + uint32_t sum = a ^ b ^ c; + uint32_t carry = (a & b) | (a & c) | (b & c); + + uint32_t mask = 0xfefefefe; + + // Not having a byte based average instruction means we should mask to avoid + // underflow. + sum = (((sum ^ d) & mask) >> 1) + (sum & d); + + return (((sum ^ carry) & mask) >> 1) + (sum & carry); +} + +// Simple 2 pixel average version of the function above. +MOZ_ALWAYS_INLINE uint32_t Avg2(uint32_t a, uint32_t b) +{ + uint32_t sum = a ^ b; + uint32_t carry = (a & b); + + uint32_t mask = 0xfefefefe; + + return ((sum & mask) >> 1) + carry; +} + +namespace mozilla { +namespace gfx { + +void +ImageHalfScaler::HalfImage2D_SSE2(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ + const int Bpp = 4; + + for (int y = 0; y < aSourceSize.height; y += 2) { + __m128i *storage = (__m128i*)(aDest + (y / 2) * aDestStride); + int x = 0; + // Run a loop depending on alignment. + if (!(uintptr_t(aSource + (y * aSourceStride)) % 16) && + !(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = _mm_load_si128(upperRow); + __m128i b = _mm_load_si128(upperRow + 1); + __m128i c = _mm_load_si128(lowerRow); + __m128i d = _mm_load_si128(lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } else if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = _mm_load_si128(upperRow); + __m128i b = _mm_load_si128(upperRow + 1); + __m128i c = loadUnaligned128(lowerRow); + __m128i d = loadUnaligned128(lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } else if (!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = loadUnaligned128((__m128i*)upperRow); + __m128i b = loadUnaligned128((__m128i*)upperRow + 1); + __m128i c = _mm_load_si128((__m128i*)lowerRow); + __m128i d = _mm_load_si128((__m128i*)lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } else { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i *upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i *lowerRow = (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = loadUnaligned128(upperRow); + __m128i b = loadUnaligned128(upperRow + 1); + __m128i c = loadUnaligned128(lowerRow); + __m128i d = loadUnaligned128(lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } + + uint32_t *unalignedStorage = (uint32_t*)storage; + // Take care of the final pixels, we know there's an even number of pixels + // in the source rectangle. We use a 2x2 'simd' implementation for this. + // + // Potentially we only have to do this in the last row since overflowing + // 8 pixels in an earlier row would appear to be harmless as it doesn't + // touch invalid memory. Even when reading and writing to the same surface. + // in practice we only do this when doing an additional downscale pass, and + // in this situation we have unused stride to write into harmlessly. + // I do not believe the additional code complexity would be worth it though. + for (; x < aSourceSize.width; x += 2) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * Bpp); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * Bpp); + + *unalignedStorage++ = Avg2x2(*(uint32_t*)upperRow, *((uint32_t*)upperRow + 1), + *(uint32_t*)lowerRow, *((uint32_t*)lowerRow + 1)); + } + } +} + +void +ImageHalfScaler::HalfImageVertical_SSE2(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ + for (int y = 0; y < aSourceSize.height; y += 2) { + __m128i *storage = (__m128i*)(aDest + (y / 2) * aDestStride); + int x = 0; + // Run a loop depending on alignment. + if (!(uintptr_t(aSource + (y * aSourceStride)) % 16) && + !(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = _mm_load_si128((__m128i*)upperRow); + __m128i b = _mm_load_si128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } else if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) { + // This line doesn't align well. + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = _mm_load_si128((__m128i*)upperRow); + __m128i b = loadUnaligned128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } else if (!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = loadUnaligned128((__m128i*)upperRow); + __m128i b = _mm_load_si128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } else { + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = loadUnaligned128((__m128i*)upperRow); + __m128i b = loadUnaligned128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } + + uint32_t *unalignedStorage = (uint32_t*)storage; + // Take care of the final pixels, we know there's an even number of pixels + // in the source rectangle. + // + // Similar overflow considerations are valid as in the previous function. + for (; x < aSourceSize.width; x++) { + uint8_t *upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t *lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + *unalignedStorage++ = Avg2(*(uint32_t*)upperRow, *(uint32_t*)lowerRow); + } + } +} + +void +ImageHalfScaler::HalfImageHorizontal_SSE2(uint8_t *aSource, int32_t aSourceStride, + const IntSize &aSourceSize, uint8_t *aDest, + uint32_t aDestStride) +{ + for (int y = 0; y < aSourceSize.height; y++) { + __m128i *storage = (__m128i*)(aDest + (y * aDestStride)); + int x = 0; + // Run a loop depending on alignment. + if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* pixels = (__m128i*)(aSource + (y * aSourceStride + x * 4)); + + __m128i a = _mm_load_si128(pixels); + __m128i b = _mm_load_si128(pixels + 1); + + *storage++ = avg_sse2_8x1_4x1(a, b); + } + } else { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* pixels = (__m128i*)(aSource + (y * aSourceStride + x * 4)); + + __m128i a = loadUnaligned128(pixels); + __m128i b = loadUnaligned128(pixels + 1); + + *storage++ = avg_sse2_8x1_4x1(a, b); + } + } + + uint32_t *unalignedStorage = (uint32_t*)storage; + // Take care of the final pixels, we know there's an even number of pixels + // in the source rectangle. + // + // Similar overflow considerations are valid as in the previous function. + for (; x < aSourceSize.width; x += 2) { + uint32_t *pixels = (uint32_t*)(aSource + (y * aSourceStride + x * 4)); + + *unalignedStorage++ = Avg2(*pixels, *(pixels + 1)); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/IterableArena.h b/gfx/2d/IterableArena.h new file mode 100644 index 000000000..a444c9a38 --- /dev/null +++ b/gfx/2d/IterableArena.h @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_ITERABLEARENA_H_ +#define MOZILLA_GFX_ITERABLEARENA_H_ + +#include "mozilla/Move.h" +#include "mozilla/Assertions.h" +#include "mozilla/gfx/Logging.h" + +#include <string.h> +#include <vector> +#include <stdint.h> +#include <stdio.h> + +namespace mozilla { +namespace gfx { + +/// A simple pool allocator for plain data structures. +/// +/// Beware that the pool will not attempt to run the destructors. It is the +/// responsibility of the user of this class to either use objects with no +/// destructor or to manually call the allocated objects destructors. +/// If the pool is growable, its allocated objects must be safely moveable in +/// in memory (through memcpy). +class IterableArena { +protected: + struct Header + { + size_t mBlocSize; + }; +public: + enum ArenaType { + FIXED_SIZE, + GROWABLE + }; + + IterableArena(ArenaType aType, size_t aStorageSize) + : mSize(aStorageSize) + , mCursor(0) + , mIsGrowable(aType == GROWABLE) + { + if (mSize == 0) { + mSize = 128; + } + + mStorage = (uint8_t*)malloc(mSize); + if (mStorage == nullptr) { + gfxCriticalError() << "Not enough Memory allocate a memory pool of size " << aStorageSize; + MOZ_CRASH("GFX: Out of memory IterableArena"); + } + } + + ~IterableArena() + { + free(mStorage); + } + + /// Constructs a new item in the pool and returns a positive offset in case of + /// success. + /// + /// The offset never changes even if the storage is reallocated, so users + /// of this class should prefer storing offsets rather than direct pointers + /// to the allocated objects. + /// Alloc can cause the storage to be reallocated if the pool was initialized + /// with IterableArena::GROWABLE. + /// If for any reason the pool fails to allocate enough space for the new item + /// Alloc returns a negative offset and the object's constructor is not called. + template<typename T, typename... Args> + ptrdiff_t + Alloc(Args&&... aArgs) + { + void* storage = nullptr; + auto offset = AllocRaw(sizeof(T), &storage); + if (offset < 0) { + return offset; + } + new (storage) T(Forward<Args>(aArgs)...); + return offset; + } + + ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr) + { + const size_t blocSize = AlignedSize(sizeof(Header) + aSize); + + if (AlignedSize(mCursor + blocSize) > mSize) { + if (!mIsGrowable) { + return -1; + } + + size_t newSize = mSize * 2; + while (AlignedSize(mCursor + blocSize) > newSize) { + newSize *= 2; + } + + uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize); + if (!newStorage) { + gfxCriticalError() << "Not enough Memory to grow the memory pool, size: " << newSize; + return -1; + } + + mStorage = newStorage; + mSize = newSize; + } + ptrdiff_t offset = mCursor; + GetHeader(offset)->mBlocSize = blocSize; + mCursor += blocSize; + if (aOutPtr) { + *aOutPtr = GetStorage(offset); + } + return offset; + } + + /// Get access to an allocated item at a given offset (only use offsets returned + /// by Alloc or AllocRaw). + /// + /// If the pool is growable, the returned pointer is only valid temporarily. The + /// underlying storage can be reallocated in Alloc or AllocRaw, so do not keep + /// these pointers around and store the offset instead. + void* GetStorage(ptrdiff_t offset = 0) + { + MOZ_ASSERT(offset >= 0); + MOZ_ASSERT(offset < mCursor); + return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr; + } + + /// Clears the storage without running any destructor and without deallocating it. + void Clear() + { + mCursor = 0; + } + + /// Iterate over the elements allocated in this pool. + /// + /// Takes a lambda or function object accepting a void* as parameter. + template<typename Func> + void ForEach(Func cb) + { + Iterator it; + while (void* ptr = it.Next(this)) { + cb(ptr); + } + } + + /// A simple iterator over an arena. + class Iterator { + public: + Iterator() + : mCursor(0) + {} + + void* Next(IterableArena* aArena) + { + if (mCursor >= aArena->mCursor) { + return nullptr; + } + void* result = aArena->GetStorage(mCursor); + const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize; + MOZ_ASSERT(blocSize != 0); + mCursor += blocSize; + return result; + } + + private: + ptrdiff_t mCursor; + }; + +protected: + Header* GetHeader(ptrdiff_t offset) + { + return (Header*) (mStorage + offset); + } + + size_t AlignedSize(size_t aSize) const + { + const size_t alignment = sizeof(uintptr_t); + return aSize + (alignment - (aSize % alignment)) % alignment; + } + + uint8_t* mStorage; + uint32_t mSize; + ptrdiff_t mCursor; + bool mIsGrowable; + + friend class Iterator; +}; + +} // namespace +} // namespace + +#endif diff --git a/gfx/2d/JobScheduler.cpp b/gfx/2d/JobScheduler.cpp new file mode 100644 index 000000000..2c687cde0 --- /dev/null +++ b/gfx/2d/JobScheduler.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "JobScheduler.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +JobScheduler* JobScheduler::sSingleton = nullptr; + +bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues) +{ + MOZ_ASSERT(!sSingleton); + MOZ_ASSERT(aNumThreads >= aNumQueues); + + sSingleton = new JobScheduler(); + sSingleton->mNextQueue = 0; + + for (uint32_t i = 0; i < aNumQueues; ++i) { + sSingleton->mDrawingQueues.push_back(new MultiThreadedJobQueue()); + } + + for (uint32_t i = 0; i < aNumThreads; ++i) { + sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues])); + } + return true; +} + +void JobScheduler::ShutDown() +{ + MOZ_ASSERT(IsEnabled()); + if (!IsEnabled()) { + return; + } + + for (auto queue : sSingleton->mDrawingQueues) { + queue->ShutDown(); + delete queue; + } + + for (WorkerThread* thread : sSingleton->mWorkerThreads) { + // this will block until the thread is joined. + delete thread; + } + + sSingleton->mWorkerThreads.clear(); + delete sSingleton; + sSingleton = nullptr; +} + +JobStatus +JobScheduler::ProcessJob(Job* aJob) +{ + MOZ_ASSERT(aJob); + auto status = aJob->Run(); + if (status == JobStatus::Error || status == JobStatus::Complete) { + delete aJob; + } + return status; +} + +void +JobScheduler::SubmitJob(Job* aJob) +{ + MOZ_ASSERT(aJob); + RefPtr<SyncObject> start = aJob->GetStartSync(); + if (start && start->Register(aJob)) { + // The Job buffer starts with a non-signaled sync object, it + // is now registered in the list of task buffers waiting on the + // sync object, so we should not place it in the queue. + return; + } + + GetQueueForJob(aJob)->SubmitJob(aJob); +} + +void +JobScheduler::Join(SyncObject* aCompletion) +{ + RefPtr<EventObject> waitForCompletion = new EventObject(); + JobScheduler::SubmitJob(new SetEventJob(waitForCompletion, aCompletion)); + waitForCompletion->Wait(); +} + +MultiThreadedJobQueue* +JobScheduler::GetQueueForJob(Job* aJob) +{ + return aJob->IsPinnedToAThread() ? aJob->GetWorkerThread()->GetJobQueue() + : GetDrawingQueue(); +} + +Job::Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread) +: mNextWaitingJob(nullptr) +, mStartSync(aStart) +, mCompletionSync(aCompletion) +, mPinToThread(aThread) +{ + if (mStartSync) { + mStartSync->AddSubsequent(this); + } + if (mCompletionSync) { + mCompletionSync->AddPrerequisite(this); + } +} + +Job::~Job() +{ + if (mCompletionSync) { + //printf(" -- Job %p dtor completion %p\n", this, mCompletionSync); + mCompletionSync->Signal(); + mCompletionSync = nullptr; + } +} + +JobStatus +SetEventJob::Run() +{ + mEvent->Set(); + return JobStatus::Complete; +} + +SetEventJob::SetEventJob(EventObject* aEvent, + SyncObject* aStart, SyncObject* aCompletion, + WorkerThread* aWorker) +: Job(aStart, aCompletion, aWorker) +, mEvent(aEvent) +{} + +SetEventJob::~SetEventJob() +{} + +SyncObject::SyncObject(uint32_t aNumPrerequisites) +: mSignals(aNumPrerequisites) +, mFirstWaitingJob(nullptr) +#ifdef DEBUG +, mNumPrerequisites(aNumPrerequisites) +, mAddedPrerequisites(0) +#endif +{} + +SyncObject::~SyncObject() +{ + MOZ_ASSERT(mFirstWaitingJob == nullptr); +} + +bool +SyncObject::Register(Job* aJob) +{ + MOZ_ASSERT(aJob); + + // For now, ensure that when we schedule the first subsequent, we have already + // created all of the prerequisites. This is an arbitrary restriction because + // we specify the number of prerequisites in the constructor, but in the typical + // scenario, if the assertion FreezePrerequisite blows up here it probably means + // we got the initial nmber of prerequisites wrong. We can decide to remove + // this restriction if needed. + FreezePrerequisites(); + + int32_t signals = mSignals; + + if (signals > 0) { + AddWaitingJob(aJob); + // Since Register and Signal can be called concurrently, it can happen that + // reading mSignals in Register happens before decrementing mSignals in Signal, + // but SubmitWaitingJobs happens before AddWaitingJob. This ordering means + // the SyncObject ends up in the signaled state with a task sitting in the + // waiting list. To prevent that we check mSignals a second time and submit + // again if signals reached zero in the mean time. + // We do this instead of holding a mutex around mSignals+mJobs to reduce + // lock contention. + int32_t signals2 = mSignals; + if (signals2 == 0) { + SubmitWaitingJobs(); + } + return true; + } + + return false; +} + +void +SyncObject::Signal() +{ + int32_t signals = --mSignals; + MOZ_ASSERT(signals >= 0); + + if (signals == 0) { + SubmitWaitingJobs(); + } +} + +void +SyncObject::AddWaitingJob(Job* aJob) +{ + // Push (using atomics) the task into the list of waiting tasks. + for (;;) { + Job* first = mFirstWaitingJob; + aJob->mNextWaitingJob = first; + if (mFirstWaitingJob.compareExchange(first, aJob)) { + break; + } + } +} + +void SyncObject::SubmitWaitingJobs() +{ + // Scheduling the tasks can cause code that modifies <this>'s reference + // count to run concurrently, and cause the caller of this function to + // be owned by another thread. We need to make sure the reference count + // does not reach 0 on another thread before the end of this method, so + // hold a strong ref to prevent that! + RefPtr<SyncObject> kungFuDeathGrip(this); + + // First atomically swap mFirstWaitingJob and waitingJobs... + Job* waitingJobs = nullptr; + for (;;) { + waitingJobs = mFirstWaitingJob; + if (mFirstWaitingJob.compareExchange(waitingJobs, nullptr)) { + break; + } + } + + // ... and submit all of the waiting tasks in waitingJob now that they belong + // to this thread. + while (waitingJobs) { + Job* next = waitingJobs->mNextWaitingJob; + waitingJobs->mNextWaitingJob = nullptr; + JobScheduler::GetQueueForJob(waitingJobs)->SubmitJob(waitingJobs); + waitingJobs = next; + } +} + +bool +SyncObject::IsSignaled() +{ + return mSignals == 0; +} + +void +SyncObject::FreezePrerequisites() +{ + MOZ_ASSERT(mAddedPrerequisites == mNumPrerequisites); +} + +void +SyncObject::AddPrerequisite(Job* aJob) +{ + MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites); +} + +void +SyncObject::AddSubsequent(Job* aJob) +{ +} + +WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue) +: mQueue(aJobQueue) +{ + aJobQueue->RegisterThread(); +} + +void +WorkerThread::Run() +{ + SetName("gfx worker"); + + for (;;) { + Job* commands = nullptr; + if (!mQueue->WaitForJob(commands)) { + mQueue->UnregisterThread(); + return; + } + + JobStatus status = JobScheduler::ProcessJob(commands); + + if (status == JobStatus::Error) { + // Don't try to handle errors for now, but that's open to discussions. + // I expect errors to be mostly OOM issues. + gfxDevCrash(LogReason::JobStatusError) << "Invalid job status " << (int)status; + } + } +} + +} //namespace +} //namespace diff --git a/gfx/2d/JobScheduler.h b/gfx/2d/JobScheduler.h new file mode 100644 index 000000000..483842904 --- /dev/null +++ b/gfx/2d/JobScheduler.h @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TASKSCHEDULER_H_ +#define MOZILLA_GFX_TASKSCHEDULER_H_ + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/RefCounted.h" + +#ifdef WIN32 +#include "mozilla/gfx/JobScheduler_win32.h" +#else +#include "mozilla/gfx/JobScheduler_posix.h" +#endif + +#include <vector> + +namespace mozilla { +namespace gfx { + +class MultiThreadedJobQueue; +class SyncObject; +class WorkerThread; + +class JobScheduler { +public: + /// Return one of the queues that the drawing worker threads pull from, chosen + /// pseudo-randomly. + static MultiThreadedJobQueue* GetDrawingQueue() + { + return sSingleton->mDrawingQueues[ + sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size() + ]; + } + + /// Return one of the queues that the drawing worker threads pull from with a + /// hash to choose the queue. + /// + /// Calling this function several times with the same hash will yield the same queue. + static MultiThreadedJobQueue* GetDrawingQueue(uint32_t aHash) + { + return sSingleton->mDrawingQueues[ + aHash % sSingleton->mDrawingQueues.size() + ]; + } + + /// Return the task queue associated to the worker the task is pinned to if + /// the task is pinned to a worker, or a random queue. + static MultiThreadedJobQueue* GetQueueForJob(Job* aJob); + + /// Initialize the task scheduler with aNumThreads worker threads for drawing + /// and aNumQueues task queues. + /// + /// The number of threads must be superior or equal to the number of queues + /// (since for now a worker thread only pulls from one queue). + static bool Init(uint32_t aNumThreads, uint32_t aNumQueues); + + /// Shut the scheduler down. + /// + /// This will block until worker threads are joined and deleted. + static void ShutDown(); + + /// Returns true if there is a successfully initialized JobScheduler singleton. + static bool IsEnabled() { return !!sSingleton; } + + /// Submit a task buffer to its associated queue. + /// + /// The caller looses ownership of the task buffer. + static void SubmitJob(Job* aJobs); + + /// Convenience function to block the current thread until a given SyncObject + /// is in the signaled state. + /// + /// The current thread will first try to steal jobs before blocking. + static void Join(SyncObject* aCompletionSync); + + /// Process commands until the command buffer needs to block on a sync object, + /// completes, yields, or encounters an error. + /// + /// Can be used on any thread. Worker threads basically loop over this, but the + /// main thread can also dequeue pending task buffers and process them alongside + /// the worker threads if it is about to block until completion anyway. + /// + /// The caller looses ownership of the task buffer. + static JobStatus ProcessJob(Job* aJobs); + +protected: + static JobScheduler* sSingleton; + + // queues of Job that are ready to be processed + std::vector<MultiThreadedJobQueue*> mDrawingQueues; + std::vector<WorkerThread*> mWorkerThreads; + Atomic<uint32_t> mNextQueue; +}; + +/// Jobs are not reference-counted because they don't have shared ownership. +/// The ownership of tasks can change when they are passed to certain methods +/// of JobScheduler and SyncObject. See the docuumentaion of these classes. +class Job { +public: + Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread = nullptr); + + virtual ~Job(); + + virtual JobStatus Run() = 0; + + /// For use in JobScheduler::SubmitJob. Don't use it anywhere else. + //already_AddRefed<SyncObject> GetAndResetStartSync(); + SyncObject* GetStartSync() { return mStartSync; } + + bool IsPinnedToAThread() const { return !!mPinToThread; } + + WorkerThread* GetWorkerThread() { return mPinToThread; } + +protected: + // An intrusive linked list of tasks waiting for a sync object to enter the + // signaled state. When the task is not waiting for a sync object, mNextWaitingJob + // should be null. This is only accessed from the thread that owns the task. + Job* mNextWaitingJob; + + RefPtr<SyncObject> mStartSync; + RefPtr<SyncObject> mCompletionSync; + WorkerThread* mPinToThread; + + friend class SyncObject; +}; + +class EventObject; + +/// This task will set an EventObject. +/// +/// Typically used as the final task, so that the main thread can block on the +/// corresponfing EventObject until all of the tasks are processed. +class SetEventJob : public Job +{ +public: + explicit SetEventJob(EventObject* aEvent, + SyncObject* aStart, SyncObject* aCompletion = nullptr, + WorkerThread* aPinToWorker = nullptr); + + ~SetEventJob(); + + JobStatus Run() override; + + EventObject* GetEvent() { return mEvent; } + +protected: + RefPtr<EventObject> mEvent; +}; + +/// A synchronization object that can be used to express dependencies and ordering between +/// tasks. +/// +/// Jobs can register to SyncObjects in order to asynchronously wait for a signal. +/// In practice, Job objects usually start with a sync object (startSyc) and end +/// with another one (completionSync). +/// a Job never gets processed before its startSync is in the signaled state, and +/// signals its completionSync as soon as it finishes. This is how dependencies +/// between tasks is expressed. +class SyncObject final : public external::AtomicRefCounted<SyncObject> { +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject) + + /// Create a synchronization object. + /// + /// aNumPrerequisites represents the number of times the object must be signaled + /// before actually entering the signaled state (in other words, it means the + /// number of dependencies of this sync object). + /// + /// Explicitly specifying the number of prerequisites when creating sync objects + /// makes it easy to start scheduling some of the prerequisite tasks while + /// creating the others, which is how we typically use the task scheduler. + /// Automatically determining the number of prerequisites using Job's constructor + /// brings the risk that the sync object enters the signaled state while we + /// are still adding prerequisites which is hard to fix without using muteces. + explicit SyncObject(uint32_t aNumPrerequisites = 1); + + ~SyncObject(); + + /// Attempt to register a task. + /// + /// If the sync object is already in the signaled state, the buffer is *not* + /// registered and the sync object does not take ownership of the task. + /// If the object is not yet in the signaled state, it takes ownership of + /// the task and places it in a list of pending tasks. + /// Pending tasks will not be processed by the worker thread. + /// When the SyncObject reaches the signaled state, it places the pending + /// tasks back in the available buffer queue, so that they can be + /// scheduled again. + /// + /// Returns true if the SyncOject is not already in the signaled state. + /// This means that if this method returns true, the SyncObject has taken + /// ownership of the Job. + bool Register(Job* aJob); + + /// Signal the SyncObject. + /// + /// This decrements an internal counter. The sync object reaches the signaled + /// state when the counter gets to zero. + void Signal(); + + /// Returns true if mSignals is equal to zero. In other words, returns true + /// if all prerequisite tasks have already signaled the sync object. + bool IsSignaled(); + + /// Asserts that the number of added prerequisites is equal to the number + /// specified in the constructor (does nothin in release builds). + void FreezePrerequisites(); + +private: + // Called by Job's constructor + void AddSubsequent(Job* aJob); + void AddPrerequisite(Job* aJob); + + void AddWaitingJob(Job* aJob); + + void SubmitWaitingJobs(); + + Atomic<int32_t> mSignals; + Atomic<Job*> mFirstWaitingJob; + +#ifdef DEBUG + uint32_t mNumPrerequisites; + Atomic<uint32_t> mAddedPrerequisites; +#endif + + friend class Job; + friend class JobScheduler; +}; + +/// Base class for worker threads. +class WorkerThread +{ +public: + static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue); + + virtual ~WorkerThread() {} + + void Run(); + + MultiThreadedJobQueue* GetJobQueue() { return mQueue; } + +protected: + explicit WorkerThread(MultiThreadedJobQueue* aJobQueue); + + virtual void SetName(const char* aName) {} + + MultiThreadedJobQueue* mQueue; +}; + +} // namespace +} // namespace + +#endif diff --git a/gfx/2d/JobScheduler_posix.cpp b/gfx/2d/JobScheduler_posix.cpp new file mode 100644 index 000000000..e41388f21 --- /dev/null +++ b/gfx/2d/JobScheduler_posix.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "JobScheduler.h" +#include "mozilla/gfx/Logging.h" + +using namespace std; + +namespace mozilla { +namespace gfx { + +void* ThreadCallback(void* threadData); + +class WorkerThreadPosix : public WorkerThread { +public: + explicit WorkerThreadPosix(MultiThreadedJobQueue* aJobQueue) + : WorkerThread(aJobQueue) + { + pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this)); + } + + ~WorkerThreadPosix() + { + pthread_join(mThread, nullptr); + } + + virtual void SetName(const char*) override + { +// XXX - temporarily disabled, see bug 1209039 +// +// // Call this from the thread itself because of Mac. +//#ifdef XP_MACOSX +// pthread_setname_np(aName); +//#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) +// pthread_set_name_np(mThread, aName); +//#elif defined(__NetBSD__) +// pthread_setname_np(mThread, "%s", (void*)aName); +//#else +// pthread_setname_np(mThread, aName); +//#endif + } + +protected: + pthread_t mThread; +}; + +void* ThreadCallback(void* threadData) +{ + WorkerThread* thread = static_cast<WorkerThread*>(threadData); + thread->Run(); + return nullptr; +} + +WorkerThread* +WorkerThread::Create(MultiThreadedJobQueue* aJobQueue) +{ + return new WorkerThreadPosix(aJobQueue); +} + +MultiThreadedJobQueue::MultiThreadedJobQueue() +: mThreadsCount(0) +, mShuttingDown(false) +{} + +MultiThreadedJobQueue::~MultiThreadedJobQueue() +{ + MOZ_ASSERT(mJobs.empty()); +} + +bool +MultiThreadedJobQueue::WaitForJob(Job*& aOutJob) +{ + return PopJob(aOutJob, BLOCKING); +} + +bool +MultiThreadedJobQueue::PopJob(Job*& aOutJobs, AccessType aAccess) +{ + for (;;) { + CriticalSectionAutoEnter lock(&mMutex); + + while (aAccess == BLOCKING && !mShuttingDown && mJobs.empty()) { + mAvailableCondvar.Wait(&mMutex); + } + + if (mShuttingDown) { + return false; + } + + if (mJobs.empty()) { + if (aAccess == NON_BLOCKING) { + return false; + } + continue; + } + + Job* task = mJobs.front(); + MOZ_ASSERT(task); + + mJobs.pop_front(); + + aOutJobs = task; + return true; + } +} + +void +MultiThreadedJobQueue::SubmitJob(Job* aJobs) +{ + MOZ_ASSERT(aJobs); + CriticalSectionAutoEnter lock(&mMutex); + mJobs.push_back(aJobs); + mAvailableCondvar.Broadcast(); +} + +size_t +MultiThreadedJobQueue::NumJobs() +{ + CriticalSectionAutoEnter lock(&mMutex); + return mJobs.size(); +} + +bool +MultiThreadedJobQueue::IsEmpty() +{ + CriticalSectionAutoEnter lock(&mMutex); + return mJobs.empty(); +} + +void +MultiThreadedJobQueue::ShutDown() +{ + CriticalSectionAutoEnter lock(&mMutex); + mShuttingDown = true; + while (mThreadsCount) { + mAvailableCondvar.Broadcast(); + mShutdownCondvar.Wait(&mMutex); + } +} + +void +MultiThreadedJobQueue::RegisterThread() +{ + mThreadsCount += 1; +} + +void +MultiThreadedJobQueue::UnregisterThread() +{ + CriticalSectionAutoEnter lock(&mMutex); + mThreadsCount -= 1; + if (mThreadsCount == 0) { + mShutdownCondvar.Broadcast(); + } +} + +EventObject::EventObject() +: mIsSet(false) +{} + +EventObject::~EventObject() +{} + +bool +EventObject::Peak() +{ + CriticalSectionAutoEnter lock(&mMutex); + return mIsSet; +} + +void +EventObject::Set() +{ + CriticalSectionAutoEnter lock(&mMutex); + if (!mIsSet) { + mIsSet = true; + mCond.Broadcast(); + } +} + +void +EventObject::Wait() +{ + CriticalSectionAutoEnter lock(&mMutex); + if (mIsSet) { + return; + } + mCond.Wait(&mMutex); +} + +} // namespce +} // namespce diff --git a/gfx/2d/JobScheduler_posix.h b/gfx/2d/JobScheduler_posix.h new file mode 100644 index 000000000..cc1bef84e --- /dev/null +++ b/gfx/2d/JobScheduler_posix.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WIN32 +#ifndef MOZILLA_GFX_TASKSCHEDULER_POSIX_H_ +#define MOZILLA_GFX_TASKSCHEDULER_POSIX_H_ + +#include <string> +#include <vector> +#include <list> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> + +#include "mozilla/RefPtr.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/CriticalSection.h" +#include "mozilla/RefCounted.h" + +namespace mozilla { +namespace gfx { + +class Job; +class PosixCondVar; +class WorkerThread; + +// posix platforms only! +class PosixCondVar { +public: + PosixCondVar() { + DebugOnly<int> err = pthread_cond_init(&mCond, nullptr); + MOZ_ASSERT(!err); + } + + ~PosixCondVar() { + DebugOnly<int> err = pthread_cond_destroy(&mCond); + MOZ_ASSERT(!err); + } + + void Wait(CriticalSection* aMutex) { + DebugOnly<int> err = pthread_cond_wait(&mCond, &aMutex->mMutex); + MOZ_ASSERT(!err); + } + + void Broadcast() { + DebugOnly<int> err = pthread_cond_broadcast(&mCond); + MOZ_ASSERT(!err); + } + +protected: + pthread_cond_t mCond; +}; + + +/// A simple and naive multithreaded task queue +/// +/// The public interface of this class must remain identical to its equivalent +/// in JobScheduler_win32.h +class MultiThreadedJobQueue { +public: + enum AccessType { + BLOCKING, + NON_BLOCKING + }; + + // Producer thread + MultiThreadedJobQueue(); + + // Producer thread + ~MultiThreadedJobQueue(); + + // Worker threads + bool WaitForJob(Job*& aOutJob); + + // Any thread + bool PopJob(Job*& aOutJob, AccessType aAccess); + + // Any threads + void SubmitJob(Job* aJob); + + // Producer thread + void ShutDown(); + + // Any thread + size_t NumJobs(); + + // Any thread + bool IsEmpty(); + + // Producer thread + void RegisterThread(); + + // Worker threads + void UnregisterThread(); + +protected: + + std::list<Job*> mJobs; + CriticalSection mMutex; + PosixCondVar mAvailableCondvar; + PosixCondVar mShutdownCondvar; + int32_t mThreadsCount; + bool mShuttingDown; + + friend class WorkerThread; +}; + +/// An object that a thread can synchronously wait on. +/// Usually set by a SetEventJob. +class EventObject : public external::AtomicRefCounted<EventObject> +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject) + + EventObject(); + + ~EventObject(); + + /// Synchronously wait until the event is set. + void Wait(); + + /// Return true if the event is set, without blocking. + bool Peak(); + + /// Set the event. + void Set(); + +protected: + CriticalSection mMutex; + PosixCondVar mCond; + bool mIsSet; +}; + +} // namespace +} // namespace + +#include "JobScheduler.h" + +#endif +#endif diff --git a/gfx/2d/JobScheduler_win32.cpp b/gfx/2d/JobScheduler_win32.cpp new file mode 100644 index 000000000..989965adc --- /dev/null +++ b/gfx/2d/JobScheduler_win32.cpp @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "JobScheduler.h" +#include "mozilla/gfx/Logging.h" + +using namespace std; + +namespace mozilla { +namespace gfx { + +DWORD __stdcall ThreadCallback(void* threadData); + +class WorkerThreadWin32 : public WorkerThread { +public: + explicit WorkerThreadWin32(MultiThreadedJobQueue* aJobQueue) + : WorkerThread(aJobQueue) + { + mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr); + } + + ~WorkerThreadWin32() + { + ::WaitForSingleObject(mThread, INFINITE); + ::CloseHandle(mThread); + } + +protected: + HANDLE mThread; +}; + +DWORD __stdcall ThreadCallback(void* threadData) +{ + WorkerThread* thread = static_cast<WorkerThread*>(threadData); + thread->Run(); + return 0; +} + +WorkerThread* +WorkerThread::Create(MultiThreadedJobQueue* aJobQueue) +{ + return new WorkerThreadWin32(aJobQueue); +} + +bool +MultiThreadedJobQueue::PopJob(Job*& aOutJob, AccessType aAccess) +{ + for (;;) { + while (aAccess == BLOCKING && mJobs.empty()) { + { + CriticalSectionAutoEnter lock(&mSection); + if (mShuttingDown) { + return false; + } + } + + HANDLE handles[] = { mAvailableEvent, mShutdownEvent }; + ::WaitForMultipleObjects(2, handles, FALSE, INFINITE); + } + + CriticalSectionAutoEnter lock(&mSection); + + if (mShuttingDown) { + return false; + } + + if (mJobs.empty()) { + if (aAccess == NON_BLOCKING) { + return false; + } + continue; + } + + Job* task = mJobs.front(); + MOZ_ASSERT(task); + + mJobs.pop_front(); + + if (mJobs.empty()) { + ::ResetEvent(mAvailableEvent); + } + + aOutJob = task; + return true; + } +} + +void +MultiThreadedJobQueue::SubmitJob(Job* aJob) +{ + MOZ_ASSERT(aJob); + CriticalSectionAutoEnter lock(&mSection); + mJobs.push_back(aJob); + ::SetEvent(mAvailableEvent); +} + +void +MultiThreadedJobQueue::ShutDown() +{ + { + CriticalSectionAutoEnter lock(&mSection); + mShuttingDown = true; + } + while (mThreadsCount) { + ::SetEvent(mAvailableEvent); + ::WaitForSingleObject(mShutdownEvent, INFINITE); + } +} + +size_t +MultiThreadedJobQueue::NumJobs() +{ + CriticalSectionAutoEnter lock(&mSection); + return mJobs.size(); +} + +bool +MultiThreadedJobQueue::IsEmpty() +{ + CriticalSectionAutoEnter lock(&mSection); + return mJobs.empty(); +} + +void +MultiThreadedJobQueue::RegisterThread() +{ + mThreadsCount += 1; +} + +void +MultiThreadedJobQueue::UnregisterThread() +{ + mSection.Enter(); + mThreadsCount -= 1; + bool finishShutdown = mThreadsCount == 0; + mSection.Leave(); + + if (finishShutdown) { + // Can't touch mSection or any other member from now on because this object + // may get deleted on the main thread after mShutdownEvent is set. + ::SetEvent(mShutdownEvent); + } +} + +} // namespace +} // namespace diff --git a/gfx/2d/JobScheduler_win32.h b/gfx/2d/JobScheduler_win32.h new file mode 100644 index 000000000..73ccbd4ed --- /dev/null +++ b/gfx/2d/JobScheduler_win32.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef WIN32 +#ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_ +#define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_ + +#include <windows.h> +#include <list> + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/CriticalSection.h" +#include "mozilla/RefCounted.h" + +namespace mozilla { +namespace gfx { + +class WorkerThread; +class Job; + +// The public interface of this class must remain identical to its equivalent +// in JobScheduler_posix.h +class MultiThreadedJobQueue { +public: + enum AccessType { + BLOCKING, + NON_BLOCKING + }; + + MultiThreadedJobQueue() + : mThreadsCount(0) + , mShuttingDown(false) + { + mAvailableEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); + mShutdownEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); + } + + ~MultiThreadedJobQueue() + { + ::CloseHandle(mAvailableEvent); + ::CloseHandle(mShutdownEvent); + } + + bool WaitForJob(Job*& aOutJob) { return PopJob(aOutJob, BLOCKING); } + + bool PopJob(Job*& aOutJob, AccessType aAccess); + + void SubmitJob(Job* aJob); + + void ShutDown(); + + size_t NumJobs(); + + bool IsEmpty(); + + void RegisterThread(); + + void UnregisterThread(); + +protected: + std::list<Job*> mJobs; + CriticalSection mSection; + HANDLE mAvailableEvent; + HANDLE mShutdownEvent; + int32_t mThreadsCount; + bool mShuttingDown; + + friend class WorkerThread; +}; + + +// The public interface of this class must remain identical to its equivalent +// in JobScheduler_posix.h +class EventObject : public external::AtomicRefCounted<EventObject> +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject) + + EventObject() { mEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); } + + ~EventObject() { ::CloseHandle(mEvent); } + + void Wait() { ::WaitForSingleObject(mEvent, INFINITE); } + + bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; } + + void Set() { ::SetEvent(mEvent); } +protected: + // TODO: it's expensive to create events so we should try to reuse them + HANDLE mEvent; +}; + +} // namespace +} // namespace + +#endif +#endif diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h new file mode 100644 index 000000000..e6be2a7e8 --- /dev/null +++ b/gfx/2d/Logging.h @@ -0,0 +1,706 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LOGGING_H_ +#define MOZILLA_GFX_LOGGING_H_ + +#include <string> +#include <sstream> +#include <stdio.h> +#include <vector> + +#ifdef MOZ_LOGGING +#include "mozilla/Logging.h" +#endif +#include "mozilla/Tuple.h" + +#if defined(MOZ_WIDGET_ANDROID) +#include "nsDebug.h" +#endif +#include "Point.h" +#include "BaseRect.h" +#include "Matrix.h" +#include "LoggingConstants.h" + +#if defined(MOZ_LOGGING) +extern GFX2D_API mozilla::LogModule* GetGFX2DLog(); +#endif + +namespace mozilla { +namespace gfx { + +#if defined(MOZ_LOGGING) +inline mozilla::LogLevel PRLogLevelForLevel(int aLevel) { + switch (aLevel) { + case LOG_CRITICAL: + return LogLevel::Error; + case LOG_WARNING: + return LogLevel::Warning; + case LOG_DEBUG: + return LogLevel::Debug; + case LOG_DEBUG_PRLOG: + return LogLevel::Debug; + case LOG_EVERYTHING: + return LogLevel::Error; + } + return LogLevel::Debug; +} +#endif + +class LoggingPrefs +{ +public: + // Used to choose the level of logging we get. The higher the number, + // the more logging we get. Value of zero will give you no logging, + // 1 just errors, 2 adds warnings and 3 or 4 add debug logging. + // In addition to setting the value to 4, you will need to set the + // environment variable MOZ_LOG to gfx:4. See mozilla/Logging.h for details. + static int32_t sGfxLogLevel; +}; + +/// Graphics logging is available in both debug and release builds and is +/// controlled with a gfx.logging.level preference. If not set, the default +/// for the preference is 5 in the debug builds, 1 in the release builds. +/// +/// gfxDebug only works in the debug builds, and is used for information +/// level messages, helping with debugging. In addition to only working +/// in the debug builds, the value of the above preference of 3 or higher +/// is required. +/// +/// gfxWarning messages are available in both debug and release builds, +/// on by default in the debug builds, and off by default in the release builds. +/// Setting the preference gfx.logging.level to a value of 2 or higher will +/// show the warnings. +/// +/// gfxCriticalError is available in debug and release builds by default. +/// It is only unavailable if gfx.logging.level is set to 0 (or less.) +/// It outputs the message to stderr or equivalent, like gfxWarning. +/// In the event of a crash, the crash report is annotated with first and +/// the last few of these errors, under the key GraphicsCriticalError. +/// The total number of errors stored in the crash report is controlled +/// by preference gfx.logging.crash.length. +/// +/// On platforms that support MOZ_LOGGING, the story is slightly more involved. +/// In that case, unless gfx.logging.level is set to 4 or higher, the output +/// is further controlled by the "gfx2d" logging module. However, in the case +/// where such module would disable the output, in all but gfxDebug cases, +/// we will still send a printf. + +// The range is due to the values set in Histograms.json +enum class LogReason : int { + MustBeMoreThanThis = -1, + // Start. Do not insert, always add at end. If you remove items, + // make sure the other items retain their values. + D3D11InvalidCallDeviceRemoved = 0, + D3D11InvalidCall, + D3DLockTimeout, + D3D10FinalizeFrame, + D3D11FinalizeFrame, + D3D10SyncLock, + D3D11SyncLock, + D2D1NoWriteMap, + JobStatusError, + FilterInputError, + FilterInputData, // 10 + FilterInputRect, + FilterInputSet, + FilterInputFormat, + FilterNodeD2D1Target, + FilterNodeD2D1Backend, + SourceSurfaceIncompatible, + GlyphAllocFailedCairo, + GlyphAllocFailedCG, + InvalidRect, + CannotDraw3D, // 20 + IncompatibleBasicTexturedEffect, + InvalidFont, + PAllocTextureBackendMismatch, + GetFontFileDataFailed, + MessageChannelCloseFailure, + MessageChannelInvalidHandle, + TextureAliveAfterShutdown, + InvalidContext, + InvalidCommandList, + AsyncTransactionTimeout, // 30 + TextureCreation, + InvalidCacheSurface, + AlphaWithBasicClient, + UnbalancedClipStack, + ProcessingError, + NativeFontResourceNotFound, + // End + MustBeLessThanThis = 101, +}; + +struct BasicLogger +{ + // For efficiency, this method exists and copies the logic of the + // OutputMessage below. If making any changes here, also make it + // in the appropriate places in that method. + static bool ShouldOutputMessage(int aLevel) { + if (LoggingPrefs::sGfxLogLevel >= aLevel) { +#if defined(MOZ_WIDGET_ANDROID) + return true; +#else +#if defined(MOZ_LOGGING) + if (MOZ_LOG_TEST(GetGFX2DLog(), PRLogLevelForLevel(aLevel))) { + return true; + } else +#endif + if ((LoggingPrefs::sGfxLogLevel >= LOG_DEBUG_PRLOG) || + (aLevel < LOG_DEBUG)) { + return true; + } +#endif + } + return false; + } + + // Only for really critical errors. + static void CrashAction(LogReason aReason) {} + + static void OutputMessage(const std::string &aString, + int aLevel, + bool aNoNewline) { + // This behavior (the higher the preference, the more we log) + // is consistent with what prlog does in general. Note that if prlog + // is in the build, but disabled, we will printf if the preferences + // requires us to log something (see sGfxLogLevel for the special + // treatment of LOG_DEBUG and LOG_DEBUG_PRLOG) + // + // If making any logic changes to this method, you should probably + // make the corresponding change in the ShouldOutputMessage method + // above. + if (LoggingPrefs::sGfxLogLevel >= aLevel) { +#if defined(MOZ_WIDGET_ANDROID) + printf_stderr("%s%s", aString.c_str(), aNoNewline ? "" : "\n"); +#else +#if defined(MOZ_LOGGING) + if (MOZ_LOG_TEST(GetGFX2DLog(), PRLogLevelForLevel(aLevel))) { + PR_LogPrint("%s%s", aString.c_str(), aNoNewline ? "" : "\n"); + } else +#endif + if ((LoggingPrefs::sGfxLogLevel >= LOG_DEBUG_PRLOG) || + (aLevel < LOG_DEBUG)) { + printf("%s%s", aString.c_str(), aNoNewline ? "" : "\n"); + } +#endif + } + } +}; + +struct CriticalLogger { + static void OutputMessage(const std::string &aString, int aLevel, bool aNoNewline); + static void CrashAction(LogReason aReason); +}; + +// The int is the index of the Log call; if the number of logs exceeds some preset +// capacity we may not get all of them, so the indices help figure out which +// ones we did save. The double is expected to be the "TimeDuration", +// time in seconds since the process creation. +typedef mozilla::Tuple<int32_t,std::string,double> LoggingRecordEntry; + +// Implement this interface and init the Factory with an instance to +// forward critical logs. +typedef std::vector<LoggingRecordEntry> LoggingRecord; +class LogForwarder { +public: + virtual ~LogForwarder() {} + virtual void Log(const std::string &aString) = 0; + virtual void CrashAction(LogReason aReason) = 0; + virtual bool UpdateStringsVector(const std::string& aString) = 0; + + // Provide a copy of the logs to the caller. + virtual LoggingRecord LoggingRecordCopy() = 0; +}; + +class NoLog +{ +public: + NoLog() {} + ~NoLog() {} + + // No-op + MOZ_IMPLICIT NoLog(const NoLog&) {} + + template<typename T> + NoLog &operator <<(const T &aLogText) { return *this; } +}; + +enum class LogOptions : int { + NoNewline = 0x01, + AutoPrefix = 0x02, + AssertOnCall = 0x04, + CrashAction = 0x08, +}; + +template<typename T> +struct Hexa { + explicit Hexa(T aVal) : mVal(aVal) {} + T mVal; +}; +template<typename T> +Hexa<T> hexa(T val) { return Hexa<T>(val); } + +template<int L, typename Logger = BasicLogger> +class Log +{ +public: + // The default is to have the prefix, have the new line, and for critical + // logs assert on each call. + static int DefaultOptions(bool aWithAssert = true) { + return (int(LogOptions::AutoPrefix) | + (aWithAssert ? int(LogOptions::AssertOnCall) : 0)); + } + + // Note that we're calling BasicLogger::ShouldOutputMessage, rather than + // Logger::ShouldOutputMessage. Since we currently don't have a different + // version of that method for different loggers, this is OK. Once we do, + // change BasicLogger::ShouldOutputMessage to Logger::ShouldOutputMessage. + explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL), + LogReason aReason = LogReason::MustBeMoreThanThis) + : mOptions(0) + , mLogIt(false) + { + Init(aOptions, BasicLogger::ShouldOutputMessage(L), aReason); + } + + ~Log() { + Flush(); + } + + void Flush() { + if (MOZ_LIKELY(!LogIt())) return; + + std::string str = mMessage.str(); + if (!str.empty()) { + WriteLog(str); + } + mMessage.str(""); + } + + Log &operator <<(char aChar) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aChar; + } + return *this; + } + Log &operator <<(const std::string &aLogText) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLogText; + } + return *this; + } + Log &operator <<(const char aStr[]) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << static_cast<const char*>(aStr); + } + return *this; + } + Log &operator <<(bool aBool) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << (aBool ? "true" : "false"); + } + return *this; + } + Log &operator <<(int aInt) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aInt; + } + return *this; + } + Log &operator <<(unsigned int aInt) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aInt; + } + return *this; + } + Log &operator <<(long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log &operator <<(unsigned long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log &operator <<(long long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log &operator <<(unsigned long long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log &operator <<(Float aFloat) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aFloat; + } + return *this; + } + Log &operator <<(double aDouble) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aDouble; + } + return *this; + } + template <typename T, typename Sub, typename Coord> + Log &operator <<(const BasePoint<T, Sub, Coord>& aPoint) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Point" << aPoint; + } + return *this; + } + template <typename T, typename Sub> + Log &operator <<(const BaseSize<T, Sub>& aSize) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Size(" << aSize.width << "," << aSize.height << ")"; + } + return *this; + } + template <typename T, typename Sub, typename Point, typename SizeT, typename Margin> + Log &operator <<(const BaseRect<T, Sub, Point, SizeT, Margin>& aRect) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Rect" << aRect; + } + return *this; + } + Log &operator<<(const Matrix& aMatrix) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Matrix(" << aMatrix._11 << " " << aMatrix._12 << " ; " << aMatrix._21 << " " << aMatrix._22 << " ; " << aMatrix._31 << " " << aMatrix._32 << ")"; + } + return *this; + } + template<typename T> + Log &operator<<(Hexa<T> aHex) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << std::showbase << std::hex + << aHex.mVal + << std::noshowbase << std::dec; + } + return *this; + } + + Log& operator<<(SurfaceFormat aFormat) { + if (MOZ_UNLIKELY(LogIt())) { + switch(aFormat) { + case SurfaceFormat::B8G8R8A8: + mMessage << "SurfaceFormat::B8G8R8A8"; + break; + case SurfaceFormat::B8G8R8X8: + mMessage << "SurfaceFormat::B8G8R8X8"; + break; + case SurfaceFormat::R8G8B8A8: + mMessage << "SurfaceFormat::R8G8B8A8"; + break; + case SurfaceFormat::R8G8B8X8: + mMessage << "SurfaceFormat::R8G8B8X8"; + break; + case SurfaceFormat::R5G6B5_UINT16: + mMessage << "SurfaceFormat::R5G6B5_UINT16"; + break; + case SurfaceFormat::A8: + mMessage << "SurfaceFormat::A8"; + break; + case SurfaceFormat::YUV: + mMessage << "SurfaceFormat::YUV"; + break; + case SurfaceFormat::UNKNOWN: + mMessage << "SurfaceFormat::UNKNOWN"; + break; + default: + mMessage << "Invalid SurfaceFormat (" << (int)aFormat << ")"; + break; + } + } + return *this; + } + + Log& operator<<(SurfaceType aType) { + if (MOZ_UNLIKELY(LogIt())) { + switch(aType) { + case SurfaceType::DATA: + mMessage << "SurfaceType::DATA"; + break; + case SurfaceType::D2D1_BITMAP: + mMessage << "SurfaceType::D2D1_BITMAP"; + break; + case SurfaceType::D2D1_DRAWTARGET: + mMessage << "SurfaceType::D2D1_DRAWTARGET"; + break; + case SurfaceType::CAIRO: + mMessage << "SurfaceType::CAIRO"; + break; + case SurfaceType::CAIRO_IMAGE: + mMessage << "SurfaceType::CAIRO_IMAGE"; + break; + case SurfaceType::COREGRAPHICS_IMAGE: + mMessage << "SurfaceType::COREGRAPHICS_IMAGE"; + break; + case SurfaceType::COREGRAPHICS_CGCONTEXT: + mMessage << "SurfaceType::COREGRAPHICS_CGCONTEXT"; + break; + case SurfaceType::SKIA: + mMessage << "SurfaceType::SKIA"; + break; + case SurfaceType::DUAL_DT: + mMessage << "SurfaceType::DUAL_DT"; + break; + case SurfaceType::D2D1_1_IMAGE: + mMessage << "SurfaceType::D2D1_1_IMAGE"; + break; + case SurfaceType::RECORDING: + mMessage << "SurfaceType::RECORDING"; + break; + case SurfaceType::TILED: + mMessage << "SurfaceType::TILED"; + break; + default: + mMessage << "Invalid SurfaceType (" << (int)aType << ")"; + break; + } + } + return *this; + } + + inline bool LogIt() const { return mLogIt; } + inline bool NoNewline() const { return mOptions & int(LogOptions::NoNewline); } + inline bool AutoPrefix() const { return mOptions & int(LogOptions::AutoPrefix); } + inline bool ValidReason() const { return (int)mReason > (int)LogReason::MustBeMoreThanThis && (int)mReason < (int)LogReason::MustBeLessThanThis; } + + // We do not want this version to do any work, and stringstream can't be + // copied anyway. It does come in handy for the "Once" macro defined below. + MOZ_IMPLICIT Log(const Log& log) { Init(log.mOptions, false, log.mReason); } + +private: + // Initialization common to two constructors + void Init(int aOptions, bool aLogIt, LogReason aReason) { + mOptions = aOptions; + mReason = aReason; + mLogIt = aLogIt; + if (mLogIt) { + if (AutoPrefix()) { + if (mOptions & int(LogOptions::AssertOnCall)) { + mMessage << "[GFX" << L; + } else { + mMessage << "[GFX" << L << "-"; + } + } + if ((mOptions & int(LogOptions::CrashAction)) && ValidReason()) { + mMessage << " " << (int)mReason; + } + if (AutoPrefix()) { + mMessage << "]: "; + } + } + } + + void WriteLog(const std::string &aString) { + if (MOZ_UNLIKELY(LogIt())) { + Logger::OutputMessage(aString, L, NoNewline()); + // Assert if required. We don't have a three parameter MOZ_ASSERT + // so use the underlying functions instead (see bug 1281702): +#ifdef DEBUG + if (mOptions & int(LogOptions::AssertOnCall)) { + MOZ_ReportAssertionFailure(aString.c_str(), __FILE__, __LINE__); + MOZ_CRASH("GFX: An assert from the graphics logger"); + } +#endif + if ((mOptions & int(LogOptions::CrashAction)) && ValidReason()) { + Logger::CrashAction(mReason); + } + } + } + + std::stringstream mMessage; + int mOptions; + LogReason mReason; + bool mLogIt; +}; + +typedef Log<LOG_DEBUG> DebugLog; +typedef Log<LOG_WARNING> WarningLog; +typedef Log<LOG_CRITICAL, CriticalLogger> CriticalLog; + +// Macro to glue names to get us less chance of name clashing. +#if defined GFX_LOGGING_GLUE1 || defined GFX_LOGGING_GLUE +#error "Clash of the macro GFX_LOGGING_GLUE1 or GFX_LOGGING_GLUE" +#endif +#define GFX_LOGGING_GLUE1(x, y) x##y +#define GFX_LOGGING_GLUE(x, y) GFX_LOGGING_GLUE1(x, y) + +// This log goes into crash reports, use with care. +#define gfxCriticalError mozilla::gfx::CriticalLog +#define gfxCriticalErrorOnce static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxCriticalError + +// This is a shortcut for errors we want logged in crash reports/about support +// but we do not want asserting. These are available in all builds, so it is +// not worth trying to do magic to avoid matching the syntax of gfxCriticalError. +// So, this one is used as +// gfxCriticalNote << "Something to report and not assert"; +// while the critical error is +// gfxCriticalError() << "Something to report and assert"; +#define gfxCriticalNote gfxCriticalError(gfxCriticalError::DefaultOptions(false)) +#define gfxCriticalNoteOnce static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxCriticalNote + +// The "once" versions will only trigger the first time through. You can do this: +// gfxCriticalErrorOnce() << "This message only shows up once; +// instead of the usual: +// static bool firstTime = true; +// if (firstTime) { +// firstTime = false; +// gfxCriticalError() << "This message only shows up once; +// } +#if defined(DEBUG) +#define gfxDebug mozilla::gfx::DebugLog +#define gfxDebugOnce static gfxDebug GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxDebug +#else +#define gfxDebug if (1) ; else mozilla::gfx::NoLog +#define gfxDebugOnce if (1) ; else mozilla::gfx::NoLog +#endif + +// Have gfxWarning available (behind a runtime preference) +#define gfxWarning mozilla::gfx::WarningLog +#define gfxWarningOnce static gfxWarning GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxWarning + +// In the debug build, this is equivalent to the default gfxCriticalError. +// In the non-debug build, on nightly and dev edition, it will MOZ_CRASH. +// On beta and release versions, it will telemetry count, but proceed. +// +// You should create a (new) enum in the LogReason and use it for the reason +// parameter to ensure uniqueness. +#define gfxDevCrash(reason) gfxCriticalError(int(gfx::LogOptions::AutoPrefix) | int(gfx::LogOptions::AssertOnCall) | int(gfx::LogOptions::CrashAction), (reason)) + +// See nsDebug.h and the NS_WARN_IF macro + +#ifdef __cplusplus + // For now, have MOZ2D_ERROR_IF available in debug and non-debug builds +inline bool MOZ2D_error_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) +{ + if (MOZ_UNLIKELY(aCondition)) { + gfxCriticalError() << aExpr << " at " << aFile << ":" << aLine; + } + return aCondition; +} +#define MOZ2D_ERROR_IF(condition) \ + MOZ2D_error_if_impl(condition, #condition, __FILE__, __LINE__) + +#ifdef DEBUG +inline bool MOZ2D_warn_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) +{ + if (MOZ_UNLIKELY(aCondition)) { + gfxWarning() << aExpr << " at " << aFile << ":" << aLine; + } + return aCondition; +} +#define MOZ2D_WARN_IF(condition) \ + MOZ2D_warn_if_impl(condition, #condition, __FILE__, __LINE__) +#else +#define MOZ2D_WARN_IF(condition) (bool)(condition) +#endif +#endif + +const int INDENT_PER_LEVEL = 2; + +class TreeLog +{ +public: + explicit TreeLog(const std::string& aPrefix = "") + : mLog(int(LogOptions::NoNewline)), + mPrefix(aPrefix), + mDepth(0), + mStartOfLine(true), + mConditionedOnPref(false), + mPrefFunction(nullptr) {} + + template <typename T> + TreeLog& operator<<(const T& aObject) { + if (mConditionedOnPref && !mPrefFunction()) { + return *this; + } + if (mStartOfLine) { + mLog << '[' << mPrefix << "] " << std::string(mDepth * INDENT_PER_LEVEL, ' '); + mStartOfLine = false; + } + mLog << aObject; + if (EndsInNewline(aObject)) { + // Don't indent right here as the user may change the indent + // between now and the first output to the next line. + mLog.Flush(); + mStartOfLine = true; + } + return *this; + } + + void IncreaseIndent() { ++mDepth; } + void DecreaseIndent() { + MOZ_ASSERT(mDepth > 0); + --mDepth; + } + + void ConditionOnPrefFunction(bool(*aPrefFunction)()) { + mConditionedOnPref = true; + mPrefFunction = aPrefFunction; + } +private: + Log<LOG_DEBUG> mLog; + std::string mPrefix; + uint32_t mDepth; + bool mStartOfLine; + bool mConditionedOnPref; + bool (*mPrefFunction)(); + + template <typename T> + static bool EndsInNewline(const T& aObject) { + return false; + } + + static bool EndsInNewline(const std::string& aString) { + return !aString.empty() && aString[aString.length() - 1] == '\n'; + } + + static bool EndsInNewline(char aChar) { + return aChar == '\n'; + } + + static bool EndsInNewline(const char* aString) { + return EndsInNewline(std::string(aString)); + } +}; + +class TreeAutoIndent +{ +public: + explicit TreeAutoIndent(TreeLog& aTreeLog) : mTreeLog(aTreeLog) { + mTreeLog.IncreaseIndent(); + } + + TreeAutoIndent(const TreeAutoIndent& aTreeAutoIndent) : + TreeAutoIndent(aTreeAutoIndent.mTreeLog) { + mTreeLog.IncreaseIndent(); + } + + TreeAutoIndent& operator=(const TreeAutoIndent& aTreeAutoIndent) = delete; + + ~TreeAutoIndent() { + mTreeLog.DecreaseIndent(); + } +private: + TreeLog& mTreeLog; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_LOGGING_H_ */ diff --git a/gfx/2d/LoggingConstants.h b/gfx/2d/LoggingConstants.h new file mode 100644 index 000000000..08eb2014d --- /dev/null +++ b/gfx/2d/LoggingConstants.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LOGGING_CONSTANTS_H_ +#define MOZILLA_GFX_LOGGING_CONSTANTS_H_ + +namespace mozilla { +namespace gfx { + +// Attempting to be consistent with prlog values, but that isn't critical +// (and note that 5 has a special meaning - see the description +// with LoggingPrefs::sGfxLogLevel) +const int LOG_CRITICAL = 1; +const int LOG_WARNING = 2; +const int LOG_DEBUG = 3; +const int LOG_DEBUG_PRLOG = 4; +const int LOG_EVERYTHING = 5; // This needs to be the highest value + +#if defined(DEBUG) +const int LOG_DEFAULT = LOG_EVERYTHING; +#else +const int LOG_DEFAULT = LOG_CRITICAL; +#endif + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_LOGGING_CONSTANTS_H_ */ diff --git a/gfx/2d/MMIHelpers.h b/gfx/2d/MMIHelpers.h new file mode 100644 index 000000000..2a9b16a9d --- /dev/null +++ b/gfx/2d/MMIHelpers.h @@ -0,0 +1,196 @@ +/* + ============================================================================ + Name : MMIHelpers.h + Author : Heiher <r@hev.cc> + Version : 0.0.1 + Copyright : Copyright (c) 2015 everyone. + Description : The helpers for x86 SSE to Loongson MMI. + ============================================================================ + */ + +#ifndef __MMI_HELPERS_H__ +#define __MMI_HELPERS_H__ + +#define __mm_packxxxx(_f, _D, _d, _s, _t) \ + #_f" %["#_t"], %["#_d"h], %["#_s"h] \n\t" \ + #_f" %["#_D"l], %["#_d"l], %["#_s"l] \n\t" \ + "punpckhwd %["#_D"h], %["#_D"l], %["#_t"] \n\t" \ + "punpcklwd %["#_D"l], %["#_D"l], %["#_t"] \n\t" + +#define _mm_or(_D, _d, _s) \ + "or %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "or %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +#define _mm_xor(_D, _d, _s) \ + "xor %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "xor %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +#define _mm_and(_D, _d, _s) \ + "and %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "and %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pandn */ +#define _mm_pandn(_D, _d, _s) \ + "pandn %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pandn %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pshuflw */ +#define _mm_pshuflh(_D, _d, _s) \ + "mov.d %["#_D"h], %["#_d"h] \n\t" \ + "pshufh %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psllw (bits) */ +#define _mm_psllh(_D, _d, _s) \ + "psllh %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psllh %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: pslld (bits) */ +#define _mm_psllw(_D, _d, _s) \ + "psllw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psllw %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psllq (bits) */ +#define _mm_pslld(_D, _d, _s) \ + "dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: pslldq (bytes) */ +#define _mm_psllq(_D, _d, _s, _s64, _tf) \ + "subu %["#_tf"], %["#_s64"], %["#_s"] \n\t" \ + "dsrl %["#_tf"], %["#_d"l], %["#_tf"] \n\t" \ + "dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t" \ + "or %["#_D"h], %["#_D"h], %["#_tf"] \n\t" + +/* SSE: psrlw (bits) */ +#define _mm_psrlh(_D, _d, _s) \ + "psrlh %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psrlh %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psrld (bits) */ +#define _mm_psrlw(_D, _d, _s) \ + "psrlw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psrlw %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psrlq (bits) */ +#define _mm_psrld(_D, _d, _s) \ + "dsrl %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsrl %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psrldq (bytes) */ +#define _mm_psrlq(_D, _d, _s, _s64, _tf) \ + "subu %["#_tf"], %["#_s64"], %["#_s"] \n\t" \ + "dsll %["#_tf"], %["#_d"h], %["#_tf"] \n\t" \ + "dsrl %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsrl %["#_D"l], %["#_d"l], %["#_s"] \n\t" \ + "or %["#_D"l], %["#_D"l], %["#_tf"] \n\t" + +/* SSE: psrad */ +#define _mm_psraw(_D, _d, _s) \ + "psraw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psraw %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: paddb */ +#define _mm_paddb(_D, _d, _s) \ + "paddb %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "paddb %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: paddw */ +#define _mm_paddh(_D, _d, _s) \ + "paddh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "paddh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: paddd */ +#define _mm_paddw(_D, _d, _s) \ + "paddw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "paddw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: paddq */ +#define _mm_paddd(_D, _d, _s) \ + "dadd %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "dadd %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: psubw */ +#define _mm_psubh(_D, _d, _s) \ + "psubh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "psubh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: psubd */ +#define _mm_psubw(_D, _d, _s) \ + "psubw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "psubw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmaxub */ +#define _mm_pmaxub(_D, _d, _s) \ + "pmaxub %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmaxub %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmullw */ +#define _mm_pmullh(_D, _d, _s) \ + "pmullh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmullh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmulhw */ +#define _mm_pmulhh(_D, _d, _s) \ + "pmulhh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmulhh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmuludq */ +#define _mm_pmuluw(_D, _d, _s) \ + "pmuluw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmuluw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: packsswb */ +#define _mm_packsshb(_D, _d, _s, _t) \ + __mm_packxxxx(packsshb, _D, _d, _s, _t) + +/* SSE: packssdw */ +#define _mm_packsswh(_D, _d, _s, _t) \ + __mm_packxxxx(packsswh, _D, _d, _s, _t) + +/* SSE: packuswb */ +#define _mm_packushb(_D, _d, _s, _t) \ + __mm_packxxxx(packushb, _D, _d, _s, _t) + +/* SSE: punpcklbw */ +#define _mm_punpcklbh(_D, _d, _s) \ + "punpckhbh %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \ + "punpcklbh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: punpcklwd */ +#define _mm_punpcklhw(_D, _d, _s) \ + "punpckhhw %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \ + "punpcklhw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: punpckldq */ +#define _mm_punpcklwd(_D, _d, _s) \ + "punpckhwd %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \ + "punpcklwd %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: punpcklqdq */ +#define _mm_punpckldq(_D, _d, _s) \ + "mov.d %["#_D"h], %["#_s"l] \n\t" \ + "mov.d %["#_D"l], %["#_d"l] \n\t" + +/* SSE: punpckhbw */ +#define _mm_punpckhbh(_D, _d, _s) \ + "punpcklbh %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \ + "punpckhbh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" + +/* SSE: punpckhwd */ +#define _mm_punpckhhw(_D, _d, _s) \ + "punpcklhw %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \ + "punpckhhw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" + +/* SSE: punpckhdq */ +#define _mm_punpckhwd(_D, _d, _s) \ + "punpcklwd %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \ + "punpckhwd %["#_D"h], %["#_d"h], %["#_s"h] \n\t" + +/* SSE: punpckhqdq */ +#define _mm_punpckhdq(_D, _d, _s) \ + "mov.d %["#_D"l], %["#_d"h] \n\t" \ + "mov.d %["#_D"h], %["#_s"h] \n\t" + +#endif /* __MMI_HELPERS_H__ */ + diff --git a/gfx/2d/MacIOSurface.cpp b/gfx/2d/MacIOSurface.cpp new file mode 100644 index 000000000..759de8575 --- /dev/null +++ b/gfx/2d/MacIOSurface.cpp @@ -0,0 +1,615 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacIOSurface.h" +#include <OpenGL/gl.h> +#include <QuartzCore/QuartzCore.h> +#include <dlfcn.h> +#include "mozilla/RefPtr.h" +#include "mozilla/Assertions.h" +#include "GLConsts.h" + +using namespace mozilla; +// IOSurface signatures +#define IOSURFACE_FRAMEWORK_PATH \ + "/System/Library/Frameworks/IOSurface.framework/IOSurface" +#define OPENGL_FRAMEWORK_PATH \ + "/System/Library/Frameworks/OpenGL.framework/OpenGL" +#define COREGRAPHICS_FRAMEWORK_PATH \ + "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" \ + "CoreGraphics.framework/CoreGraphics" +#define COREVIDEO_FRAMEWORK_PATH \ + "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" \ + "CoreVideo.framework/CoreVideo" + +#define GET_CONST(const_name) \ + ((CFStringRef*) dlsym(sIOSurfaceFramework, const_name)) +#define GET_IOSYM(dest,sym_name) \ + (typeof(dest)) dlsym(sIOSurfaceFramework, sym_name) +#define GET_CGLSYM(dest,sym_name) \ + (typeof(dest)) dlsym(sOpenGLFramework, sym_name) +#define GET_CGSYM(dest,sym_name) \ + (typeof(dest)) dlsym(sCoreGraphicsFramework, sym_name) +#define GET_CVSYM(dest, sym_name) \ + (typeof(dest)) dlsym(sCoreVideoFramework, sym_name) + +MacIOSurfaceLib::LibraryUnloader MacIOSurfaceLib::sLibraryUnloader; +bool MacIOSurfaceLib::isLoaded = false; +void* MacIOSurfaceLib::sIOSurfaceFramework; +void* MacIOSurfaceLib::sOpenGLFramework; +void* MacIOSurfaceLib::sCoreGraphicsFramework; +void* MacIOSurfaceLib::sCoreVideoFramework; +IOSurfaceCreateFunc MacIOSurfaceLib::sCreate; +IOSurfaceGetIDFunc MacIOSurfaceLib::sGetID; +IOSurfaceLookupFunc MacIOSurfaceLib::sLookup; +IOSurfaceGetBaseAddressFunc MacIOSurfaceLib::sGetBaseAddress; +IOSurfaceGetBaseAddressOfPlaneFunc MacIOSurfaceLib::sGetBaseAddressOfPlane; +IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sWidth; +IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sHeight; +IOSurfaceSizeTFunc MacIOSurfaceLib::sPlaneCount; +IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sBytesPerRow; +IOSurfaceGetPropertyMaximumFunc MacIOSurfaceLib::sGetPropertyMaximum; +IOSurfaceVoidFunc MacIOSurfaceLib::sIncrementUseCount; +IOSurfaceVoidFunc MacIOSurfaceLib::sDecrementUseCount; +IOSurfaceLockFunc MacIOSurfaceLib::sLock; +IOSurfaceUnlockFunc MacIOSurfaceLib::sUnlock; +CGLTexImageIOSurface2DFunc MacIOSurfaceLib::sTexImage; +IOSurfaceContextCreateFunc MacIOSurfaceLib::sIOSurfaceContextCreate; +IOSurfaceContextCreateImageFunc MacIOSurfaceLib::sIOSurfaceContextCreateImage; +IOSurfaceContextGetSurfaceFunc MacIOSurfaceLib::sIOSurfaceContextGetSurface; +CVPixelBufferGetIOSurfaceFunc MacIOSurfaceLib::sCVPixelBufferGetIOSurface; +unsigned int (*MacIOSurfaceLib::sCGContextGetTypePtr) (CGContextRef) = nullptr; +IOSurfacePixelFormatFunc MacIOSurfaceLib::sPixelFormat; + +CFStringRef MacIOSurfaceLib::kPropWidth; +CFStringRef MacIOSurfaceLib::kPropHeight; +CFStringRef MacIOSurfaceLib::kPropBytesPerElem; +CFStringRef MacIOSurfaceLib::kPropBytesPerRow; +CFStringRef MacIOSurfaceLib::kPropIsGlobal; + +bool MacIOSurfaceLib::isInit() { + // Guard against trying to reload the library + // if it is not available. + if (!isLoaded) + LoadLibrary(); + MOZ_ASSERT(sIOSurfaceFramework); + return sIOSurfaceFramework; +} + +IOSurfacePtr MacIOSurfaceLib::IOSurfaceCreate(CFDictionaryRef properties) { + return sCreate(properties); +} + +IOSurfacePtr MacIOSurfaceLib::IOSurfaceLookup(IOSurfaceID aIOSurfaceID) { + return sLookup(aIOSurfaceID); +} + +IOSurfaceID MacIOSurfaceLib::IOSurfaceGetID(IOSurfacePtr aIOSurfacePtr) { + return sGetID(aIOSurfacePtr); +} + +void* MacIOSurfaceLib::IOSurfaceGetBaseAddress(IOSurfacePtr aIOSurfacePtr) { + return sGetBaseAddress(aIOSurfacePtr); +} + +void* MacIOSurfaceLib::IOSurfaceGetBaseAddressOfPlane(IOSurfacePtr aIOSurfacePtr, + size_t planeIndex) { + return sGetBaseAddressOfPlane(aIOSurfacePtr, planeIndex); +} + +size_t MacIOSurfaceLib::IOSurfaceGetPlaneCount(IOSurfacePtr aIOSurfacePtr) { + return sPlaneCount(aIOSurfacePtr); +} + +size_t MacIOSurfaceLib::IOSurfaceGetWidth(IOSurfacePtr aIOSurfacePtr, size_t plane) { + return sWidth(aIOSurfacePtr, plane); +} + +size_t MacIOSurfaceLib::IOSurfaceGetHeight(IOSurfacePtr aIOSurfacePtr, size_t plane) { + return sHeight(aIOSurfacePtr, plane); +} + +size_t MacIOSurfaceLib::IOSurfaceGetBytesPerRow(IOSurfacePtr aIOSurfacePtr, size_t plane) { + return sBytesPerRow(aIOSurfacePtr, plane); +} + +size_t MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(CFStringRef property) { + return sGetPropertyMaximum(property); +} + +OSType MacIOSurfaceLib::IOSurfaceGetPixelFormat(IOSurfacePtr aIOSurfacePtr) { + return sPixelFormat(aIOSurfacePtr); +} + +IOReturn MacIOSurfaceLib::IOSurfaceLock(IOSurfacePtr aIOSurfacePtr, + uint32_t options, uint32_t* seed) { + return sLock(aIOSurfacePtr, options, seed); +} + +IOReturn MacIOSurfaceLib::IOSurfaceUnlock(IOSurfacePtr aIOSurfacePtr, + uint32_t options, uint32_t *seed) { + return sUnlock(aIOSurfacePtr, options, seed); +} + +void MacIOSurfaceLib::IOSurfaceIncrementUseCount(IOSurfacePtr aIOSurfacePtr) { + sIncrementUseCount(aIOSurfacePtr); +} + +void MacIOSurfaceLib::IOSurfaceDecrementUseCount(IOSurfacePtr aIOSurfacePtr) { + sDecrementUseCount(aIOSurfacePtr); +} + +CGLError MacIOSurfaceLib::CGLTexImageIOSurface2D(CGLContextObj ctxt, + GLenum target, GLenum internalFormat, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + IOSurfacePtr ioSurface, GLuint plane) { + return sTexImage(ctxt, target, internalFormat, width, height, + format, type, ioSurface, plane); +} + +IOSurfacePtr MacIOSurfaceLib::CVPixelBufferGetIOSurface(CVPixelBufferRef aPixelBuffer) { + return sCVPixelBufferGetIOSurface(aPixelBuffer); +} + +CGContextRef MacIOSurfaceLib::IOSurfaceContextCreate(IOSurfacePtr aIOSurfacePtr, + unsigned aWidth, unsigned aHeight, + unsigned aBitsPerComponent, unsigned aBytes, + CGColorSpaceRef aColorSpace, CGBitmapInfo bitmapInfo) { + if (!sIOSurfaceContextCreate) + return nullptr; + return sIOSurfaceContextCreate(aIOSurfacePtr, aWidth, aHeight, aBitsPerComponent, aBytes, aColorSpace, bitmapInfo); +} + +CGImageRef MacIOSurfaceLib::IOSurfaceContextCreateImage(CGContextRef aContext) { + if (!sIOSurfaceContextCreateImage) + return nullptr; + return sIOSurfaceContextCreateImage(aContext); +} + +IOSurfacePtr MacIOSurfaceLib::IOSurfaceContextGetSurface(CGContextRef aContext) { + if (!sIOSurfaceContextGetSurface) + return nullptr; + return sIOSurfaceContextGetSurface(aContext); +} + +CFStringRef MacIOSurfaceLib::GetIOConst(const char* symbole) { + CFStringRef *address = (CFStringRef*)dlsym(sIOSurfaceFramework, symbole); + if (!address) + return nullptr; + + return *address; +} + +void MacIOSurfaceLib::LoadLibrary() { + if (isLoaded) { + return; + } + isLoaded = true; + sIOSurfaceFramework = dlopen(IOSURFACE_FRAMEWORK_PATH, + RTLD_LAZY | RTLD_LOCAL); + sOpenGLFramework = dlopen(OPENGL_FRAMEWORK_PATH, + RTLD_LAZY | RTLD_LOCAL); + + sCoreGraphicsFramework = dlopen(COREGRAPHICS_FRAMEWORK_PATH, + RTLD_LAZY | RTLD_LOCAL); + + sCoreVideoFramework = dlopen(COREVIDEO_FRAMEWORK_PATH, + RTLD_LAZY | RTLD_LOCAL); + + if (!sIOSurfaceFramework || !sOpenGLFramework || !sCoreGraphicsFramework || + !sCoreVideoFramework) { + if (sIOSurfaceFramework) + dlclose(sIOSurfaceFramework); + if (sOpenGLFramework) + dlclose(sOpenGLFramework); + if (sCoreGraphicsFramework) + dlclose(sCoreGraphicsFramework); + if (sCoreVideoFramework) + dlclose(sCoreVideoFramework); + sIOSurfaceFramework = nullptr; + sOpenGLFramework = nullptr; + sCoreGraphicsFramework = nullptr; + sCoreVideoFramework = nullptr; + return; + } + + kPropWidth = GetIOConst("kIOSurfaceWidth"); + kPropHeight = GetIOConst("kIOSurfaceHeight"); + kPropBytesPerElem = GetIOConst("kIOSurfaceBytesPerElement"); + kPropBytesPerRow = GetIOConst("kIOSurfaceBytesPerRow"); + kPropIsGlobal = GetIOConst("kIOSurfaceIsGlobal"); + sCreate = GET_IOSYM(sCreate, "IOSurfaceCreate"); + sGetID = GET_IOSYM(sGetID, "IOSurfaceGetID"); + sWidth = GET_IOSYM(sWidth, "IOSurfaceGetWidthOfPlane"); + sHeight = GET_IOSYM(sHeight, "IOSurfaceGetHeightOfPlane"); + sBytesPerRow = GET_IOSYM(sBytesPerRow, "IOSurfaceGetBytesPerRowOfPlane"); + sGetPropertyMaximum = GET_IOSYM(sGetPropertyMaximum, "IOSurfaceGetPropertyMaximum"); + sLookup = GET_IOSYM(sLookup, "IOSurfaceLookup"); + sLock = GET_IOSYM(sLock, "IOSurfaceLock"); + sUnlock = GET_IOSYM(sUnlock, "IOSurfaceUnlock"); + sIncrementUseCount = + GET_IOSYM(sIncrementUseCount, "IOSurfaceIncrementUseCount"); + sDecrementUseCount = + GET_IOSYM(sDecrementUseCount, "IOSurfaceDecrementUseCount"); + sGetBaseAddress = GET_IOSYM(sGetBaseAddress, "IOSurfaceGetBaseAddress"); + sGetBaseAddressOfPlane = + GET_IOSYM(sGetBaseAddressOfPlane, "IOSurfaceGetBaseAddressOfPlane"); + sPlaneCount = GET_IOSYM(sPlaneCount, "IOSurfaceGetPlaneCount"); + sPixelFormat = GET_IOSYM(sPixelFormat, "IOSurfaceGetPixelFormat"); + + sTexImage = GET_CGLSYM(sTexImage, "CGLTexImageIOSurface2D"); + sCGContextGetTypePtr = (unsigned int (*)(CGContext*))dlsym(RTLD_DEFAULT, "CGContextGetType"); + + sCVPixelBufferGetIOSurface = + GET_CVSYM(sCVPixelBufferGetIOSurface, "CVPixelBufferGetIOSurface"); + + // Optional symbols + sIOSurfaceContextCreate = GET_CGSYM(sIOSurfaceContextCreate, "CGIOSurfaceContextCreate"); + sIOSurfaceContextCreateImage = GET_CGSYM(sIOSurfaceContextCreateImage, "CGIOSurfaceContextCreateImage"); + sIOSurfaceContextGetSurface = GET_CGSYM(sIOSurfaceContextGetSurface, "CGIOSurfaceContextGetSurface"); + + if (!sCreate || !sGetID || !sLookup || !sTexImage || !sGetBaseAddress || + !sGetBaseAddressOfPlane || !sPlaneCount || + !kPropWidth || !kPropHeight || !kPropBytesPerElem || !kPropIsGlobal || + !sLock || !sUnlock || !sIncrementUseCount || !sDecrementUseCount || + !sWidth || !sHeight || !kPropBytesPerRow || + !sBytesPerRow || !sGetPropertyMaximum || !sCVPixelBufferGetIOSurface) { + CloseLibrary(); + } +} + +void MacIOSurfaceLib::CloseLibrary() { + if (sIOSurfaceFramework) { + dlclose(sIOSurfaceFramework); + } + if (sOpenGLFramework) { + dlclose(sOpenGLFramework); + } + if (sCoreVideoFramework) { + dlclose(sCoreVideoFramework); + } + sIOSurfaceFramework = nullptr; + sOpenGLFramework = nullptr; + sCoreVideoFramework = nullptr; +} + +MacIOSurface::MacIOSurface(const void* aIOSurfacePtr, + double aContentsScaleFactor, bool aHasAlpha) + : mIOSurfacePtr(aIOSurfacePtr) + , mContentsScaleFactor(aContentsScaleFactor) + , mHasAlpha(aHasAlpha) +{ + CFRetain(mIOSurfacePtr); + IncrementUseCount(); +} + +MacIOSurface::~MacIOSurface() { + DecrementUseCount(); + CFRelease(mIOSurfacePtr); +} + +already_AddRefed<MacIOSurface> MacIOSurface::CreateIOSurface(int aWidth, int aHeight, + double aContentsScaleFactor, + bool aHasAlpha) { + if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0) + return nullptr; + + CFMutableDictionaryRef props = ::CFDictionaryCreateMutable( + kCFAllocatorDefault, 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (!props) + return nullptr; + + MOZ_ASSERT((size_t)aWidth <= GetMaxWidth()); + MOZ_ASSERT((size_t)aHeight <= GetMaxHeight()); + + int32_t bytesPerElem = 4; + size_t intScaleFactor = ceil(aContentsScaleFactor); + aWidth *= intScaleFactor; + aHeight *= intScaleFactor; + CFNumberRef cfWidth = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aWidth); + CFNumberRef cfHeight = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aHeight); + CFNumberRef cfBytesPerElem = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &bytesPerElem); + ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropWidth, + cfWidth); + ::CFRelease(cfWidth); + ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropHeight, + cfHeight); + ::CFRelease(cfHeight); + ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropBytesPerElem, + cfBytesPerElem); + ::CFRelease(cfBytesPerElem); + ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropIsGlobal, + kCFBooleanTrue); + + IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceCreate(props); + ::CFRelease(props); + + if (!surfaceRef) + return nullptr; + + RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha); + if (!ioSurface) { + ::CFRelease(surfaceRef); + return nullptr; + } + + // Release the IOSurface because MacIOSurface retained it + CFRelease(surfaceRef); + + return ioSurface.forget(); +} + +already_AddRefed<MacIOSurface> MacIOSurface::LookupSurface(IOSurfaceID aIOSurfaceID, + double aContentsScaleFactor, + bool aHasAlpha) { + if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0) + return nullptr; + + IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceLookup(aIOSurfaceID); + if (!surfaceRef) + return nullptr; + + RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha); + if (!ioSurface) { + ::CFRelease(surfaceRef); + return nullptr; + } + + // Release the IOSurface because MacIOSurface retained it + CFRelease(surfaceRef); + + return ioSurface.forget(); +} + +IOSurfaceID MacIOSurface::GetIOSurfaceID() { + return MacIOSurfaceLib::IOSurfaceGetID(mIOSurfacePtr); +} + +void* MacIOSurface::GetBaseAddress() { + return MacIOSurfaceLib::IOSurfaceGetBaseAddress(mIOSurfacePtr); +} + +void* MacIOSurface::GetBaseAddressOfPlane(size_t aPlaneIndex) +{ + return MacIOSurfaceLib::IOSurfaceGetBaseAddressOfPlane(mIOSurfacePtr, + aPlaneIndex); +} + +size_t MacIOSurface::GetWidth(size_t plane) { + size_t intScaleFactor = ceil(mContentsScaleFactor); + return GetDevicePixelWidth(plane) / intScaleFactor; +} + +size_t MacIOSurface::GetHeight(size_t plane) { + size_t intScaleFactor = ceil(mContentsScaleFactor); + return GetDevicePixelHeight(plane) / intScaleFactor; +} + +size_t MacIOSurface::GetPlaneCount() { + return MacIOSurfaceLib::IOSurfaceGetPlaneCount(mIOSurfacePtr); +} + +/*static*/ size_t MacIOSurface::GetMaxWidth() { + if (!MacIOSurfaceLib::isInit()) + return -1; + return MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(MacIOSurfaceLib::kPropWidth); +} + +/*static*/ size_t MacIOSurface::GetMaxHeight() { + if (!MacIOSurfaceLib::isInit()) + return -1; + return MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(MacIOSurfaceLib::kPropHeight); +} + +size_t MacIOSurface::GetDevicePixelWidth(size_t plane) { + return MacIOSurfaceLib::IOSurfaceGetWidth(mIOSurfacePtr, plane); +} + +size_t MacIOSurface::GetDevicePixelHeight(size_t plane) { + return MacIOSurfaceLib::IOSurfaceGetHeight(mIOSurfacePtr, plane); +} + +size_t MacIOSurface::GetBytesPerRow(size_t plane) { + return MacIOSurfaceLib::IOSurfaceGetBytesPerRow(mIOSurfacePtr, plane); +} + +OSType MacIOSurface::GetPixelFormat() { + return MacIOSurfaceLib::IOSurfaceGetPixelFormat(mIOSurfacePtr); +} + +void MacIOSurface::IncrementUseCount() { + MacIOSurfaceLib::IOSurfaceIncrementUseCount(mIOSurfacePtr); +} + +void MacIOSurface::DecrementUseCount() { + MacIOSurfaceLib::IOSurfaceDecrementUseCount(mIOSurfacePtr); +} + +#define READ_ONLY 0x1 +void MacIOSurface::Lock(bool aReadOnly) { + MacIOSurfaceLib::IOSurfaceLock(mIOSurfacePtr, aReadOnly ? READ_ONLY : 0, nullptr); +} + +void MacIOSurface::Unlock(bool aReadOnly) { + MacIOSurfaceLib::IOSurfaceUnlock(mIOSurfacePtr, aReadOnly ? READ_ONLY : 0, nullptr); +} + +using mozilla::gfx::SourceSurface; +using mozilla::gfx::IntSize; +using mozilla::gfx::SurfaceFormat; + +void +MacIOSurfaceBufferDeallocator(void* aClosure) +{ + MOZ_ASSERT(aClosure); + + delete [] static_cast<unsigned char*>(aClosure); +} + +already_AddRefed<SourceSurface> +MacIOSurface::GetAsSurface() { + Lock(); + size_t bytesPerRow = GetBytesPerRow(); + size_t ioWidth = GetDevicePixelWidth(); + size_t ioHeight = GetDevicePixelHeight(); + + unsigned char* ioData = (unsigned char*)GetBaseAddress(); + unsigned char* dataCpy = + new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)]; + for (size_t i = 0; i < ioHeight; i++) { + memcpy(dataCpy + i * bytesPerRow, + ioData + i * bytesPerRow, ioWidth * 4); + } + + Unlock(); + + SurfaceFormat format = HasAlpha() ? mozilla::gfx::SurfaceFormat::B8G8R8A8 : + mozilla::gfx::SurfaceFormat::B8G8R8X8; + + RefPtr<mozilla::gfx::DataSourceSurface> surf = + mozilla::gfx::Factory::CreateWrappingDataSourceSurface(dataCpy, + bytesPerRow, + IntSize(ioWidth, ioHeight), + format, + &MacIOSurfaceBufferDeallocator, + static_cast<void*>(dataCpy)); + + return surf.forget(); +} + +SurfaceFormat +MacIOSurface::GetFormat() +{ + OSType pixelFormat = GetPixelFormat(); + if (pixelFormat == '420v') { + return SurfaceFormat::NV12; + } else if (pixelFormat == '2vuy') { + return SurfaceFormat::YUV422; + } else { + return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8; + } +} + +SurfaceFormat +MacIOSurface::GetReadFormat() +{ + OSType pixelFormat = GetPixelFormat(); + if (pixelFormat == '420v') { + return SurfaceFormat::NV12; + } else if (pixelFormat == '2vuy') { + return SurfaceFormat::R8G8B8X8; + } else { + return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8; + } +} + +CGLError +MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, size_t plane) +{ + MOZ_ASSERT(plane >= 0); + OSType pixelFormat = GetPixelFormat(); + + GLenum internalFormat; + GLenum format; + GLenum type; + if (pixelFormat == '420v') { + MOZ_ASSERT(GetPlaneCount() == 2); + MOZ_ASSERT(plane < 2); + + if (plane == 0) { + internalFormat = format = GL_LUMINANCE; + } else { + internalFormat = format = GL_LUMINANCE_ALPHA; + } + type = GL_UNSIGNED_BYTE; + } else if (pixelFormat == '2vuy') { + MOZ_ASSERT(plane == 0); + + internalFormat = GL_RGB; + format = LOCAL_GL_YCBCR_422_APPLE; + type = GL_UNSIGNED_SHORT_8_8_APPLE; + } else { + MOZ_ASSERT(plane == 0); + + internalFormat = HasAlpha() ? GL_RGBA : GL_RGB; + format = GL_BGRA; + type = GL_UNSIGNED_INT_8_8_8_8_REV; + } + CGLError temp = MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx, + GL_TEXTURE_RECTANGLE_ARB, + internalFormat, + GetDevicePixelWidth(plane), + GetDevicePixelHeight(plane), + format, + type, + mIOSurfacePtr, plane); + return temp; +} + +static +CGColorSpaceRef CreateSystemColorSpace() { + CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID()); + if (!cspace) { + cspace = ::CGColorSpaceCreateDeviceRGB(); + } + return cspace; +} + +CGContextRef MacIOSurface::CreateIOSurfaceContext() { + CGColorSpaceRef cspace = CreateSystemColorSpace(); + CGContextRef ref = MacIOSurfaceLib::IOSurfaceContextCreate(mIOSurfacePtr, + GetDevicePixelWidth(), + GetDevicePixelHeight(), + 8, 32, cspace, 0x2002); + ::CGColorSpaceRelease(cspace); + return ref; +} + +CGImageRef MacIOSurface::CreateImageFromIOSurfaceContext(CGContextRef aContext) { + if (!MacIOSurfaceLib::isInit()) + return nullptr; + + return MacIOSurfaceLib::IOSurfaceContextCreateImage(aContext); +} + +already_AddRefed<MacIOSurface> MacIOSurface::IOSurfaceContextGetSurface(CGContextRef aContext, + double aContentsScaleFactor, + bool aHasAlpha) { + if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0) + return nullptr; + + IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceContextGetSurface(aContext); + if (!surfaceRef) + return nullptr; + + RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha); + if (!ioSurface) { + ::CFRelease(surfaceRef); + return nullptr; + } + return ioSurface.forget(); +} + + +CGContextType GetContextType(CGContextRef ref) +{ + if (!MacIOSurfaceLib::isInit() || !MacIOSurfaceLib::sCGContextGetTypePtr) + return CG_CONTEXT_TYPE_UNKNOWN; + + unsigned int type = MacIOSurfaceLib::sCGContextGetTypePtr(ref); + if (type == CG_CONTEXT_TYPE_BITMAP) { + return CG_CONTEXT_TYPE_BITMAP; + } else if (type == CG_CONTEXT_TYPE_IOSURFACE) { + return CG_CONTEXT_TYPE_IOSURFACE; + } else { + return CG_CONTEXT_TYPE_UNKNOWN; + } +} + + diff --git a/gfx/2d/MacIOSurface.h b/gfx/2d/MacIOSurface.h new file mode 100644 index 000000000..0d4f79a15 --- /dev/null +++ b/gfx/2d/MacIOSurface.h @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MacIOSurface_h__ +#define MacIOSurface_h__ +#ifdef XP_DARWIN +#include <QuartzCore/QuartzCore.h> +#include <CoreVideo/CoreVideo.h> +#include <dlfcn.h> + +struct _CGLContextObject; + +typedef _CGLContextObject* CGLContextObj; +typedef struct CGContext* CGContextRef; +typedef struct CGImage* CGImageRef; +typedef uint32_t IOSurfaceID; + +#ifdef XP_IOS +typedef kern_return_t IOReturn; +typedef int CGLError; +#endif + +typedef CFTypeRef IOSurfacePtr; +typedef IOSurfacePtr (*IOSurfaceCreateFunc) (CFDictionaryRef properties); +typedef IOSurfacePtr (*IOSurfaceLookupFunc) (uint32_t io_surface_id); +typedef IOSurfaceID (*IOSurfaceGetIDFunc)(IOSurfacePtr io_surface); +typedef void (*IOSurfaceVoidFunc)(IOSurfacePtr io_surface); +typedef IOReturn (*IOSurfaceLockFunc)(IOSurfacePtr io_surface, uint32_t options, + uint32_t *seed); +typedef IOReturn (*IOSurfaceUnlockFunc)(IOSurfacePtr io_surface, + uint32_t options, uint32_t *seed); +typedef void* (*IOSurfaceGetBaseAddressFunc)(IOSurfacePtr io_surface); +typedef void* (*IOSurfaceGetBaseAddressOfPlaneFunc)(IOSurfacePtr io_surface, + size_t planeIndex); +typedef size_t (*IOSurfaceSizeTFunc)(IOSurfacePtr io_surface); +typedef size_t (*IOSurfaceSizePlaneTFunc)(IOSurfacePtr io_surface, size_t plane); +typedef size_t (*IOSurfaceGetPropertyMaximumFunc) (CFStringRef property); +typedef CGLError (*CGLTexImageIOSurface2DFunc) (CGLContextObj ctxt, + GLenum target, GLenum internalFormat, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + IOSurfacePtr ioSurface, GLuint plane); +typedef CGContextRef (*IOSurfaceContextCreateFunc)(CFTypeRef io_surface, + unsigned width, unsigned height, + unsigned bitsPerComponent, unsigned bytes, + CGColorSpaceRef colorSpace, CGBitmapInfo bitmapInfo); +typedef CGImageRef (*IOSurfaceContextCreateImageFunc)(CGContextRef ref); +typedef IOSurfacePtr (*IOSurfaceContextGetSurfaceFunc)(CGContextRef ref); + +typedef IOSurfacePtr (*CVPixelBufferGetIOSurfaceFunc)( + CVPixelBufferRef pixelBuffer); + +typedef OSType (*IOSurfacePixelFormatFunc)(IOSurfacePtr io_surface); + +#ifdef XP_MACOSX +#import <OpenGL/OpenGL.h> +#else +#import <OpenGLES/ES2/gl.h> +#endif + +#include "2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/RefCounted.h" + +enum CGContextType { + CG_CONTEXT_TYPE_UNKNOWN = 0, + // These are found by inspection, it's possible they could be changed + CG_CONTEXT_TYPE_BITMAP = 4, + CG_CONTEXT_TYPE_IOSURFACE = 8 +}; + +CGContextType GetContextType(CGContextRef ref); + +class MacIOSurface final : public mozilla::external::AtomicRefCounted<MacIOSurface> { +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(MacIOSurface) + typedef mozilla::gfx::SourceSurface SourceSurface; + + // The usage count of the IOSurface is increased by 1 during the lifetime + // of the MacIOSurface instance. + // MacIOSurface holds a reference to the corresponding IOSurface. + + static already_AddRefed<MacIOSurface> CreateIOSurface(int aWidth, int aHeight, + double aContentsScaleFactor = 1.0, + bool aHasAlpha = true); + static void ReleaseIOSurface(MacIOSurface *aIOSurface); + static already_AddRefed<MacIOSurface> LookupSurface(IOSurfaceID aSurfaceID, + double aContentsScaleFactor = 1.0, + bool aHasAlpha = true); + + explicit MacIOSurface(const void *aIOSurfacePtr, + double aContentsScaleFactor = 1.0, + bool aHasAlpha = true); + ~MacIOSurface(); + IOSurfaceID GetIOSurfaceID(); + void *GetBaseAddress(); + void *GetBaseAddressOfPlane(size_t planeIndex); + size_t GetPlaneCount(); + OSType GetPixelFormat(); + // GetWidth() and GetHeight() return values in "display pixels". A + // "display pixel" is the smallest fully addressable part of a display. + // But in HiDPI modes each "display pixel" corresponds to more than one + // device pixel. Use GetDevicePixel**() to get device pixels. + size_t GetWidth(size_t plane = 0); + size_t GetHeight(size_t plane = 0); + double GetContentsScaleFactor() { return mContentsScaleFactor; } + size_t GetDevicePixelWidth(size_t plane = 0); + size_t GetDevicePixelHeight(size_t plane = 0); + size_t GetBytesPerRow(size_t plane = 0); + void Lock(bool aReadOnly = true); + void Unlock(bool aReadOnly = true); + void IncrementUseCount(); + void DecrementUseCount(); + bool HasAlpha() { return mHasAlpha; } + mozilla::gfx::SurfaceFormat GetFormat(); + mozilla::gfx::SurfaceFormat GetReadFormat(); + + // We would like to forward declare NSOpenGLContext, but it is an @interface + // and this file is also used from c++, so we use a void *. + CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, size_t plane = 0); + already_AddRefed<SourceSurface> GetAsSurface(); + CGContextRef CreateIOSurfaceContext(); + + // FIXME This doesn't really belong here + static CGImageRef CreateImageFromIOSurfaceContext(CGContextRef aContext); + static already_AddRefed<MacIOSurface> IOSurfaceContextGetSurface(CGContextRef aContext, + double aContentsScaleFactor = 1.0, + bool aHasAlpha = true); + static size_t GetMaxWidth(); + static size_t GetMaxHeight(); + +private: + friend class nsCARenderer; + const void* mIOSurfacePtr; + double mContentsScaleFactor; + bool mHasAlpha; +}; + +class MacIOSurfaceLib { +public: + MacIOSurfaceLib() = delete; + static void *sIOSurfaceFramework; + static void *sOpenGLFramework; + static void *sCoreGraphicsFramework; + static void *sCoreVideoFramework; + static bool isLoaded; + static IOSurfaceCreateFunc sCreate; + static IOSurfaceGetIDFunc sGetID; + static IOSurfaceLookupFunc sLookup; + static IOSurfaceGetBaseAddressFunc sGetBaseAddress; + static IOSurfaceGetBaseAddressOfPlaneFunc sGetBaseAddressOfPlane; + static IOSurfaceSizeTFunc sPlaneCount; + static IOSurfaceLockFunc sLock; + static IOSurfaceUnlockFunc sUnlock; + static IOSurfaceVoidFunc sIncrementUseCount; + static IOSurfaceVoidFunc sDecrementUseCount; + static IOSurfaceSizePlaneTFunc sWidth; + static IOSurfaceSizePlaneTFunc sHeight; + static IOSurfaceSizePlaneTFunc sBytesPerRow; + static IOSurfaceGetPropertyMaximumFunc sGetPropertyMaximum; + static CGLTexImageIOSurface2DFunc sTexImage; + static IOSurfaceContextCreateFunc sIOSurfaceContextCreate; + static IOSurfaceContextCreateImageFunc sIOSurfaceContextCreateImage; + static IOSurfaceContextGetSurfaceFunc sIOSurfaceContextGetSurface; + static CVPixelBufferGetIOSurfaceFunc sCVPixelBufferGetIOSurface; + static IOSurfacePixelFormatFunc sPixelFormat; + static CFStringRef kPropWidth; + static CFStringRef kPropHeight; + static CFStringRef kPropBytesPerElem; + static CFStringRef kPropBytesPerRow; + static CFStringRef kPropIsGlobal; + + static bool isInit(); + static CFStringRef GetIOConst(const char* symbole); + static IOSurfacePtr IOSurfaceCreate(CFDictionaryRef properties); + static IOSurfacePtr IOSurfaceLookup(IOSurfaceID aIOSurfaceID); + static IOSurfaceID IOSurfaceGetID(IOSurfacePtr aIOSurfacePtr); + static void* IOSurfaceGetBaseAddress(IOSurfacePtr aIOSurfacePtr); + static void* IOSurfaceGetBaseAddressOfPlane(IOSurfacePtr aIOSurfacePtr, + size_t aPlaneIndex); + static size_t IOSurfaceGetPlaneCount(IOSurfacePtr aIOSurfacePtr); + static size_t IOSurfaceGetWidth(IOSurfacePtr aIOSurfacePtr, size_t plane); + static size_t IOSurfaceGetHeight(IOSurfacePtr aIOSurfacePtr, size_t plane); + static size_t IOSurfaceGetBytesPerRow(IOSurfacePtr aIOSurfacePtr, size_t plane); + static size_t IOSurfaceGetPropertyMaximum(CFStringRef property); + static IOReturn IOSurfaceLock(IOSurfacePtr aIOSurfacePtr, + uint32_t options, uint32_t *seed); + static IOReturn IOSurfaceUnlock(IOSurfacePtr aIOSurfacePtr, + uint32_t options, uint32_t *seed); + static void IOSurfaceIncrementUseCount(IOSurfacePtr aIOSurfacePtr); + static void IOSurfaceDecrementUseCount(IOSurfacePtr aIOSurfacePtr); + static CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, + GLenum target, GLenum internalFormat, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + IOSurfacePtr ioSurface, GLuint plane); + static CGContextRef IOSurfaceContextCreate(IOSurfacePtr aIOSurfacePtr, + unsigned aWidth, unsigned aHeight, + unsigned aBitsPerCompoent, unsigned aBytes, + CGColorSpaceRef aColorSpace, CGBitmapInfo bitmapInfo); + static CGImageRef IOSurfaceContextCreateImage(CGContextRef ref); + static IOSurfacePtr IOSurfaceContextGetSurface(CGContextRef ref); + static IOSurfacePtr CVPixelBufferGetIOSurface(CVPixelBufferRef apixelBuffer); + static OSType IOSurfaceGetPixelFormat(IOSurfacePtr aIOSurfacePtr); + static unsigned int (*sCGContextGetTypePtr) (CGContextRef); + static void LoadLibrary(); + static void CloseLibrary(); + + // Static deconstructor + static class LibraryUnloader { + public: + ~LibraryUnloader() { + CloseLibrary(); + } + } sLibraryUnloader; +}; + +#endif +#endif diff --git a/gfx/2d/Matrix.cpp b/gfx/2d/Matrix.cpp new file mode 100644 index 000000000..b3fce00ba --- /dev/null +++ b/gfx/2d/Matrix.cpp @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Matrix.h" +#include "Quaternion.h" +#include "Tools.h" +#include <algorithm> +#include <ostream> +#include <math.h> +#include <float.h> // for FLT_EPSILON + +#include "mozilla/FloatingPoint.h" // for UnspecifiedNaN + +using namespace std; + + +namespace mozilla { +namespace gfx { + +/* Force small values to zero. We do this to avoid having sin(360deg) + * evaluate to a tiny but nonzero value. + */ +double +FlushToZero(double aVal) +{ + // XXX Is double precision really necessary here + if (-FLT_EPSILON < aVal && aVal < FLT_EPSILON) { + return 0.0f; + } else { + return aVal; + } +} + +/* Computes tan(aTheta). For values of aTheta such that tan(aTheta) is + * undefined or very large, SafeTangent returns a manageably large value + * of the correct sign. + */ +double +SafeTangent(double aTheta) +{ + // XXX Is double precision really necessary here + const double kEpsilon = 0.0001; + + /* tan(theta) = sin(theta)/cos(theta); problems arise when + * cos(theta) is too close to zero. Limit cos(theta) to the + * range [-1, -epsilon] U [epsilon, 1]. + */ + + double sinTheta = sin(aTheta); + double cosTheta = cos(aTheta); + + if (cosTheta >= 0 && cosTheta < kEpsilon) { + cosTheta = kEpsilon; + } else if (cosTheta < 0 && cosTheta >= -kEpsilon) { + cosTheta = -kEpsilon; + } + return FlushToZero(sinTheta / cosTheta); +} + +std::ostream& +operator<<(std::ostream& aStream, const Matrix& aMatrix) +{ + return aStream << "[ " << aMatrix._11 + << " " << aMatrix._12 + << "; " << aMatrix._21 + << " " << aMatrix._22 + << "; " << aMatrix._31 + << " " << aMatrix._32 + << "; ]"; +} + +Matrix +Matrix::Rotation(Float aAngle) +{ + Matrix newMatrix; + + Float s = sinf(aAngle); + Float c = cosf(aAngle); + + newMatrix._11 = c; + newMatrix._12 = s; + newMatrix._21 = -s; + newMatrix._22 = c; + + return newMatrix; +} + +Rect +Matrix::TransformBounds(const Rect &aRect) const +{ + int i; + Point quad[4]; + Float min_x, max_x; + Float min_y, max_y; + + quad[0] = TransformPoint(aRect.TopLeft()); + quad[1] = TransformPoint(aRect.TopRight()); + quad[2] = TransformPoint(aRect.BottomLeft()); + quad[3] = TransformPoint(aRect.BottomRight()); + + min_x = max_x = quad[0].x; + min_y = max_y = quad[0].y; + + for (i = 1; i < 4; i++) { + if (quad[i].x < min_x) + min_x = quad[i].x; + if (quad[i].x > max_x) + max_x = quad[i].x; + + if (quad[i].y < min_y) + min_y = quad[i].y; + if (quad[i].y > max_y) + max_y = quad[i].y; + } + + return Rect(min_x, min_y, max_x - min_x, max_y - min_y); +} + +Matrix& +Matrix::NudgeToIntegers() +{ + NudgeToInteger(&_11); + NudgeToInteger(&_12); + NudgeToInteger(&_21); + NudgeToInteger(&_22); + NudgeToInteger(&_31); + NudgeToInteger(&_32); + return *this; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h new file mode 100644 index 000000000..22a01ca10 --- /dev/null +++ b/gfx/2d/Matrix.h @@ -0,0 +1,1676 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MATRIX_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" + +namespace mozilla { +namespace gfx { + +static 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; +} + +class Matrix +{ +public: + Matrix() + : _11(1.0f), _12(0) + , _21(0), _22(1.0f) + , _31(0), _32(0) + {} + Matrix(Float a11, Float a12, Float a21, Float a22, Float a31, Float a32) + : _11(a11), _12(a12) + , _21(a21), _22(a22) + , _31(a31), _32(a32) + {} + union { + struct { + Float _11, _12; + Float _21, _22; + Float _31, _32; + }; + Float components[6]; + }; + + MOZ_ALWAYS_INLINE Matrix Copy() const + { + return Matrix(*this); + } + + friend std::ostream& operator<<(std::ostream& aStream, const Matrix& aMatrix); + + Point TransformPoint(const Point &aPoint) const + { + Point retPoint; + + retPoint.x = aPoint.x * _11 + aPoint.y * _21 + _31; + retPoint.y = aPoint.x * _12 + aPoint.y * _22 + _32; + + return retPoint; + } + + Size TransformSize(const Size &aSize) const + { + Size retSize; + + retSize.width = aSize.width * _11 + aSize.height * _21; + retSize.height = aSize.width * _12 + aSize.height * _22; + + return retSize; + } + + GFX2D_API Rect TransformBounds(const Rect& rect) const; + + static Matrix Translation(Float aX, Float aY) + { + return Matrix(1.0f, 0.0f, 0.0f, 1.0f, aX, aY); + } + + static Matrix Translation(Point 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: + * + * Matrix::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.) + */ + Matrix &PreTranslate(Float aX, Float aY) + { + _31 += _11 * aX + _21 * aY; + _32 += _12 * aX + _22 * aY; + + return *this; + } + + Matrix &PreTranslate(const Point &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. + */ + Matrix &PostTranslate(Float aX, Float aY) + { + _31 += aX; + _32 += aY; + return *this; + } + + Matrix &PostTranslate(const Point &aPoint) + { + return PostTranslate(aPoint.x, aPoint.y); + } + + static Matrix Scaling(Float aScaleX, Float aScaleY) + { + return Matrix(aScaleX, 0.0f, 0.0f, aScaleY, 0.0f, 0.0f); + } + + /** + * Similar to PreTranslate, but applies a scale instead of a translation. + */ + Matrix &PreScale(Float aX, Float aY) + { + _11 *= aX; + _12 *= aX; + _21 *= aY; + _22 *= aY; + + return *this; + } + + /** + * Similar to PostTranslate, but applies a scale instead of a translation. + */ + Matrix &PostScale(Float aScaleX, Float aScaleY) + { + _11 *= aScaleX; + _12 *= aScaleY; + _21 *= aScaleX; + _22 *= aScaleY; + _31 *= aScaleX; + _32 *= aScaleY; + + return *this; + } + + GFX2D_API static Matrix Rotation(Float aAngle); + + /** + * Similar to PreTranslate, but applies a rotation instead of a translation. + */ + Matrix &PreRotate(Float aAngle) + { + return *this = Matrix::Rotation(aAngle) * *this; + } + + bool Invert() + { + // Compute co-factors. + Float A = _22; + Float B = -_21; + Float C = _21 * _32 - _22 * _31; + Float D = -_12; + Float E = _11; + Float F = _31 * _12 - _11 * _32; + + Float det = Determinant(); + + if (!det) { + return false; + } + + Float 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; + } + + Matrix Inverse() const + { + Matrix clone = *this; + DebugOnly<bool> inverted = clone.Invert(); + MOZ_ASSERT(inverted, "Attempted to get the inverse of a non-invertible matrix"); + return clone; + } + + Float Determinant() const + { + return _11 * _22 - _12 * _21; + } + + Matrix operator*(const Matrix &aMatrix) const + { + Matrix 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; + } + + Matrix& operator*=(const Matrix &aMatrix) + { + *this = *this * aMatrix; + return *this; + } + + /** + * Multiplies in the opposite order to operator=*. + */ + Matrix &PreMultiply(const Matrix &aMatrix) + { + *this = aMatrix * *this; + return *this; + } + + /* Returns true if the other matrix is fuzzy-equal to this matrix. + * Note that this isn't a cheap comparison! + */ + bool operator==(const Matrix& other) const + { + return FuzzyEqual(_11, other._11) && FuzzyEqual(_12, other._12) && + FuzzyEqual(_21, other._21) && FuzzyEqual(_22, other._22) && + FuzzyEqual(_31, other._31) && FuzzyEqual(_32, other._32); + } + + bool operator!=(const Matrix& other) const + { + return !(*this == other); + } + + bool ExactlyEquals(const Matrix& 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 mozilla::IsFinite(_11) && mozilla::IsFinite(_12) && + mozilla::IsFinite(_21) && mozilla::IsFinite(_22) && + mozilla::IsFinite(_31) && mozilla::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 + Float(0.5))) || + !FuzzyEqual(_32, floor(_32 + Float(0.5))); + } + + /** + * 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 + { + Float det = Determinant(); + return !mozilla::IsFinite(det) || det == 0; + } + + GFX2D_API Matrix &NudgeToIntegers(); + + bool IsTranslation() const + { + return FuzzyEqual(_11, 1.0f) && FuzzyEqual(_12, 0.0f) && + FuzzyEqual(_21, 0.0f) && FuzzyEqual(_22, 1.0f); + } + + static bool FuzzyIsInteger(Float 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); + } + + Point GetTranslation() const { + return Point(_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); + } +}; + +// 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 <typename SourceUnits, typename TargetUnits> +class Matrix4x4Typed +{ +public: + typedef PointTyped<SourceUnits> SourcePoint; + typedef PointTyped<TargetUnits> TargetPoint; + typedef Point3DTyped<SourceUnits> SourcePoint3D; + typedef Point3DTyped<TargetUnits> TargetPoint3D; + typedef Point4DTyped<SourceUnits> SourcePoint4D; + typedef Point4DTyped<TargetUnits> TargetPoint4D; + typedef RectTyped<SourceUnits> SourceRect; + typedef RectTyped<TargetUnits> 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(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) + : _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 Float aArray[16]) + { + memcpy(components, aArray, sizeof(components)); + } + + Matrix4x4Typed(const Matrix4x4Typed& aOther) + { + memcpy(this, &aOther, sizeof(*this)); + } + + union { + struct { + Float _11, _12, _13, _14; + Float _21, _22, _23, _24; + Float _31, _32, _33, _34; + Float _41, _42, _43, _44; + }; + Float components[16]; + }; + + friend std::ostream& operator<<(std::ostream& aStream, const Matrix4x4Typed& aMatrix) + { + const Float *f = &aMatrix._11; + aStream << "[ " << f[0] << " " << f[1] << " " << f[2] << " " << f[3] << " ;" << std::endl; f += 4; + aStream << " " << f[0] << " " << f[1] << " " << f[2] << " " << f[3] << " ;" << std::endl; f += 4; + aStream << " " << f[0] << " " << f[1] << " " << f[2] << " " << f[3] << " ;" << std::endl; f += 4; + aStream << " " << f[0] << " " << f[1] << " " << f[2] << " " << f[3] << " ]" << std::endl; + return aStream; + } + + Point4D& operator[](int aIndex) + { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); + return *reinterpret_cast<Point4D*>((&_11)+4*aIndex); + } + const Point4D& operator[](int aIndex) const + { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); + return *reinterpret_cast<const Point4D*>((&_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(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; + } + + Matrix As2D() const + { + MOZ_ASSERT(Is2D(), "Matrix is not a 2D affine transform"); + + return Matrix(_11, _12, _21, _22, _41, _42); + } + + bool CanDraw2D(Matrix* 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) { + Float 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); + max_x = std::max(max_x, verts[i].x); + min_y = std::min(min_y, verts[i].y); + max_y = std::max(max_y, verts[i].y); + } + + 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 that 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 + { + // Initialize a double-buffered array of points in homogenous space with + // the input rectangle, aRect. + Point4DTyped<UnknownUnits, F> points[2][kTransformAndClipRectMaxVerts]; + Point4DTyped<UnknownUnits, F>* dstPoint = points[0]; + + *dstPoint++ = TransformPoint(Point4DTyped<UnknownUnits, F>(aRect.x, aRect.y, 0, 1)); + *dstPoint++ = TransformPoint(Point4DTyped<UnknownUnits, F>(aRect.XMost(), aRect.y, 0, 1)); + *dstPoint++ = TransformPoint(Point4DTyped<UnknownUnits, F>(aRect.XMost(), aRect.YMost(), 0, 1)); + *dstPoint++ = TransformPoint(Point4DTyped<UnknownUnits, F>(aRect.x, aRect.YMost(), 0, 1)); + + // View frustum clipping planes are described as normals originating from + // the 0,0,0,0 origin. + Point4DTyped<UnknownUnits, F> planeNormals[4]; + planeNormals[0] = Point4DTyped<UnknownUnits, F>(1.0, 0.0, 0.0, -aClip.x); + planeNormals[1] = Point4DTyped<UnknownUnits, F>(-1.0, 0.0, 0.0, aClip.XMost()); + planeNormals[2] = Point4DTyped<UnknownUnits, F>(0.0, 1.0, 0.0, -aClip.y); + planeNormals[3] = Point4DTyped<UnknownUnits, F>(0.0, -1.0, 0.0, aClip.YMost()); + + // Iterate through each clipping plane and clip the polygon. + // In each pass, we double buffer, alternating between points[0] and + // points[1]. + for (int plane=0; plane < 4; plane++) { + planeNormals[plane].Normalize(); + Point4DTyped<UnknownUnits, F>* srcPoint = points[plane & 1]; + Point4DTyped<UnknownUnits, F>* srcPointEnd = dstPoint; + + dstPoint = points[~plane & 1]; + Point4DTyped<UnknownUnits, F>* dstPointStart = dstPoint; + + Point4DTyped<UnknownUnits, F>* prevPoint = srcPointEnd - 1; + F prevDot = planeNormals[plane].DotProduct(*prevPoint); + while (srcPoint < srcPointEnd && ((dstPoint - dstPointStart) < kTransformAndClipRectMaxVerts)) { + F nextDot = planeNormals[plane].DotProduct(*srcPoint); + + if ((nextDot >= 0.0) != (prevDot >= 0.0)) { + // An intersection with the clipping plane has been detected. + // Interpolate to find the intersecting point and emit it. + F t = -prevDot / (nextDot - prevDot); + *dstPoint++ = *srcPoint * t + *prevPoint * (1.0 - t); + } + + if (nextDot >= 0.0) { + // Emit any source points that are on the positive side of the + // clipping plane. + *dstPoint++ = *srcPoint; + } + + prevPoint = srcPoint++; + prevDot = nextDot; + } + + if (dstPoint == dstPointStart) { + break; + } + } + + size_t dstPointCount = 0; + size_t srcPointCount = dstPoint - points[0]; + for (Point4DTyped<UnknownUnits, F>* srcPoint = points[0]; srcPoint < points[0] + srcPointCount; srcPoint++) { + + 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 (dstPointCount == 0 || p != aVerts[dstPointCount - 1]) { + aVerts[dstPointCount++] = p; + } + } + + return dstPointCount; + } + + static const int kTransformAndClipRectMaxVerts = 32; + + static Matrix4x4Typed From2D(const Matrix &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 + { + Point4DTyped<TargetUnits, F> verts[4]; + verts[0] = TransformPoint(Point4DTyped<SourceUnits, F>(aRect.x, aRect.y, 0.0, 1.0)); + verts[1] = TransformPoint(Point4DTyped<SourceUnits, F>(aRect.XMost(), aRect.y, 0.0, 1.0)); + verts[2] = TransformPoint(Point4DTyped<SourceUnits, F>(aRect.XMost(), aRect.YMost(), 0.0, 1.0)); + verts[3] = TransformPoint(Point4DTyped<SourceUnits, F>(aRect.x, aRect.YMost(), 0.0, 1.0)); + + 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(Float aX, Float aY, Float 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(Float aX, Float aY, Float 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 Point3D& 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(Float aX, Float aY, Float 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(Float aScaleX, Float aScaleY, float 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(Float aX, Float aY, Float 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(Float aScaleX, Float aScaleY, Float 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(Float aSkew) + { + (*this)[1] += (*this)[0] * aSkew; + } + + void SkewXZ(Float aSkew) + { + (*this)[2] += (*this)[0] * aSkew; + } + + void SkewYZ(Float aSkew) + { + (*this)[2] += (*this)[1] * aSkew; + } + + Matrix4x4Typed &ChangeBasis(const Point3D& aOrigin) + { + return ChangeBasis(aOrigin.x, aOrigin.y, aOrigin.z); + } + + Matrix4x4Typed &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; + } + + 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); + } + + template <typename NewTargetUnits> + Matrix4x4Typed<SourceUnits, NewTargetUnits> operator*(const Matrix4x4Typed<TargetUnits, NewTargetUnits> &aMatrix) const + { + Matrix4x4Typed<SourceUnits, NewTargetUnits> 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> &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; + } + + Float 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() + { + Float det = Determinant(); + if (!det) { + return false; + } + + Matrix4x4Typed<SourceUnits, TargetUnits> 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> Inverse() const + { + typedef Matrix4x4Typed<TargetUnits, SourceUnits> 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; + } + + 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; + Float det = Determinant(); + Float __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 Point4D(*((&_11)+aIndex), *((&_21)+aIndex), *((&_31)+aIndex), *((&_41)+aIndex)); + } + + void SetTransposedVector(int aIndex, Point4D &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; + } + + // Sets this matrix to a rotation matrix given by aQuat. + // This quaternion *MUST* be normalized! + // Implemented in Quaternion.cpp + void SetRotationFromQuaternion(const Quaternion& q) + { + const Float x2 = q.x + q.x, y2 = q.y + q.y, z2 = q.z + q.z; + const Float xx = q.x * x2, xy = q.x * y2, xz = q.x * z2; + const Float yy = q.y * y2, yz = q.y * z2, zz = q.z * z2; + const Float 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<Float>(); + _21 = UnspecifiedNaN<Float>(); + _31 = UnspecifiedNaN<Float>(); + _41 = UnspecifiedNaN<Float>(); + _12 = UnspecifiedNaN<Float>(); + _22 = UnspecifiedNaN<Float>(); + _32 = UnspecifiedNaN<Float>(); + _42 = UnspecifiedNaN<Float>(); + _13 = UnspecifiedNaN<Float>(); + _23 = UnspecifiedNaN<Float>(); + _33 = UnspecifiedNaN<Float>(); + _43 = UnspecifiedNaN<Float>(); + _14 = UnspecifiedNaN<Float>(); + _24 = UnspecifiedNaN<Float>(); + _34 = UnspecifiedNaN<Float>(); + _44 = UnspecifiedNaN<Float>(); + } + + void SkewXY(double aXSkew, double aYSkew) + { + // XXX Is double precision really necessary here + float tanX = SafeTangent(aXSkew); + float tanY = SafeTangent(aYSkew); + float 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)); + + float 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)); + + float 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)); + + float 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://www.w3.org/TR/css3-3d-transforms/#Rotate3dDefined + void SetRotateAxisAngle(double aX, double aY, double aZ, double aTheta) + { + Point3D vector(aX, aY, aZ); + if (!vector.Length()) { + return; + } + vector.Normalize(); + + 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(float 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. + Point3D a = TransformPoint(Point3D(0, 0, 0)); + Point3D b = TransformPoint(Point3D(0, 1, 0)); + Point3D c = TransformPoint(Point3D(1, 0, 0)); + + // Convert to two vectors on the surface of the plane. + Point3D ab = b - a; + Point3D 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; + } + + /** + * Convert between typed and untyped matrices. + */ + Matrix4x4 ToUnknownMatrix() const { + return Matrix4x4{_11, _12, _13, _14, + _21, _22, _23, _24, + _31, _32, _33, _34, + _41, _42, _43, _44}; + } + static Matrix4x4Typed FromUnknownMatrix(const Matrix4x4& 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}; + } +}; + +typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4; + +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; + } + + 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]; + }; +}; + +} // 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 000000000..ec62368b3 --- /dev/null +++ b/gfx/2d/MatrixFwd.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef MOZILLA_GFX_MATRIX_FWD_H_ +#define MOZILLA_GFX_MATRIX_FWD_H_ + + +// Forward declare enough things to define the typedef |Matrix4x4|. + +namespace mozilla { +namespace gfx { + +struct UnknownUnits; + +template<class SourceUnits, class TargetUnits> +class Matrix4x4Typed; + +typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/NativeFontResourceDWrite.cpp b/gfx/2d/NativeFontResourceDWrite.cpp new file mode 100644 index 000000000..e4d12ad87 --- /dev/null +++ b/gfx/2d/NativeFontResourceDWrite.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeFontResourceDWrite.h" + +#include <unordered_map> + +#include "DrawTargetD2D1.h" +#include "Logging.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace gfx { + +static Atomic<uint64_t> sNextFontFileKey; +static std::unordered_map<uint64_t, IDWriteFontFileStream*> sFontFileStreams; + +class DWriteFontFileLoader : public IDWriteFontFileLoader +{ +public: + DWriteFontFileLoader() + { + } + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) + { + if (iid == __uuidof(IDWriteFontFileLoader)) { + *ppObject = static_cast<IDWriteFontFileLoader*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() + { + return 1; + } + + IFACEMETHOD_(ULONG, Release)() + { + return 1; + } + + // IDWriteFontFileLoader methods + /** + * Important! Note the key here has to be a uint64_t that will have been + * generated by incrementing sNextFontFileKey. + */ + virtual HRESULT STDMETHODCALLTYPE + CreateStreamFromKey(void const* fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + OUT IDWriteFontFileStream** fontFileStream); + + /** + * Gets the singleton loader instance. Note that when using this font + * loader, the key must be a uint64_t that has been generated by incrementing + * sNextFontFileKey. + * Also note that this is _not_ threadsafe. + */ + static IDWriteFontFileLoader* Instance() + { + if (!mInstance) { + mInstance = new DWriteFontFileLoader(); + DrawTargetD2D1::GetDWriteFactory()-> + RegisterFontFileLoader(mInstance); + } + return mInstance; + } + +private: + static IDWriteFontFileLoader* mInstance; +}; + +class DWriteFontFileStream : public IDWriteFontFileStream +{ +public: + /** + * Used by the FontFileLoader to create a new font stream, + * this font stream is created from data in memory. The memory + * passed may be released after object creation, it will be + * copied internally. + * + * @param aData Font data + */ + DWriteFontFileStream(uint8_t *aData, uint32_t aSize, uint64_t aFontFileKey); + ~DWriteFontFileStream(); + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) + { + if (iid == __uuidof(IDWriteFontFileStream)) { + *ppObject = static_cast<IDWriteFontFileStream*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() + { + ++mRefCnt; + return mRefCnt; + } + + IFACEMETHOD_(ULONG, Release)() + { + --mRefCnt; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + // IDWriteFontFileStream methods + virtual HRESULT STDMETHODCALLTYPE ReadFileFragment(void const** fragmentStart, + UINT64 fileOffset, + UINT64 fragmentSize, + OUT void** fragmentContext); + + virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext); + + virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize); + + virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime); + +private: + std::vector<uint8_t> mData; + uint32_t mRefCnt; + uint64_t mFontFileKey; +}; + +IDWriteFontFileLoader* DWriteFontFileLoader::mInstance = nullptr; + +HRESULT STDMETHODCALLTYPE +DWriteFontFileLoader::CreateStreamFromKey(const void *fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream **fontFileStream) +{ + if (!fontFileReferenceKey || !fontFileStream) { + return E_POINTER; + } + + uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey); + auto found = sFontFileStreams.find(fontFileKey); + if (found == sFontFileStreams.end()) { + *fontFileStream = nullptr; + return E_FAIL; + } + + found->second->AddRef(); + *fontFileStream = found->second; + return S_OK; +} + +DWriteFontFileStream::DWriteFontFileStream(uint8_t *aData, uint32_t aSize, + uint64_t aFontFileKey) + : mRefCnt(0) + , mFontFileKey(aFontFileKey) +{ + mData.resize(aSize); + memcpy(&mData.front(), aData, aSize); +} + +DWriteFontFileStream::~DWriteFontFileStream() +{ + sFontFileStreams.erase(mFontFileKey); +} + +HRESULT STDMETHODCALLTYPE +DWriteFontFileStream::GetFileSize(UINT64 *fileSize) +{ + *fileSize = mData.size(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +DWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DWriteFontFileStream::ReadFileFragment(const void **fragmentStart, + UINT64 fileOffset, + UINT64 fragmentSize, + void **fragmentContext) +{ + // We are required to do bounds checking. + if (fileOffset + fragmentSize > mData.size()) { + return E_FAIL; + } + + // truncate the 64 bit fileOffset to size_t sized index into mData + size_t index = static_cast<size_t>(fileOffset); + + // We should be alive for the duration of this. + *fragmentStart = &mData[index]; + *fragmentContext = nullptr; + return S_OK; +} + +void STDMETHODCALLTYPE +DWriteFontFileStream::ReleaseFileFragment(void *fragmentContext) +{ +} + +/* static */ +already_AddRefed<NativeFontResourceDWrite> +NativeFontResourceDWrite::Create(uint8_t *aFontData, uint32_t aDataLength, + bool aNeedsCairo) +{ + IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory(); + if (!factory) { + gfxWarning() << "Failed to get DWrite Factory."; + return nullptr; + } + + uint64_t fontFileKey = sNextFontFileKey++; + RefPtr<IDWriteFontFileStream> ffsRef = + new DWriteFontFileStream(aFontData, aDataLength, fontFileKey); + sFontFileStreams[fontFileKey] = ffsRef; + + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = + factory->CreateCustomFontFileReference(&fontFileKey, sizeof(fontFileKey), + DWriteFontFileLoader::Instance(), + getter_AddRefs(fontFile)); + if (FAILED(hr)) { + gfxWarning() << "Failed to load font file from data!"; + return nullptr; + } + + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + DWRITE_FONT_FACE_TYPE faceType; + UINT32 numberOfFaces; + hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numberOfFaces); + if (FAILED(hr) || !isSupported) { + gfxWarning() << "Font file is not supported."; + return nullptr; + } + + RefPtr<NativeFontResourceDWrite> fontResource = + new NativeFontResourceDWrite(factory, fontFile.forget(), faceType, + numberOfFaces, aNeedsCairo); + return fontResource.forget(); +} + +already_AddRefed<ScaledFont> +NativeFontResourceDWrite::CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) +{ + if (aIndex >= mNumberOfFaces) { + gfxWarning() << "Font face index is too high for font resource."; + return nullptr; + } + + IDWriteFontFile *fontFile = mFontFile; + RefPtr<IDWriteFontFace> fontFace; + if (FAILED(mFactory->CreateFontFace(mFaceType, 1, &fontFile, aIndex, + DWRITE_FONT_SIMULATIONS_NONE, getter_AddRefs(fontFace)))) { + gfxWarning() << "Failed to create font face from font file data."; + return nullptr; + } + + RefPtr<ScaledFontBase> scaledFont = new ScaledFontDWrite(fontFace, aGlyphSize); + if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) { + gfxWarning() << "Unable to create cairo scaled font DWrite font."; + return nullptr; + } + + return scaledFont.forget(); +} + +} // gfx +} // mozilla diff --git a/gfx/2d/NativeFontResourceDWrite.h b/gfx/2d/NativeFontResourceDWrite.h new file mode 100644 index 000000000..fc11c7b0d --- /dev/null +++ b/gfx/2d/NativeFontResourceDWrite.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceDWrite_h +#define mozilla_gfx_NativeFontResourceDWrite_h + +#include <dwrite.h> + +#include "2D.h" +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceDWrite final : public NativeFontResource +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceDWrite) + /** + * Creates a NativeFontResourceDWrite if data is valid. Note aFontData will be + * copied if required and so can be released after calling. + * + * @param aFontData the SFNT data. + * @param aDataLength length of data. + * @param aNeedsCairo whether the ScaledFont created needs a cairo scaled font + * @return Referenced NativeFontResourceDWrite or nullptr if invalid. + */ + static already_AddRefed<NativeFontResourceDWrite> + Create(uint8_t *aFontData, uint32_t aDataLength, bool aNeedsCairo); + + already_AddRefed<ScaledFont> + CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final; + +private: + NativeFontResourceDWrite(IDWriteFactory *aFactory, + already_AddRefed<IDWriteFontFile> aFontFile, + DWRITE_FONT_FACE_TYPE aFaceType, + uint32_t aNumberOfFaces, bool aNeedsCairo) + : mFactory(aFactory), mFontFile(aFontFile), mFaceType(aFaceType) + , mNumberOfFaces(aNumberOfFaces), mNeedsCairo(aNeedsCairo) + {} + + IDWriteFactory *mFactory; + RefPtr<IDWriteFontFile> mFontFile; + DWRITE_FONT_FACE_TYPE mFaceType; + uint32_t mNumberOfFaces; + bool mNeedsCairo; +}; + +} // gfx +} // mozilla + +#endif // mozilla_gfx_NativeFontResourceDWrite_h diff --git a/gfx/2d/NativeFontResourceGDI.cpp b/gfx/2d/NativeFontResourceGDI.cpp new file mode 100644 index 000000000..f51e75179 --- /dev/null +++ b/gfx/2d/NativeFontResourceGDI.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeFontResourceGDI.h" + +#include "Logging.h" +#include "mozilla/RefPtr.h" +#include "ScaledFontWin.h" + +namespace mozilla { +namespace gfx { + +/* static */ +already_AddRefed<NativeFontResourceGDI> +NativeFontResourceGDI::Create(uint8_t *aFontData, uint32_t aDataLength, + bool aNeedsCairo) +{ + DWORD numberOfFontsAdded; + HANDLE fontResourceHandle = ::AddFontMemResourceEx(aFontData, aDataLength, + 0, &numberOfFontsAdded); + if (!fontResourceHandle) { + gfxWarning() << "Failed to add memory font resource."; + return nullptr; + } + + RefPtr<NativeFontResourceGDI> fontResouce = + new NativeFontResourceGDI(fontResourceHandle, aNeedsCairo); + + return fontResouce.forget(); +} + +NativeFontResourceGDI::~NativeFontResourceGDI() +{ + ::RemoveFontMemResourceEx(mFontResourceHandle); +} + +already_AddRefed<ScaledFont> +NativeFontResourceGDI::CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) +{ + if (aInstanceDataLength < sizeof(LOGFONT)) { + gfxWarning() << "GDI scaled font instance data is truncated."; + return nullptr; + } + + const LOGFONT* logFont = reinterpret_cast<const LOGFONT*>(aInstanceData); + + // Constructor for ScaledFontWin dereferences and copies the LOGFONT, so we + // are safe to pass this reference. + RefPtr<ScaledFontBase> scaledFont = new ScaledFontWin(logFont, aGlyphSize); + if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) { + gfxWarning() << "Unable to create cairo scaled font GDI font."; + return nullptr; + } + + return scaledFont.forget(); +} + +} // gfx +} // mozilla diff --git a/gfx/2d/NativeFontResourceGDI.h b/gfx/2d/NativeFontResourceGDI.h new file mode 100644 index 000000000..8a68b13e5 --- /dev/null +++ b/gfx/2d/NativeFontResourceGDI.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceGDI_h +#define mozilla_gfx_NativeFontResourceGDI_h + +#include <windows.h> + +#include "2D.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Vector.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceGDI final : public NativeFontResource +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceGDI) + /** + * Creates a NativeFontResourceGDI if data is valid. Note aFontData will be + * copied if required and so can be released after calling. + * + * @param aFontData the SFNT data. + * @param aDataLength length of data. + * @param aNeedsCairo whether the ScaledFont created need a cairo scaled font + * @return Referenced NativeFontResourceGDI or nullptr if invalid. + */ + static already_AddRefed<NativeFontResourceGDI> + Create(uint8_t *aFontData, uint32_t aDataLength, bool aNeedsCairo); + + ~NativeFontResourceGDI(); + + already_AddRefed<ScaledFont> + CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final; + +private: + NativeFontResourceGDI(HANDLE aFontResourceHandle, + bool aNeedsCairo) + : mFontResourceHandle(aFontResourceHandle) + , mNeedsCairo(aNeedsCairo) + {} + + HANDLE mFontResourceHandle; + bool mNeedsCairo; +}; + +} // gfx +} // mozilla + +#endif // mozilla_gfx_NativeFontResourceGDI_h diff --git a/gfx/2d/NativeFontResourceMac.cpp b/gfx/2d/NativeFontResourceMac.cpp new file mode 100644 index 000000000..aaf6db181 --- /dev/null +++ b/gfx/2d/NativeFontResourceMac.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeFontResourceMac.h" +#include "Types.h" + +#include "mozilla/RefPtr.h" + +#ifdef MOZ_WIDGET_UIKIT +#include <CoreFoundation/CoreFoundation.h> +#endif + +namespace mozilla { +namespace gfx { + +/* static */ +already_AddRefed<NativeFontResourceMac> +NativeFontResourceMac::Create(uint8_t *aFontData, uint32_t aDataLength) +{ + // copy font data + CFDataRef data = CFDataCreate(kCFAllocatorDefault, aFontData, aDataLength); + if (!data) { + return nullptr; + } + + // create a provider + CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); + + // release our reference to the CFData, provider keeps it alive + CFRelease(data); + + // create the font object + CGFontRef fontRef = CGFontCreateWithDataProvider(provider); + + // release our reference, font will keep it alive as long as needed + CGDataProviderRelease(provider); + + if (!fontRef) { + return nullptr; + } + + // passes ownership of fontRef to the NativeFontResourceMac instance + RefPtr<NativeFontResourceMac> fontResource = + new NativeFontResourceMac(fontRef); + + return fontResource.forget(); +} + +already_AddRefed<ScaledFont> +NativeFontResourceMac::CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) +{ + RefPtr<ScaledFontBase> scaledFont = new ScaledFontMac(mFontRef, aGlyphSize); + + if (!scaledFont->PopulateCairoScaledFont()) { + gfxWarning() << "Unable to create cairo scaled Mac font."; + return nullptr; + } + + return scaledFont.forget(); +} + +} // gfx +} // mozilla diff --git a/gfx/2d/NativeFontResourceMac.h b/gfx/2d/NativeFontResourceMac.h new file mode 100644 index 000000000..47ca92e68 --- /dev/null +++ b/gfx/2d/NativeFontResourceMac.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceMac_h +#define mozilla_gfx_NativeFontResourceMac_h + +#include "2D.h" +#include "mozilla/AlreadyAddRefed.h" +#include "ScaledFontMac.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceMac final : public NativeFontResource +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceMac) + + static already_AddRefed<NativeFontResourceMac> + Create(uint8_t *aFontData, uint32_t aDataLength); + + already_AddRefed<ScaledFont> + CreateScaledFont(uint32_t aIndex, Float aGlyphSize, + const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final; + + ~NativeFontResourceMac() + { + CFRelease(mFontRef); + } + +private: + explicit NativeFontResourceMac(CGFontRef aFontRef) : mFontRef(aFontRef) {} + + CGFontRef mFontRef; +}; + +} // gfx +} // mozilla + +#endif // mozilla_gfx_NativeFontResourceMac_h diff --git a/gfx/2d/NumericTools.h b/gfx/2d/NumericTools.h new file mode 100644 index 000000000..03aa7a8e2 --- /dev/null +++ b/gfx/2d/NumericTools.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_NUMERICTOOLS_H_ +#define MOZILLA_GFX_NUMERICTOOLS_H_ + +namespace mozilla { + +// XXX - Move these into mfbt/MathAlgorithms.h? + +// Returns the largest multiple of aMultiplied that's <= x. +// Same as int32_t(floor(double(x) / aMultiplier)) * aMultiplier, +// but faster. +inline int32_t +RoundDownToMultiple(int32_t x, int32_t aMultiplier) +{ + // We don't use float division + floor because that's hard for the compiler + // to optimize. + int mod = x % aMultiplier; + if (x > 0) { + return x - mod; + } + return mod ? x - aMultiplier - mod : x; +} + +// Returns the smallest multiple of aMultiplied that's >= x. +// Same as int32_t(ceil(double(x) / aMultiplier)) * aMultiplier, +// but faster. +inline int32_t +RoundUpToMultiple(int32_t x, int32_t aMultiplier) +{ + int mod = x % aMultiplier; + if (x > 0) { + return mod ? x + aMultiplier - mod : x; + } + return x - mod; +} + +} // namespace mozilla + +#endif /* MOZILLA_GFX_NUMERICTOOLS_H_ */ diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp new file mode 100644 index 000000000..f863e12ec --- /dev/null +++ b/gfx/2d/Path.cpp @@ -0,0 +1,550 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "PathAnalysis.h" +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +static double CubicRoot(double aValue) { + if (aValue < 0.0) { + return -CubicRoot(-aValue); + } + else { + return pow(aValue, 1.0 / 3.0); + } +} + +struct PointD : public BasePoint<double, PointD> { + typedef BasePoint<double, PointD> Super; + + PointD() : Super() {} + PointD(double aX, double aY) : Super(aX, aY) {} + MOZ_IMPLICIT PointD(const Point& aPoint) : Super(aPoint.x, aPoint.y) {} + + Point ToPoint() const { + return Point(static_cast<Float>(x), static_cast<Float>(y)); + } +}; + +struct BezierControlPoints +{ + BezierControlPoints() {} + BezierControlPoints(const PointD &aCP1, const PointD &aCP2, + const PointD &aCP3, const PointD &aCP4) + : mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4) + { + } + + PointD mCP1, mCP2, mCP3, mCP4; +}; + +void +FlattenBezier(const BezierControlPoints &aPoints, + PathSink *aSink, double aTolerance); + + +Path::Path() +{ +} + +Path::~Path() +{ +} + +Float +Path::ComputeLength() +{ + EnsureFlattenedPath(); + return mFlattenedPath->ComputeLength(); +} + +Point +Path::ComputePointAtLength(Float aLength, Point* aTangent) +{ + EnsureFlattenedPath(); + return mFlattenedPath->ComputePointAtLength(aLength, aTangent); +} + +void +Path::EnsureFlattenedPath() +{ + if (!mFlattenedPath) { + mFlattenedPath = new FlattenedPath(); + StreamToSink(mFlattenedPath); + } +} + +// This is the maximum deviation we allow (with an additional ~20% margin of +// error) of the approximation from the actual Bezier curve. +const Float kFlatteningTolerance = 0.0001f; + +void +FlattenedPath::MoveTo(const Point &aPoint) +{ + MOZ_ASSERT(!mCalculatedLength); + FlatPathOp op; + op.mType = FlatPathOp::OP_MOVETO; + op.mPoint = aPoint; + mPathOps.push_back(op); + + mLastMove = aPoint; +} + +void +FlattenedPath::LineTo(const Point &aPoint) +{ + MOZ_ASSERT(!mCalculatedLength); + FlatPathOp op; + op.mType = FlatPathOp::OP_LINETO; + op.mPoint = aPoint; + mPathOps.push_back(op); +} + +void +FlattenedPath::BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3) +{ + MOZ_ASSERT(!mCalculatedLength); + FlattenBezier(BezierControlPoints(CurrentPoint(), aCP1, aCP2, aCP3), this, kFlatteningTolerance); +} + +void +FlattenedPath::QuadraticBezierTo(const Point &aCP1, + const Point &aCP2) +{ + MOZ_ASSERT(!mCalculatedLength); + // We need to elevate the degree of this quadratic B�zier to cubic, so we're + // going to add an intermediate control point, and recompute control point 1. + // The first and last control points remain the same. + // This formula can be found on http://fontforge.sourceforge.net/bezier.html + Point CP0 = CurrentPoint(); + Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; + Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; + Point CP3 = aCP2; + + BezierTo(CP1, CP2, CP3); +} + +void +FlattenedPath::Close() +{ + MOZ_ASSERT(!mCalculatedLength); + LineTo(mLastMove); +} + +void +FlattenedPath::Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) +{ + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise); +} + +Float +FlattenedPath::ComputeLength() +{ + if (!mCalculatedLength) { + Point currentPoint; + + for (uint32_t i = 0; i < mPathOps.size(); i++) { + if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { + currentPoint = mPathOps[i].mPoint; + } else { + mCachedLength += Distance(currentPoint, mPathOps[i].mPoint); + currentPoint = mPathOps[i].mPoint; + } + } + + mCalculatedLength = true; + } + + return mCachedLength; +} + +Point +FlattenedPath::ComputePointAtLength(Float aLength, Point *aTangent) +{ + // We track the last point that -wasn't- in the same place as the current + // point so if we pass the edge of the path with a bunch of zero length + // paths we still get the correct tangent vector. + Point lastPointSinceMove; + Point currentPoint; + for (uint32_t i = 0; i < mPathOps.size(); i++) { + if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { + if (Distance(currentPoint, mPathOps[i].mPoint)) { + lastPointSinceMove = currentPoint; + } + currentPoint = mPathOps[i].mPoint; + } else { + Float segmentLength = Distance(currentPoint, mPathOps[i].mPoint); + + if (segmentLength) { + lastPointSinceMove = currentPoint; + if (segmentLength > aLength) { + Point currentVector = mPathOps[i].mPoint - currentPoint; + Point tangent = currentVector / segmentLength; + if (aTangent) { + *aTangent = tangent; + } + return currentPoint + tangent * aLength; + } + } + + aLength -= segmentLength; + currentPoint = mPathOps[i].mPoint; + } + } + + Point currentVector = currentPoint - lastPointSinceMove; + if (aTangent) { + if (hypotf(currentVector.x, currentVector.y)) { + *aTangent = currentVector / hypotf(currentVector.x, currentVector.y); + } else { + *aTangent = Point(); + } + } + return currentPoint; +} + +// This function explicitly permits aControlPoints to refer to the same object +// as either of the other arguments. +static void +SplitBezier(const BezierControlPoints &aControlPoints, + BezierControlPoints *aFirstSegmentControlPoints, + BezierControlPoints *aSecondSegmentControlPoints, + double t) +{ + MOZ_ASSERT(aSecondSegmentControlPoints); + + *aSecondSegmentControlPoints = aControlPoints; + + PointD cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; + PointD cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; + PointD cp1aa = cp1a + (cp2a - cp1a) * t; + PointD cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; + PointD cp2aa = cp2a + (cp3a - cp2a) * t; + PointD cp1aaa = cp1aa + (cp2aa - cp1aa) * t; + aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4; + + if(aFirstSegmentControlPoints) { + aFirstSegmentControlPoints->mCP1 = aControlPoints.mCP1; + aFirstSegmentControlPoints->mCP2 = cp1a; + aFirstSegmentControlPoints->mCP3 = cp1aa; + aFirstSegmentControlPoints->mCP4 = cp1aaa; + } + aSecondSegmentControlPoints->mCP1 = cp1aaa; + aSecondSegmentControlPoints->mCP2 = cp2aa; + aSecondSegmentControlPoints->mCP3 = cp3a; +} + +static void +FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints, + PathSink *aSink, + double aTolerance) +{ + /* The algorithm implemented here is based on: + * http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf + * + * The basic premise is that for a small t the third order term in the + * equation of a cubic bezier curve is insignificantly small. This can + * then be approximated by a quadratic equation for which the maximum + * difference from a linear approximation can be much more easily determined. + */ + BezierControlPoints currentCP = aControlPoints; + + double t = 0; + while (t < 1.0) { + PointD cp21 = currentCP.mCP2 - currentCP.mCP1; + PointD cp31 = currentCP.mCP3 - currentCP.mCP1; + + /* To remove divisions and check for divide-by-zero, this is optimized from: + * Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y); + * t = 2 * Float(sqrt(aTolerance / (3. * std::abs(s3)))); + */ + double cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x; + double h = hypot(cp21.x, cp21.y); + if (cp21x31 * h == 0) { + break; + } + + double s3inv = h / cp21x31; + t = 2 * sqrt(aTolerance * std::abs(s3inv) / 3.); + if (t >= 1.0) { + break; + } + + SplitBezier(currentCP, nullptr, ¤tCP, t); + + aSink->LineTo(currentCP.mCP1.ToPoint()); + } + + aSink->LineTo(currentCP.mCP4.ToPoint()); +} + +static inline void +FindInflectionApproximationRange(BezierControlPoints aControlPoints, + double *aMin, double *aMax, double aT, + double aTolerance) +{ + SplitBezier(aControlPoints, nullptr, &aControlPoints, aT); + + PointD cp21 = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD cp41 = aControlPoints.mCP4 - aControlPoints.mCP1; + + if (cp21.x == 0. && cp21.y == 0.) { + // In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = cp41.x - cp41.y. + + // Use the absolute value so that Min and Max will correspond with the + // minimum and maximum of the range. + *aMin = aT - CubicRoot(std::abs(aTolerance / (cp41.x - cp41.y))); + *aMax = aT + CubicRoot(std::abs(aTolerance / (cp41.x - cp41.y))); + return; + } + + double s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypot(cp21.x, cp21.y); + + if (s3 == 0) { + // This means within the precision we have it can be approximated + // infinitely by a linear segment. Deal with this by specifying the + // approximation range as extending beyond the entire curve. + *aMin = -1.0; + *aMax = 2.0; + return; + } + + double tf = CubicRoot(std::abs(aTolerance / s3)); + + *aMin = aT - tf * (1 - aT); + *aMax = aT + tf * (1 - aT); +} + +/* Find the inflection points of a bezier curve. Will return false if the + * curve is degenerate in such a way that it is best approximated by a straight + * line. + * + * The below algorithm was written by Jeff Muizelaar <jmuizelaar@mozilla.com>, explanation follows: + * + * The lower inflection point is returned in aT1, the higher one in aT2. In the + * case of a single inflection point this will be in aT1. + * + * The method is inspired by the algorithm in "analysis of in?ection points for planar cubic bezier curve" + * + * Here are some differences between this algorithm and versions discussed elsewhere in the literature: + * + * zhang et. al compute a0, d0 and e0 incrementally using the follow formula: + * + * Point a0 = CP2 - CP1 + * Point a1 = CP3 - CP2 + * Point a2 = CP4 - CP1 + * + * Point d0 = a1 - a0 + * Point d1 = a2 - a1 + + * Point e0 = d1 - d0 + * + * this avoids any multiplications and may or may not be faster than the approach take below. + * + * "fast, precise flattening of cubic bezier path and ofset curves" by hain et. al + * Point a = CP1 + 3 * CP2 - 3 * CP3 + CP4 + * Point b = 3 * CP1 - 6 * CP2 + 3 * CP3 + * Point c = -3 * CP1 + 3 * CP2 + * Point d = CP1 + * the a, b, c, d can be expressed in terms of a0, d0 and e0 defined above as: + * c = 3 * a0 + * b = 3 * d0 + * a = e0 + * + * + * a = 3a = a.y * b.x - a.x * b.y + * b = 3b = a.y * c.x - a.x * c.y + * c = 9c = b.y * c.x - b.x * c.y + * + * The additional multiples of 3 cancel each other out as show below: + * + * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a) + * x = (-3 * b + sqrt(3 * b * 3 * b - 4 * a * 3 * 9 * c / 3)) / (2 * 3 * a) + * x = 3 * (-b + sqrt(b * b - 4 * a * c)) / (2 * 3 * a) + * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a) + * + * I haven't looked into whether the formulation of the quadratic formula in + * hain has any numerical advantages over the one used below. + */ +static inline void +FindInflectionPoints(const BezierControlPoints &aControlPoints, + double *aT1, double *aT2, uint32_t *aCount) +{ + // Find inflection points. + // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation + // of this approach. + PointD A = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; + PointD C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; + + double a = B.x * C.y - B.y * C.x; + double b = A.x * C.y - A.y * C.x; + double c = A.x * B.y - A.y * B.x; + + if (a == 0) { + // Not a quadratic equation. + if (b == 0) { + // Instead of a linear acceleration change we have a constant + // acceleration change. This means the equation has no solution + // and there are no inflection points, unless the constant is 0. + // In that case the curve is a straight line, essentially that means + // the easiest way to deal with is is by saying there's an inflection + // point at t == 0. The inflection point approximation range found will + // automatically extend into infinity. + if (c == 0) { + *aCount = 1; + *aT1 = 0; + return; + } + *aCount = 0; + return; + } + *aT1 = -c / b; + *aCount = 1; + return; + } else { + double discriminant = b * b - 4 * a * c; + + if (discriminant < 0) { + // No inflection points. + *aCount = 0; + } else if (discriminant == 0) { + *aCount = 1; + *aT1 = -b / (2 * a); + } else { + /* Use the following formula for computing the roots: + * + * q = -1/2 * (b + sign(b) * sqrt(b^2 - 4ac)) + * t1 = q / a + * t2 = c / q + */ + double q = sqrt(discriminant); + if (b < 0) { + q = b - q; + } else { + q = b + q; + } + q *= -1./2; + + *aT1 = q / a; + *aT2 = c / q; + if (*aT1 > *aT2) { + std::swap(*aT1, *aT2); + } + *aCount = 2; + } + } + + return; +} + +void +FlattenBezier(const BezierControlPoints &aControlPoints, + PathSink *aSink, double aTolerance) +{ + double t1; + double t2; + uint32_t count; + + FindInflectionPoints(aControlPoints, &t1, &t2, &count); + + // Check that at least one of the inflection points is inside [0..1] + if (count == 0 || ((t1 < 0.0 || t1 >= 1.0) && (count == 1 || (t2 < 0.0 || t2 >= 1.0))) ) { + FlattenBezierCurveSegment(aControlPoints, aSink, aTolerance); + return; + } + + double t1min = t1, t1max = t1, t2min = t2, t2max = t2; + + BezierControlPoints remainingCP = aControlPoints; + + // For both inflection points, calulate the range where they can be linearly + // approximated if they are positioned within [0,1] + if (count > 0 && t1 >= 0 && t1 < 1.0) { + FindInflectionApproximationRange(aControlPoints, &t1min, &t1max, t1, aTolerance); + } + if (count > 1 && t2 >= 0 && t2 < 1.0) { + FindInflectionApproximationRange(aControlPoints, &t2min, &t2max, t2, aTolerance); + } + BezierControlPoints nextCPs = aControlPoints; + BezierControlPoints prevCPs; + + // Process ranges. [t1min, t1max] and [t2min, t2max] are approximated by line + // segments. + if (count == 1 && t1min <= 0 && t1max >= 1.0) { + // The whole range can be approximated by a line segment. + aSink->LineTo(aControlPoints.mCP4.ToPoint()); + return; + } + + if (t1min > 0) { + // Flatten the Bezier up until the first inflection point's approximation + // point. + SplitBezier(aControlPoints, &prevCPs, + &remainingCP, t1min); + FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); + } + if (t1max >= 0 && t1max < 1.0 && (count == 1 || t2min > t1max)) { + // The second inflection point's approximation range begins after the end + // of the first, approximate the first inflection point by a line and + // subsequently flatten up until the end or the next inflection point. + SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); + + aSink->LineTo(nextCPs.mCP1.ToPoint()); + + if (count == 1 || (count > 1 && t2min >= 1.0)) { + // No more inflection points to deal with, flatten the rest of the curve. + FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); + } + } else if (count > 1 && t2min > 1.0) { + // We've already concluded t2min <= t1max, so if this is true the + // approximation range for the first inflection point runs past the + // end of the curve, draw a line to the end and we're done. + aSink->LineTo(aControlPoints.mCP4.ToPoint()); + return; + } + + if (count > 1 && t2min < 1.0 && t2max > 0) { + if (t2min > 0 && t2min < t1max) { + // In this case the t2 approximation range starts inside the t1 + // approximation range. + SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); + aSink->LineTo(nextCPs.mCP1.ToPoint()); + } else if (t2min > 0 && t1max > 0) { + SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); + + // Find a control points describing the portion of the curve between t1max and t2min. + double t2mina = (t2min - t1max) / (1 - t1max); + SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina); + FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); + } else if (t2min > 0) { + // We have nothing interesting before t2min, find that bit and flatten it. + SplitBezier(aControlPoints, &prevCPs, &nextCPs, t2min); + FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); + } + if (t2max < 1.0) { + // Flatten the portion of the curve after t2max + SplitBezier(aControlPoints, nullptr, &nextCPs, t2max); + + // Draw a line to the start, this is the approximation between t2min and + // t2max. + aSink->LineTo(nextCPs.mCP1.ToPoint()); + FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); + } else { + // Our approximation range extends beyond the end of the curve. + aSink->LineTo(aControlPoints.mCP4.ToPoint()); + return; + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathAnalysis.h b/gfx/2d/PathAnalysis.h new file mode 100644 index 000000000..5a38ed161 --- /dev/null +++ b/gfx/2d/PathAnalysis.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +struct FlatPathOp +{ + enum OpType { + OP_MOVETO, + OP_LINETO, + }; + + OpType mType; + Point mPoint; +}; + +class FlattenedPath : public PathSink +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FlattenedPath) + FlattenedPath() : mCachedLength(0) + , mCalculatedLength(false) + { + } + + virtual void MoveTo(const Point &aPoint); + virtual void LineTo(const Point &aPoint); + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + virtual void Close(); + virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false); + + virtual Point CurrentPoint() const { return mPathOps.empty() ? Point() : mPathOps[mPathOps.size() - 1].mPoint; } + + Float ComputeLength(); + Point ComputePointAtLength(Float aLength, Point *aTangent); + +private: + Float mCachedLength; + bool mCalculatedLength; + Point mLastMove; + + std::vector<FlatPathOp> mPathOps; +}; + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathCG.cpp b/gfx/2d/PathCG.cpp new file mode 100644 index 000000000..4d70b2607 --- /dev/null +++ b/gfx/2d/PathCG.cpp @@ -0,0 +1,435 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathCG.h" +#include <math.h> +#include "Logging.h" +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +static inline Rect +CGRectToRect(CGRect rect) +{ + return Rect(rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height); +} + +static inline Point +CGPointToPoint(CGPoint point) +{ + return Point(point.x, point.y); +} + +static inline void +SetStrokeOptions(CGContextRef cg, const StrokeOptions &aStrokeOptions) +{ + switch (aStrokeOptions.mLineCap) + { + case CapStyle::BUTT: + CGContextSetLineCap(cg, kCGLineCapButt); + break; + case CapStyle::ROUND: + CGContextSetLineCap(cg, kCGLineCapRound); + break; + case CapStyle::SQUARE: + CGContextSetLineCap(cg, kCGLineCapSquare); + break; + } + + switch (aStrokeOptions.mLineJoin) + { + case JoinStyle::BEVEL: + CGContextSetLineJoin(cg, kCGLineJoinBevel); + break; + case JoinStyle::ROUND: + CGContextSetLineJoin(cg, kCGLineJoinRound); + break; + case JoinStyle::MITER: + case JoinStyle::MITER_OR_BEVEL: + CGContextSetLineJoin(cg, kCGLineJoinMiter); + break; + } + + CGContextSetLineWidth(cg, aStrokeOptions.mLineWidth); + CGContextSetMiterLimit(cg, aStrokeOptions.mMiterLimit); + + // XXX: rename mDashLength to dashLength + if (aStrokeOptions.mDashLength > 0) { + // we use a regular array instead of a std::vector here because we don't want to leak the <vector> include + CGFloat *dashes = new CGFloat[aStrokeOptions.mDashLength]; + for (size_t i=0; i<aStrokeOptions.mDashLength; i++) { + dashes[i] = aStrokeOptions.mDashPattern[i]; + } + CGContextSetLineDash(cg, aStrokeOptions.mDashOffset, dashes, aStrokeOptions.mDashLength); + delete[] dashes; + } +} + +static inline CGAffineTransform +GfxMatrixToCGAffineTransform(const Matrix &m) +{ + CGAffineTransform t; + t.a = m._11; + t.b = m._12; + t.c = m._21; + t.d = m._22; + t.tx = m._31; + t.ty = m._32; + return t; +} + +PathBuilderCG::~PathBuilderCG() +{ + CGPathRelease(mCGPath); +} + +void +PathBuilderCG::MoveTo(const Point &aPoint) +{ + if (!aPoint.IsFinite()) { + return; + } + CGPathMoveToPoint(mCGPath, nullptr, aPoint.x, aPoint.y); +} + +void +PathBuilderCG::LineTo(const Point &aPoint) +{ + if (!aPoint.IsFinite()) { + return; + } + + if (CGPathIsEmpty(mCGPath)) + MoveTo(aPoint); + else + CGPathAddLineToPoint(mCGPath, nullptr, aPoint.x, aPoint.y); +} + +void +PathBuilderCG::BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3) +{ + if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) { + return; + } + + if (CGPathIsEmpty(mCGPath)) + MoveTo(aCP1); + CGPathAddCurveToPoint(mCGPath, nullptr, + aCP1.x, aCP1.y, + aCP2.x, aCP2.y, + aCP3.x, aCP3.y); + +} + +void +PathBuilderCG::QuadraticBezierTo(const Point &aCP1, + const Point &aCP2) +{ + if (!aCP1.IsFinite() || !aCP2.IsFinite()) { + return; + } + + if (CGPathIsEmpty(mCGPath)) + MoveTo(aCP1); + CGPathAddQuadCurveToPoint(mCGPath, nullptr, + aCP1.x, aCP1.y, + aCP2.x, aCP2.y); +} + +void +PathBuilderCG::Close() +{ + if (!CGPathIsEmpty(mCGPath)) + CGPathCloseSubpath(mCGPath); +} + +void +PathBuilderCG::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise) +{ + if (!aOrigin.IsFinite() || !IsFinite(aRadius) || + !IsFinite(aStartAngle) || !IsFinite(aEndAngle)) { + return; + } + + // Disabled for now due to a CG bug when using CGPathAddArc with stroke + // dashing and rotation transforms that are multiples of 90 degrees. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=949661#c8 +#if 0 + // Core Graphic's initial coordinate system is y-axis up, whereas Moz2D's is + // y-axis down. Core Graphics therefore considers "clockwise" to mean "sweep + // in the direction of decreasing angle" whereas Moz2D considers it to mean + // "sweep in the direction of increasing angle". In other words if this + // Moz2D method is instructed to sweep anti-clockwise we need to tell + // CGPathAddArc to sweep clockwise, and vice versa. Hence why we pass the + // value of aAntiClockwise directly to CGPathAddArc's "clockwise" bool + // parameter. + CGPathAddArc(mCGPath, nullptr, + aOrigin.x, aOrigin.y, + aRadius, + aStartAngle, + aEndAngle, + aAntiClockwise); +#endif + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, + aAntiClockwise); +} + +Point +PathBuilderCG::CurrentPoint() const +{ + Point ret; + if (!CGPathIsEmpty(mCGPath)) { + CGPoint pt = CGPathGetCurrentPoint(mCGPath); + ret.MoveTo(pt.x, pt.y); + } + return ret; +} + +void +PathBuilderCG::EnsureActive(const Point &aPoint) +{ +} + +already_AddRefed<Path> +PathBuilderCG::Finish() +{ + return MakeAndAddRef<PathCG>(mCGPath, mFillRule); +} + +already_AddRefed<PathBuilder> +PathCG::CopyToBuilder(FillRule aFillRule) const +{ + CGMutablePathRef path = CGPathCreateMutableCopy(mPath); + return MakeAndAddRef<PathBuilderCG>(path, aFillRule); +} + + + +already_AddRefed<PathBuilder> +PathCG::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const +{ + // 10.7 adds CGPathCreateMutableCopyByTransformingPath it might be faster than doing + // this by hand + + struct TransformApplier { + CGMutablePathRef path; + CGAffineTransform transform; + static void + TranformCGPathApplierFunc(void *vinfo, const CGPathElement *element) + { + TransformApplier *info = reinterpret_cast<TransformApplier*>(vinfo); + switch (element->type) { + case kCGPathElementMoveToPoint: + { + CGPoint pt = element->points[0]; + CGPathMoveToPoint(info->path, &info->transform, pt.x, pt.y); + break; + } + case kCGPathElementAddLineToPoint: + { + CGPoint pt = element->points[0]; + CGPathAddLineToPoint(info->path, &info->transform, pt.x, pt.y); + break; + } + case kCGPathElementAddQuadCurveToPoint: + { + CGPoint cpt = element->points[0]; + CGPoint pt = element->points[1]; + CGPathAddQuadCurveToPoint(info->path, &info->transform, cpt.x, cpt.y, pt.x, pt.y); + break; + } + case kCGPathElementAddCurveToPoint: + { + CGPoint cpt1 = element->points[0]; + CGPoint cpt2 = element->points[1]; + CGPoint pt = element->points[2]; + CGPathAddCurveToPoint(info->path, &info->transform, cpt1.x, cpt1.y, cpt2.x, cpt2.y, pt.x, pt.y); + break; + } + case kCGPathElementCloseSubpath: + { + CGPathCloseSubpath(info->path); + break; + } + } + } + }; + + TransformApplier ta; + ta.path = CGPathCreateMutable(); + ta.transform = GfxMatrixToCGAffineTransform(aTransform); + + CGPathApply(mPath, &ta, TransformApplier::TranformCGPathApplierFunc); + return MakeAndAddRef<PathBuilderCG>(ta.path, aFillRule); +} + +static void +StreamPathToSinkApplierFunc(void *vinfo, const CGPathElement *element) +{ + PathSink *sink = reinterpret_cast<PathSink*>(vinfo); + switch (element->type) { + case kCGPathElementMoveToPoint: + { + CGPoint pt = element->points[0]; + sink->MoveTo(CGPointToPoint(pt)); + break; + } + case kCGPathElementAddLineToPoint: + { + CGPoint pt = element->points[0]; + sink->LineTo(CGPointToPoint(pt)); + break; + } + case kCGPathElementAddQuadCurveToPoint: + { + CGPoint cpt = element->points[0]; + CGPoint pt = element->points[1]; + sink->QuadraticBezierTo(CGPointToPoint(cpt), + CGPointToPoint(pt)); + break; + } + case kCGPathElementAddCurveToPoint: + { + CGPoint cpt1 = element->points[0]; + CGPoint cpt2 = element->points[1]; + CGPoint pt = element->points[2]; + sink->BezierTo(CGPointToPoint(cpt1), + CGPointToPoint(cpt2), + CGPointToPoint(pt)); + break; + } + case kCGPathElementCloseSubpath: + { + sink->Close(); + break; + } + } +} + +void +PathCG::StreamToSink(PathSink *aSink) const +{ + CGPathApply(mPath, aSink, StreamPathToSinkApplierFunc); +} + +bool +PathCG::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const +{ + Matrix inverse = aTransform; + inverse.Invert(); + Point transformedPoint = inverse.TransformPoint(aPoint); + // We could probably drop the input transform and just transform the point at the caller? + CGPoint point = {transformedPoint.x, transformedPoint.y}; + + // The transform parameter of CGPathContainsPoint doesn't seem to work properly on OS X 10.5 + // so we transform aPoint ourselves. + return CGPathContainsPoint(mPath, nullptr, point, mFillRule == FillRule::FILL_EVEN_ODD); +} + +static size_t +PutBytesNull(void *info, const void *buffer, size_t count) +{ + return count; +} + +/* The idea of a scratch context comes from WebKit */ +static CGContextRef +CreateScratchContext() +{ + CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; + CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); + CGContextRef cg = CGPDFContextCreate(consumer, nullptr, nullptr); + CGDataConsumerRelease(consumer); + return cg; +} + +static CGContextRef +ScratchContext() +{ + static CGContextRef cg = CreateScratchContext(); + return cg; +} + +bool +PathCG::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const +{ + Matrix inverse = aTransform; + inverse.Invert(); + Point transformedPoint = inverse.TransformPoint(aPoint); + // We could probably drop the input transform and just transform the point at the caller? + CGPoint point = {transformedPoint.x, transformedPoint.y}; + + CGContextRef cg = ScratchContext(); + + CGContextSaveGState(cg); + + CGContextBeginPath(cg); + CGContextAddPath(cg, mPath); + + SetStrokeOptions(cg, aStrokeOptions); + + CGContextReplacePathWithStrokedPath(cg); + CGContextRestoreGState(cg); + + CGPathRef sPath = CGContextCopyPath(cg); + bool inStroke = CGPathContainsPoint(sPath, nullptr, point, false); + CGPathRelease(sPath); + + return inStroke; +} + +//XXX: what should these functions return for an empty path? +// currently they return CGRectNull {inf,inf, 0, 0} +Rect +PathCG::GetBounds(const Matrix &aTransform) const +{ + //XXX: are these bounds tight enough + Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath)); + + //XXX: currently this returns the bounds of the transformed bounds + // this is strictly looser than the bounds of the transformed path + return aTransform.TransformBounds(bounds); +} + +Rect +PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform) const +{ + // 10.7 has CGPathCreateCopyByStrokingPath which we could use + // instead of this scratch context business + CGContextRef cg = ScratchContext(); + + CGContextSaveGState(cg); + + CGContextBeginPath(cg); + CGContextAddPath(cg, mPath); + + SetStrokeOptions(cg, aStrokeOptions); + + CGContextReplacePathWithStrokedPath(cg); + Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg)); + + CGContextRestoreGState(cg); + + if (!bounds.IsFinite()) { + return Rect(); + } + + return aTransform.TransformBounds(bounds); +} + + +} // namespace gfx + +} // namespace mozilla diff --git a/gfx/2d/PathCG.h b/gfx/2d/PathCG.h new file mode 100644 index 000000000..db609cb57 --- /dev/null +++ b/gfx/2d/PathCG.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHCG_H_ +#define MOZILLA_GFX_PATHCG_H_ + +#ifdef MOZ_WIDGET_COCOA +#include <ApplicationServices/ApplicationServices.h> +#else +#include <CoreGraphics/CoreGraphics.h> +#endif + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class PathCG; + +class PathBuilderCG : public PathBuilder +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCG) + // absorbs a reference of aPath + PathBuilderCG(CGMutablePathRef aPath, FillRule aFillRule) + : mFillRule(aFillRule) + { + mCGPath = aPath; + } + + explicit PathBuilderCG(FillRule aFillRule) + : mFillRule(aFillRule) + { + mCGPath = CGPathCreateMutable(); + } + + virtual ~PathBuilderCG(); + + virtual void MoveTo(const Point &aPoint); + virtual void LineTo(const Point &aPoint); + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + virtual void Close(); + virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise = false); + virtual Point CurrentPoint() const; + + virtual already_AddRefed<Path> Finish(); + + virtual BackendType GetBackendType() const { return BackendType::SKIA; } + +private: + friend class PathCG; + friend class ScaledFontMac; + + void EnsureActive(const Point &aPoint); + + CGMutablePathRef mCGPath; + Point mCurrentPoint; + Point mBeginPoint; + FillRule mFillRule; +}; + +class PathCG : public Path +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCG) + PathCG(CGMutablePathRef aPath, FillRule aFillRule) + : mPath(aPath) + , mFillRule(aFillRule) + { + CGPathRetain(mPath); + } + virtual ~PathCG() { CGPathRelease(mPath); } + + // Paths will always return BackendType::COREGRAPHICS, but note that they + // are compatible with BackendType::COREGRAPHICS_ACCELERATED backend. + virtual BackendType GetBackendType() const { return BackendType::SKIA; } + + virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const; + virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform, + FillRule aFillRule) const; + + virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const; + virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const; + virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const; + virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform = Matrix()) const; + + virtual void StreamToSink(PathSink *aSink) const; + + virtual FillRule GetFillRule() const { return mFillRule; } + + CGMutablePathRef GetPath() const { return mPath; } + +private: + friend class DrawTargetCG; + + CGMutablePathRef mPath; + Point mEndPoint; + FillRule mFillRule; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/PathCairo.cpp b/gfx/2d/PathCairo.cpp new file mode 100644 index 000000000..45bc201cb --- /dev/null +++ b/gfx/2d/PathCairo.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathCairo.h" +#include <math.h> +#include "DrawTargetCairo.h" +#include "Logging.h" +#include "PathHelpers.h" +#include "HelpersCairo.h" + +namespace mozilla { +namespace gfx { + +PathBuilderCairo::PathBuilderCairo(FillRule aFillRule) + : mFillRule(aFillRule) +{ +} + +void +PathBuilderCairo::MoveTo(const Point &aPoint) +{ + cairo_path_data_t data; + data.header.type = CAIRO_PATH_MOVE_TO; + data.header.length = 2; + mPathData.push_back(data); + data.point.x = aPoint.x; + data.point.y = aPoint.y; + mPathData.push_back(data); + + mBeginPoint = mCurrentPoint = aPoint; +} + +void +PathBuilderCairo::LineTo(const Point &aPoint) +{ + cairo_path_data_t data; + data.header.type = CAIRO_PATH_LINE_TO; + data.header.length = 2; + mPathData.push_back(data); + data.point.x = aPoint.x; + data.point.y = aPoint.y; + mPathData.push_back(data); + + mCurrentPoint = aPoint; +} + +void +PathBuilderCairo::BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3) +{ + cairo_path_data_t data; + data.header.type = CAIRO_PATH_CURVE_TO; + data.header.length = 4; + mPathData.push_back(data); + data.point.x = aCP1.x; + data.point.y = aCP1.y; + mPathData.push_back(data); + data.point.x = aCP2.x; + data.point.y = aCP2.y; + mPathData.push_back(data); + data.point.x = aCP3.x; + data.point.y = aCP3.y; + mPathData.push_back(data); + + mCurrentPoint = aCP3; +} + +void +PathBuilderCairo::QuadraticBezierTo(const Point &aCP1, + const Point &aCP2) +{ + // We need to elevate the degree of this quadratic Bézier to cubic, so we're + // going to add an intermediate control point, and recompute control point 1. + // The first and last control points remain the same. + // This formula can be found on http://fontforge.sourceforge.net/bezier.html + Point CP0 = CurrentPoint(); + Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; + Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; + Point CP3 = aCP2; + + cairo_path_data_t data; + data.header.type = CAIRO_PATH_CURVE_TO; + data.header.length = 4; + mPathData.push_back(data); + data.point.x = CP1.x; + data.point.y = CP1.y; + mPathData.push_back(data); + data.point.x = CP2.x; + data.point.y = CP2.y; + mPathData.push_back(data); + data.point.x = CP3.x; + data.point.y = CP3.y; + mPathData.push_back(data); + + mCurrentPoint = aCP2; +} + +void +PathBuilderCairo::Close() +{ + cairo_path_data_t data; + data.header.type = CAIRO_PATH_CLOSE_PATH; + data.header.length = 1; + mPathData.push_back(data); + + mCurrentPoint = mBeginPoint; +} + +void +PathBuilderCairo::Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) +{ + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise); +} + +Point +PathBuilderCairo::CurrentPoint() const +{ + return mCurrentPoint; +} + +already_AddRefed<Path> +PathBuilderCairo::Finish() +{ + return MakeAndAddRef<PathCairo>(mFillRule, mPathData, mCurrentPoint); +} + +PathCairo::PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t> &aPathData, const Point &aCurrentPoint) + : mFillRule(aFillRule) + , mContainingContext(nullptr) + , mCurrentPoint(aCurrentPoint) +{ + mPathData.swap(aPathData); +} + +PathCairo::PathCairo(cairo_t *aContext) + : mFillRule(FillRule::FILL_WINDING) + , mContainingContext(nullptr) +{ + cairo_path_t *path = cairo_copy_path(aContext); + + // XXX - mCurrentPoint is not properly set here, the same is true for the + // D2D Path code, we never require current point when hitting this codepath + // but this should be fixed. + for (int i = 0; i < path->num_data; i++) { + mPathData.push_back(path->data[i]); + } + + cairo_path_destroy(path); +} + +PathCairo::~PathCairo() +{ + if (mContainingContext) { + cairo_destroy(mContainingContext); + } +} + +already_AddRefed<PathBuilder> +PathCairo::CopyToBuilder(FillRule aFillRule) const +{ + RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); + + builder->mPathData = mPathData; + builder->mCurrentPoint = mCurrentPoint; + + return builder.forget(); +} + +already_AddRefed<PathBuilder> +PathCairo::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const +{ + RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); + + AppendPathToBuilder(builder, &aTransform); + builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint); + + return builder.forget(); +} + +bool +PathCairo::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const +{ + Matrix inverse = aTransform; + inverse.Invert(); + Point transformed = inverse.TransformPoint(aPoint); + + EnsureContainingContext(aTransform); + + return cairo_in_fill(mContainingContext, transformed.x, transformed.y); +} + +bool +PathCairo::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const +{ + Matrix inverse = aTransform; + inverse.Invert(); + Point transformed = inverse.TransformPoint(aPoint); + + EnsureContainingContext(aTransform); + + SetCairoStrokeOptions(mContainingContext, aStrokeOptions); + + return cairo_in_stroke(mContainingContext, transformed.x, transformed.y); +} + +Rect +PathCairo::GetBounds(const Matrix &aTransform) const +{ + EnsureContainingContext(aTransform); + + double x1, y1, x2, y2; + + cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2); + Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1)); + return aTransform.TransformBounds(bounds); +} + +Rect +PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform) const +{ + EnsureContainingContext(aTransform); + + double x1, y1, x2, y2; + + SetCairoStrokeOptions(mContainingContext, aStrokeOptions); + + cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2); + Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1)); + return aTransform.TransformBounds(bounds); +} + +void +PathCairo::StreamToSink(PathSink *aSink) const +{ + for (size_t i = 0; i < mPathData.size(); i++) { + switch (mPathData[i].header.type) { + case CAIRO_PATH_MOVE_TO: + i++; + aSink->MoveTo(Point(mPathData[i].point.x, mPathData[i].point.y)); + break; + case CAIRO_PATH_LINE_TO: + i++; + aSink->LineTo(Point(mPathData[i].point.x, mPathData[i].point.y)); + break; + case CAIRO_PATH_CURVE_TO: + aSink->BezierTo(Point(mPathData[i + 1].point.x, mPathData[i + 1].point.y), + Point(mPathData[i + 2].point.x, mPathData[i + 2].point.y), + Point(mPathData[i + 3].point.x, mPathData[i + 3].point.y)); + i += 3; + break; + case CAIRO_PATH_CLOSE_PATH: + aSink->Close(); + break; + default: + // Corrupt path data! + MOZ_ASSERT(false); + } + } +} + +void +PathCairo::EnsureContainingContext(const Matrix &aTransform) const +{ + if (mContainingContext) { + if (mContainingTransform.ExactlyEquals(aTransform)) { + return; + } + } else { + mContainingContext = cairo_create(DrawTargetCairo::GetDummySurface()); + } + + mContainingTransform = aTransform; + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mContainingTransform, mat); + cairo_set_matrix(mContainingContext, &mat); + + SetPathOnContext(mContainingContext); +} + +void +PathCairo::SetPathOnContext(cairo_t *aContext) const +{ + // Needs the correct fill rule set. + cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule)); + + cairo_new_path(aContext); + + if (mPathData.size()) { + cairo_path_t path; + path.data = const_cast<cairo_path_data_t*>(&mPathData.front()); + path.num_data = mPathData.size(); + path.status = CAIRO_STATUS_SUCCESS; + cairo_append_path(aContext, &path); + } +} + +void +PathCairo::AppendPathToBuilder(PathBuilderCairo *aBuilder, const Matrix *aTransform) const +{ + if (aTransform) { + size_t i = 0; + while (i < mPathData.size()) { + uint32_t pointCount = mPathData[i].header.length - 1; + aBuilder->mPathData.push_back(mPathData[i]); + i++; + for (uint32_t c = 0; c < pointCount; c++) { + cairo_path_data_t data; + Point newPoint = aTransform->TransformPoint(Point(mPathData[i].point.x, mPathData[i].point.y)); + data.point.x = newPoint.x; + data.point.y = newPoint.y; + aBuilder->mPathData.push_back(data); + i++; + } + } + } else { + for (size_t i = 0; i < mPathData.size(); i++) { + aBuilder->mPathData.push_back(mPathData[i]); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathCairo.h b/gfx/2d/PathCairo.h new file mode 100644 index 000000000..5addfb220 --- /dev/null +++ b/gfx/2d/PathCairo.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATH_CAIRO_H_ +#define MOZILLA_GFX_PATH_CAIRO_H_ + +#include "2D.h" +#include "cairo.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +class PathCairo; + +class PathBuilderCairo : public PathBuilder +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCairo) + explicit PathBuilderCairo(FillRule aFillRule); + + virtual void MoveTo(const Point &aPoint); + virtual void LineTo(const Point &aPoint); + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + virtual void Close(); + virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false); + virtual Point CurrentPoint() const; + virtual already_AddRefed<Path> Finish(); + + virtual BackendType GetBackendType() const { return BackendType::CAIRO; } + +private: // data + friend class PathCairo; + + FillRule mFillRule; + std::vector<cairo_path_data_t> mPathData; + // It's easiest to track this here, parsing the path data to find the current + // point is a little tricky. + Point mCurrentPoint; + Point mBeginPoint; +}; + +class PathCairo : public Path +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCairo) + PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t> &aPathData, const Point &aCurrentPoint); + explicit PathCairo(cairo_t *aContext); + ~PathCairo(); + + virtual BackendType GetBackendType() const { return BackendType::CAIRO; } + + virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const; + virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform, + FillRule aFillRule) const; + + virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const; + + virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const; + + virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const; + + virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform = Matrix()) const; + + virtual void StreamToSink(PathSink *aSink) const; + + virtual FillRule GetFillRule() const { return mFillRule; } + + void SetPathOnContext(cairo_t *aContext) const; + + void AppendPathToBuilder(PathBuilderCairo *aBuilder, const Matrix *aTransform = nullptr) const; +private: + void EnsureContainingContext(const Matrix &aTransform) const; + + FillRule mFillRule; + std::vector<cairo_path_data_t> mPathData; + mutable cairo_t *mContainingContext; + mutable Matrix mContainingTransform; + Point mCurrentPoint; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATH_CAIRO_H_ */ diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp new file mode 100644 index 000000000..b10e456e1 --- /dev/null +++ b/gfx/2d/PathD2D.cpp @@ -0,0 +1,523 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathD2D.h" +#include "HelpersD2D.h" +#include <math.h> +#include "DrawTargetD2D1.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows +// a geometry to be duplicated into a geometry sink, while removing the final +// figure end and thus allowing a figure that was implicitly closed to be +// continued. +class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink +{ +public: + OpeningGeometrySink(ID2D1SimplifiedGeometrySink *aSink) + : mSink(aSink) + , mNeedsFigureEnded(false) + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) + { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + // We ignore SetFillMode, the copier will decide. + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) + { EnsureFigureEnded(); return; } + STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) + { EnsureFigureEnded(); return mSink->BeginFigure(aPoint, aBegin); } + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) + { EnsureFigureEnded(); return mSink->AddLines(aLines, aCount); } + STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) + { EnsureFigureEnded(); return mSink->AddBeziers(aSegments, aCount); } + STDMETHOD(Close)() + { /* Should never be called! */ return S_OK; } + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) + { return mSink->SetSegmentFlags(aFlags); } + + // This function is special - it's the reason this class exists. + // It needs to intercept the very last endfigure. So that a user can + // continue writing to this sink as if they never stopped. + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) + { + if (aEnd == D2D1_FIGURE_END_CLOSED) { + return mSink->EndFigure(aEnd); + } else { + mNeedsFigureEnded = true; + } + } +private: + void EnsureFigureEnded() + { + if (mNeedsFigureEnded) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + mNeedsFigureEnded = false; + } + } + + ID2D1SimplifiedGeometrySink *mSink; + bool mNeedsFigureEnded; +}; + +class MOZ_STACK_CLASS AutoRestoreFP +{ +public: + AutoRestoreFP() + { + // save the current floating point control word + _controlfp_s(&savedFPSetting, 0, 0); + UINT unused; + // set the floating point control word to its default value + _controlfp_s(&unused, _CW_DEFAULT, MCW_PC); + } + ~AutoRestoreFP() + { + UINT unused; + // restore the saved floating point control word + _controlfp_s(&unused, savedFPSetting, MCW_PC); + } +private: + UINT savedFPSetting; +}; + +// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may +// get called from D2D with nonstandard floating point settings (see comments in +// bug 1134549) - use AutoRestoreFP to reset the floating point control word to +// what we expect +class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink +{ +public: + StreamingGeometrySink(PathSink *aSink) + : mSink(aSink) + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID &aIID, void **aPtr) + { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + // We ignore SetFillMode, this depends on the destination sink. + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) + { return; } + STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) + { + AutoRestoreFP resetFloatingPoint; + mSink->MoveTo(ToPoint(aPoint)); + } + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) + { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } + } + STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) + { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { + mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); + } + } + STDMETHOD(Close)() + { /* Should never be called! */ return S_OK; } + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) + { /* Should never be called! */ } + + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) + { + AutoRestoreFP resetFloatingPoint; + if (aEnd == D2D1_FIGURE_END_CLOSED) { + return mSink->Close(); + } + } +private: + + PathSink *mSink; +}; + +PathBuilderD2D::~PathBuilderD2D() +{ +} + +void +PathBuilderD2D::MoveTo(const Point &aPoint) +{ + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + mFigureActive = false; + } + EnsureActive(aPoint); + mCurrentPoint = aPoint; +} + +void +PathBuilderD2D::LineTo(const Point &aPoint) +{ + EnsureActive(aPoint); + mSink->AddLine(D2DPoint(aPoint)); + + mCurrentPoint = aPoint; +} + +void +PathBuilderD2D::BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3) + { + EnsureActive(aCP1); + mSink->AddBezier(D2D1::BezierSegment(D2DPoint(aCP1), + D2DPoint(aCP2), + D2DPoint(aCP3))); + + mCurrentPoint = aCP3; +} + +void +PathBuilderD2D::QuadraticBezierTo(const Point &aCP1, + const Point &aCP2) +{ + EnsureActive(aCP1); + mSink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(D2DPoint(aCP1), + D2DPoint(aCP2))); + + mCurrentPoint = aCP2; +} + +void +PathBuilderD2D::Close() +{ + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_CLOSED); + + mFigureActive = false; + + EnsureActive(mBeginPoint); + } +} + +void +PathBuilderD2D::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise) +{ + MOZ_ASSERT(aRadius >= 0); + + if (aAntiClockwise && aStartAngle < aEndAngle) { + // D2D does things a little differently, and draws the arc by specifying an + // beginning and an end point. This means the circle will be the wrong way + // around if the start angle is smaller than the end angle. It might seem + // tempting to invert aAntiClockwise but that would change the sweeping + // direction of the arc so instead we exchange start/begin. + Float oldStart = aStartAngle; + aStartAngle = aEndAngle; + aEndAngle = oldStart; + } + + // XXX - Workaround for now, D2D does not appear to do the desired thing when + // the angle sweeps a complete circle. + bool fullCircle = false; + if (aEndAngle - aStartAngle >= 2 * M_PI) { + fullCircle = true; + aEndAngle = Float(aStartAngle + M_PI * 1.9999); + } else if (aStartAngle - aEndAngle >= 2 * M_PI) { + fullCircle = true; + aStartAngle = Float(aEndAngle + M_PI * 1.9999); + } + + Point startPoint; + startPoint.x = aOrigin.x + aRadius * cos(aStartAngle); + startPoint.y = aOrigin.y + aRadius * sin(aStartAngle); + + if (!mFigureActive) { + EnsureActive(startPoint); + } else { + mSink->AddLine(D2DPoint(startPoint)); + } + + Point endPoint; + endPoint.x = aOrigin.x + aRadius * cosf(aEndAngle); + endPoint.y = aOrigin.y + aRadius * sinf(aEndAngle); + + D2D1_ARC_SIZE arcSize = D2D1_ARC_SIZE_SMALL; + D2D1_SWEEP_DIRECTION direction = + aAntiClockwise ? D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE : + D2D1_SWEEP_DIRECTION_CLOCKWISE; + + // if startPoint and endPoint of our circle are too close there are D2D issues + // with drawing the circle as a single arc + const Float kEpsilon = 1e-5f; + if (!fullCircle || + (std::abs(startPoint.x - endPoint.x) + + std::abs(startPoint.y - endPoint.y) > kEpsilon)) { + + if (aAntiClockwise) { + if (aStartAngle - aEndAngle > M_PI) { + arcSize = D2D1_ARC_SIZE_LARGE; + } + } else { + if (aEndAngle - aStartAngle > M_PI) { + arcSize = D2D1_ARC_SIZE_LARGE; + } + } + + mSink->AddArc(D2D1::ArcSegment(D2DPoint(endPoint), + D2D1::SizeF(aRadius, aRadius), + 0.0f, + direction, + arcSize)); + } + else { + // our first workaround attempt didn't work, so instead draw the circle as + // two half-circles + Float midAngle = aEndAngle > aStartAngle ? + Float(aStartAngle + M_PI) : Float(aEndAngle + M_PI); + Point midPoint; + midPoint.x = aOrigin.x + aRadius * cosf(midAngle); + midPoint.y = aOrigin.y + aRadius * sinf(midAngle); + + mSink->AddArc(D2D1::ArcSegment(D2DPoint(midPoint), + D2D1::SizeF(aRadius, aRadius), + 0.0f, + direction, + arcSize)); + + // if the adjusted endPoint computed above is used here and endPoint != + // startPoint then this half of the circle won't render... + mSink->AddArc(D2D1::ArcSegment(D2DPoint(startPoint), + D2D1::SizeF(aRadius, aRadius), + 0.0f, + direction, + arcSize)); + } + + mCurrentPoint = endPoint; +} + +Point +PathBuilderD2D::CurrentPoint() const +{ + return mCurrentPoint; +} + +void +PathBuilderD2D::EnsureActive(const Point &aPoint) +{ + if (!mFigureActive) { + mSink->BeginFigure(D2DPoint(aPoint), D2D1_FIGURE_BEGIN_FILLED); + mBeginPoint = aPoint; + mFigureActive = true; + } +} + +already_AddRefed<Path> +PathBuilderD2D::Finish() +{ + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + } + + HRESULT hr = mSink->Close(); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to close PathSink. Code: " << hexa(hr); + return nullptr; + } + + return MakeAndAddRef<PathD2D>(mGeometry, mFigureActive, mCurrentPoint, mFillRule, mBackendType); +} + +already_AddRefed<PathBuilder> +PathD2D::CopyToBuilder(FillRule aFillRule) const +{ + return TransformedCopyToBuilder(Matrix(), aFillRule); +} + +already_AddRefed<PathBuilder> +PathD2D::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const +{ + RefPtr<ID2D1PathGeometry> path; + HRESULT hr = DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr); + return nullptr; + } + + RefPtr<ID2D1GeometrySink> sink; + hr = path->Open(getter_AddRefs(sink)); + if (FAILED(hr)) { + gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr); + return nullptr; + } + + if (aFillRule == FillRule::FILL_WINDING) { + sink->SetFillMode(D2D1_FILL_MODE_WINDING); + } + + if (mEndedActive) { + OpeningGeometrySink wrapSink(sink); + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2DMatrix(aTransform), + &wrapSink); + } else { + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2DMatrix(aTransform), + sink); + } + if (FAILED(hr)) { + gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: " << hexa(hr) << " Active: " << mEndedActive; + return nullptr; + } + + RefPtr<PathBuilderD2D> pathBuilder = new PathBuilderD2D(sink, path, aFillRule, mBackendType); + + pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint); + + if (mEndedActive) { + pathBuilder->mFigureActive = true; + } + + return pathBuilder.forget(); +} + +void +PathD2D::StreamToSink(PathSink *aSink) const +{ + HRESULT hr; + + StreamingGeometrySink sink(aSink); + + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2D1::IdentityMatrix(), &sink); + + if (FAILED(hr)) { + gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr); + return; + } +} + +bool +PathD2D::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const +{ + BOOL result; + + HRESULT hr = mGeometry->FillContainsPoint(D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result); + + if (FAILED(hr)) { + // Log + return false; + } + + return !!result; +} + +bool +PathD2D::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const +{ + BOOL result; + + RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); + HRESULT hr = mGeometry->StrokeContainsPoint(D2DPoint(aPoint), + aStrokeOptions.mLineWidth, + strokeStyle, + D2DMatrix(aTransform), + &result); + + if (FAILED(hr)) { + // Log + return false; + } + + return !!result; +} + +Rect +PathD2D::GetBounds(const Matrix &aTransform) const +{ + D2D1_RECT_F d2dBounds; + + HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds); + + Rect bounds = ToRect(d2dBounds); + if (FAILED(hr) || !bounds.IsFinite()) { + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr); + return Rect(); + } + + return bounds; +} + +Rect +PathD2D::GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform) const +{ + D2D1_RECT_F d2dBounds; + + RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions); + HRESULT hr = + mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle, + D2DMatrix(aTransform), &d2dBounds); + + Rect bounds = ToRect(d2dBounds); + if (FAILED(hr) || !bounds.IsFinite()) { + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr); + return Rect(); + } + + return bounds; +} + +} +} diff --git a/gfx/2d/PathD2D.h b/gfx/2d/PathD2D.h new file mode 100644 index 000000000..0fb550b4a --- /dev/null +++ b/gfx/2d/PathD2D.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHD2D_H_ +#define MOZILLA_GFX_PATHD2D_H_ + +#include <d2d1.h> + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class PathD2D; + +class PathBuilderD2D : public PathBuilder +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderD2D) + PathBuilderD2D(ID2D1GeometrySink *aSink, ID2D1PathGeometry *aGeom, FillRule aFillRule, BackendType aBackendType) + : mSink(aSink) + , mGeometry(aGeom) + , mFigureActive(false) + , mFillRule(aFillRule) + , mBackendType(aBackendType) + { + } + virtual ~PathBuilderD2D(); + + virtual void MoveTo(const Point &aPoint); + virtual void LineTo(const Point &aPoint); + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + virtual void Close(); + virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise = false); + virtual Point CurrentPoint() const; + + virtual already_AddRefed<Path> Finish(); + + virtual BackendType GetBackendType() const { return mBackendType; } + + ID2D1GeometrySink *GetSink() { return mSink; } + + bool IsFigureActive() const { return mFigureActive; } + +private: + friend class PathD2D; + + void EnsureActive(const Point &aPoint); + + RefPtr<ID2D1GeometrySink> mSink; + RefPtr<ID2D1PathGeometry> mGeometry; + + bool mFigureActive; + Point mCurrentPoint; + Point mBeginPoint; + FillRule mFillRule; + BackendType mBackendType; +}; + +class PathD2D : public Path +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathD2D) + PathD2D(ID2D1PathGeometry *aGeometry, bool aEndedActive, + const Point &aEndPoint, FillRule aFillRule, BackendType aBackendType) + : mGeometry(aGeometry) + , mEndedActive(aEndedActive) + , mEndPoint(aEndPoint) + , mFillRule(aFillRule) + , mBackendType(aBackendType) + {} + + virtual BackendType GetBackendType() const { return mBackendType; } + + virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const; + virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform, + FillRule aFillRule) const; + + virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const; + + virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const; + + virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const; + + virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform = Matrix()) const; + + virtual void StreamToSink(PathSink *aSink) const; + + virtual FillRule GetFillRule() const { return mFillRule; } + + ID2D1Geometry *GetGeometry() { return mGeometry; } + +private: + friend class DrawTargetD2D; + friend class DrawTargetD2D1; + + mutable RefPtr<ID2D1PathGeometry> mGeometry; + bool mEndedActive; + Point mEndPoint; + FillRule mFillRule; + BackendType mBackendType; +}; + +} +} + +#endif /* MOZILLA_GFX_PATHD2D_H_ */ diff --git a/gfx/2d/PathHelpers.cpp b/gfx/2d/PathHelpers.cpp new file mode 100644 index 000000000..49c344b42 --- /dev/null +++ b/gfx/2d/PathHelpers.cpp @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +UserDataKey sDisablePixelSnapping; + +void +AppendRectToPath(PathBuilder* aPathBuilder, + const Rect& aRect, + bool aDrawClockwise) +{ + if (aDrawClockwise) { + aPathBuilder->MoveTo(aRect.TopLeft()); + aPathBuilder->LineTo(aRect.TopRight()); + aPathBuilder->LineTo(aRect.BottomRight()); + aPathBuilder->LineTo(aRect.BottomLeft()); + } else { + aPathBuilder->MoveTo(aRect.TopRight()); + aPathBuilder->LineTo(aRect.TopLeft()); + aPathBuilder->LineTo(aRect.BottomLeft()); + aPathBuilder->LineTo(aRect.BottomRight()); + } + aPathBuilder->Close(); +} + +void +AppendRoundedRectToPath(PathBuilder* aPathBuilder, + const Rect& aRect, + const RectCornerRadii& aRadii, + bool aDrawClockwise) +{ + // For CW drawing, this looks like: + // + // ...******0** 1 C + // **** + // *** 2 + // ** + // * + // * + // 3 + // * + // * + // + // Where 0, 1, 2, 3 are the control points of the Bezier curve for + // the corner, and C is the actual corner point. + // + // At the start of the loop, the current point is assumed to be + // the point adjacent to the top left corner on the top + // horizontal. Note that corner indices start at the top left and + // continue clockwise, whereas in our loop i = 0 refers to the top + // right corner. + // + // When going CCW, the control points are swapped, and the first + // corner that's drawn is the top left (along with the top segment). + // + // There is considerable latitude in how one chooses the four + // control points for a Bezier curve approximation to an ellipse. + // For the overall path to be continuous and show no corner at the + // endpoints of the arc, points 0 and 3 must be at the ends of the + // straight segments of the rectangle; points 0, 1, and C must be + // collinear; and points 3, 2, and C must also be collinear. This + // leaves only two free parameters: the ratio of the line segments + // 01 and 0C, and the ratio of the line segments 32 and 3C. See + // the following papers for extensive discussion of how to choose + // these ratios: + // + // Dokken, Tor, et al. "Good approximation of circles by + // curvature-continuous Bezier curves." Computer-Aided + // Geometric Design 7(1990) 33--41. + // Goldapp, Michael. "Approximation of circular arcs by cubic + // polynomials." Computer-Aided Geometric Design 8(1991) 227--238. + // Maisonobe, Luc. "Drawing an elliptical arc using polylines, + // quadratic, or cubic Bezier curves." + // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf + // + // We follow the approach in section 2 of Goldapp (least-error, + // Hermite-type approximation) and make both ratios equal to + // + // 2 2 + n - sqrt(2n + 28) + // alpha = - * --------------------- + // 3 n - 4 + // + // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ). + // + // This is the result of Goldapp's equation (10b) when the angle + // swept out by the arc is pi/2, and the parameter "a-bar" is the + // expression given immediately below equation (21). + // + // Using this value, the maximum radial error for a circle, as a + // fraction of the radius, is on the order of 0.2 x 10^-3. + // Neither Dokken nor Goldapp discusses error for a general + // ellipse; Maisonobe does, but his choice of control points + // follows different constraints, and Goldapp's expression for + // 'alpha' gives much smaller radial error, even for very flat + // ellipses, than Maisonobe's equivalent. + // + // For the various corners and for each axis, the sign of this + // constant changes, or it might be 0 -- it's multiplied by the + // appropriate multiplier from the list before using. + + const Float alpha = Float(0.55191497064665766025); + + typedef struct { Float a, b; } twoFloats; + + twoFloats cwCornerMults[4] = { { -1, 0 }, // cc == clockwise + { 0, -1 }, + { +1, 0 }, + { 0, +1 } }; + twoFloats ccwCornerMults[4] = { { +1, 0 }, // ccw == counter-clockwise + { 0, -1 }, + { -1, 0 }, + { 0, +1 } }; + + twoFloats *cornerMults = aDrawClockwise ? cwCornerMults : ccwCornerMults; + + Point cornerCoords[] = { aRect.TopLeft(), aRect.TopRight(), + aRect.BottomRight(), aRect.BottomLeft() }; + + Point pc, p0, p1, p2, p3; + + if (aDrawClockwise) { + aPathBuilder->MoveTo(Point(aRect.X() + aRadii[RectCorner::TopLeft].width, + aRect.Y())); + } else { + aPathBuilder->MoveTo(Point(aRect.X() + aRect.Width() - aRadii[RectCorner::TopRight].width, + aRect.Y())); + } + + for (int i = 0; i < 4; ++i) { + // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) + int c = aDrawClockwise ? ((i+1) % 4) : ((4-i) % 4); + + // i+2 and i+3 respectively. These are used to index into the corner + // multiplier table, and were deduced by calculating out the long form + // of each corner and finding a pattern in the signs and values. + int i2 = (i+2) % 4; + int i3 = (i+3) % 4; + + pc = cornerCoords[c]; + + if (aRadii[c].width > 0.0 && aRadii[c].height > 0.0) { + p0.x = pc.x + cornerMults[i].a * aRadii[c].width; + p0.y = pc.y + cornerMults[i].b * aRadii[c].height; + + p3.x = pc.x + cornerMults[i3].a * aRadii[c].width; + p3.y = pc.y + cornerMults[i3].b * aRadii[c].height; + + p1.x = p0.x + alpha * cornerMults[i2].a * aRadii[c].width; + p1.y = p0.y + alpha * cornerMults[i2].b * aRadii[c].height; + + p2.x = p3.x - alpha * cornerMults[i3].a * aRadii[c].width; + p2.y = p3.y - alpha * cornerMults[i3].b * aRadii[c].height; + + aPathBuilder->LineTo(p0); + aPathBuilder->BezierTo(p1, p2, p3); + } else { + aPathBuilder->LineTo(pc); + } + } + + aPathBuilder->Close(); +} + +void +AppendEllipseToPath(PathBuilder* aPathBuilder, + const Point& aCenter, + const Size& aDimensions) +{ + Size halfDim = aDimensions / 2.f; + Rect rect(aCenter - Point(halfDim.width, halfDim.height), aDimensions); + RectCornerRadii radii(halfDim.width, halfDim.height); + + AppendRoundedRectToPath(aPathBuilder, rect, radii); +} + +bool +SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2, + const DrawTarget& aDrawTarget, + Float aLineWidth) +{ + Matrix mat = aDrawTarget.GetTransform(); + if (mat.HasNonTranslation()) { + return false; + } + if (aP1.x != aP2.x && aP1.y != aP2.y) { + return false; // not a horizontal or vertical line + } + Point p1 = aP1 + mat.GetTranslation(); // into device space + Point p2 = aP2 + mat.GetTranslation(); + p1.Round(); + p2.Round(); + p1 -= mat.GetTranslation(); // back into user space + p2 -= mat.GetTranslation(); + + aP1 = p1; + aP2 = p2; + + bool lineWidthIsOdd = (int(aLineWidth) % 2) == 1; + if (lineWidthIsOdd) { + if (aP1.x == aP2.x) { + // snap vertical line, adding 0.5 to align it to be mid-pixel: + aP1 += Point(0.5, 0); + aP2 += Point(0.5, 0); + } else { + // snap horizontal line, adding 0.5 to align it to be mid-pixel: + aP1 += Point(0, 0.5); + aP2 += Point(0, 0.5); + } + } + return true; +} + +void +StrokeSnappedEdgesOfRect(const Rect& aRect, DrawTarget& aDrawTarget, + const ColorPattern& aColor, + const StrokeOptions& aStrokeOptions) +{ + if (aRect.IsEmpty()) { + return; + } + + Point p1 = aRect.TopLeft(); + Point p2 = aRect.BottomLeft(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); + + p1 = aRect.BottomLeft(); + p2 = aRect.BottomRight(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); + + p1 = aRect.TopLeft(); + p2 = aRect.TopRight(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); + + p1 = aRect.TopRight(); + p2 = aRect.BottomRight(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); +} + +// The logic for this comes from _cairo_stroke_style_max_distance_from_path +Margin +MaxStrokeExtents(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform) +{ + double styleExpansionFactor = 0.5f; + + if (aStrokeOptions.mLineCap == CapStyle::SQUARE) { + styleExpansionFactor = M_SQRT1_2; + } + + if (aStrokeOptions.mLineJoin == JoinStyle::MITER && + styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) { + styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit; + } + + styleExpansionFactor *= aStrokeOptions.mLineWidth; + + double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21); + double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12); + return Margin(dy, dx, dy, dx); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h new file mode 100644 index 000000000..553a886b9 --- /dev/null +++ b/gfx/2d/PathHelpers.h @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHHELPERS_H_ +#define MOZILLA_GFX_PATHHELPERS_H_ + +#include "2D.h" +#include "UserData.h" + +#include <cmath> + +namespace mozilla { +namespace gfx { + +// Kappa constant for 90-degree angle +const Float kKappaFactor = 0.55191497064665766025f; + +// Calculate kappa constant for partial curve. The sign of angle in the +// tangent will actually ensure this is negative for a counter clockwise +// sweep, so changing signs later isn't needed. +inline Float ComputeKappaFactor(Float aAngle) +{ + return (4.0f / 3.0f) * tanf(aAngle / 4.0f); +} + +/** + * Draws a partial arc <= 90 degrees given exact start and end points. + * Assumes that it is continuing from an already specified start point. + */ +template <typename T> +inline void PartialArcToBezier(T* aSink, + const Point& aStartOffset, const Point& aEndOffset, + const Matrix& aTransform, + Float aKappaFactor = kKappaFactor) +{ + Point cp1 = + aStartOffset + Point(-aStartOffset.y, aStartOffset.x) * aKappaFactor; + + Point cp2 = + aEndOffset + Point(aEndOffset.y, -aEndOffset.x) * aKappaFactor; + + aSink->BezierTo(aTransform.TransformPoint(cp1), + aTransform.TransformPoint(cp2), + aTransform.TransformPoint(aEndOffset)); +} + +/** + * Draws an acute arc (<= 90 degrees) given exact start and end points. + * Specialized version avoiding kappa calculation. + */ +template <typename T> +inline void AcuteArcToBezier(T* aSink, + const Point& aOrigin, const Size& aRadius, + const Point& aStartPoint, const Point& aEndPoint, + Float aKappaFactor = kKappaFactor) +{ + aSink->LineTo(aStartPoint); + if (!aRadius.IsEmpty()) { + Float kappaX = aKappaFactor * aRadius.width / aRadius.height; + Float kappaY = aKappaFactor * aRadius.height / aRadius.width; + Point startOffset = aStartPoint - aOrigin; + Point endOffset = aEndPoint - aOrigin; + aSink->BezierTo(aStartPoint + Point(-startOffset.y * kappaX, startOffset.x * kappaY), + aEndPoint + Point(endOffset.y * kappaX, -endOffset.x * kappaY), + aEndPoint); + } else if (aEndPoint != aStartPoint) { + aSink->LineTo(aEndPoint); + } +} + +/** + * Draws an acute arc (<= 90 degrees) given exact start and end points. + */ +template <typename T> +inline void AcuteArcToBezier(T* aSink, + const Point& aOrigin, const Size& aRadius, + const Point& aStartPoint, const Point& aEndPoint, + Float aStartAngle, Float aEndAngle) +{ + AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint, + ComputeKappaFactor(aEndAngle - aStartAngle)); +} + +template <typename T> +void ArcToBezier(T* aSink, const Point &aOrigin, const Size &aRadius, + float aStartAngle, float aEndAngle, bool aAntiClockwise, + float aRotation = 0.0f) +{ + Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f; + + // Calculate the total arc we're going to sweep. + Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection; + + // Clockwise we always sweep from the smaller to the larger angle, ccw + // it's vice versa. + if (arcSweepLeft < 0) { + // Rerverse sweep is modulo'd into range rather than clamped. + arcSweepLeft = Float(2.0f * M_PI) + fmodf(arcSweepLeft, Float(2.0f * M_PI)); + // Recalculate the start angle to land closer to end angle. + aStartAngle = aEndAngle - arcSweepLeft * sweepDirection; + } else if (arcSweepLeft > Float(2.0f * M_PI)) { + // Sweeping more than 2 * pi is a full circle. + arcSweepLeft = Float(2.0f * M_PI); + } + + Float currentStartAngle = aStartAngle; + Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle)); + Matrix transform = Matrix::Scaling(aRadius.width, aRadius.height); + if (aRotation != 0.0f) { + transform *= Matrix::Rotation(aRotation); + } + transform.PostTranslate(aOrigin); + aSink->LineTo(transform.TransformPoint(currentStartOffset)); + + while (arcSweepLeft > 0) { + Float currentEndAngle = + currentStartAngle + std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection; + Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle)); + + PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform, + ComputeKappaFactor(currentEndAngle - currentStartAngle)); + + // We guarantee here the current point is the start point of the next + // curve segment. + arcSweepLeft -= Float(M_PI / 2.0f); + currentStartAngle = currentEndAngle; + currentStartOffset = currentEndOffset; + } +} + +/* This is basically the ArcToBezier with the parameters for drawing a circle + * inlined which vastly simplifies it and avoids a bunch of transcedental function + * calls which should make it faster. */ +template <typename T> +void EllipseToBezier(T* aSink, const Point &aOrigin, const Size &aRadius) +{ + Matrix transform(aRadius.width, 0, 0, aRadius.height, aOrigin.x, aOrigin.y); + Point currentStartOffset(1, 0); + + aSink->LineTo(transform.TransformPoint(currentStartOffset)); + + for (int i = 0; i < 4; i++) { + // cos(x+pi/2) == -sin(x) + // sin(x+pi/2) == cos(x) + Point currentEndOffset(-currentStartOffset.y, currentStartOffset.x); + + PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform); + + // We guarantee here the current point is the start point of the next + // curve segment. + currentStartOffset = currentEndOffset; + } +} + +/** + * Appends a path represending a rectangle to the path being built by + * aPathBuilder. + * + * aRect The rectangle to append. + * aDrawClockwise If set to true, the path will start at the left of the top + * left edge and draw clockwise. If set to false the path will + * start at the right of the top left edge and draw counter- + * clockwise. + */ +GFX2D_API void AppendRectToPath(PathBuilder* aPathBuilder, + const Rect& aRect, + bool aDrawClockwise = true); + +inline already_AddRefed<Path> MakePathForRect(const DrawTarget& aDrawTarget, + const Rect& aRect, + bool aDrawClockwise = true) +{ + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + AppendRectToPath(builder, aRect, aDrawClockwise); + return builder->Finish(); +} + +struct RectCornerRadii { + Size radii[RectCorner::Count]; + + RectCornerRadii() {} + + explicit RectCornerRadii(Float radius) { + for (int i = 0; i < RectCorner::Count; i++) { + radii[i].SizeTo(radius, radius); + } + } + + explicit RectCornerRadii(Float radiusX, Float radiusY) { + for (int i = 0; i < RectCorner::Count; i++) { + radii[i].SizeTo(radiusX, radiusY); + } + } + + RectCornerRadii(Float tl, Float tr, Float br, Float bl) { + radii[RectCorner::TopLeft].SizeTo(tl, tl); + radii[RectCorner::TopRight].SizeTo(tr, tr); + radii[RectCorner::BottomRight].SizeTo(br, br); + radii[RectCorner::BottomLeft].SizeTo(bl, bl); + } + + RectCornerRadii(const Size& tl, const Size& tr, + const Size& br, const Size& bl) { + radii[RectCorner::TopLeft] = tl; + radii[RectCorner::TopRight] = tr; + radii[RectCorner::BottomRight] = br; + radii[RectCorner::BottomLeft] = bl; + } + + const Size& operator[](size_t aCorner) const { + return radii[aCorner]; + } + + Size& operator[](size_t aCorner) { + return radii[aCorner]; + } + + bool operator==(const RectCornerRadii& aOther) const { + for (size_t i = 0; i < RectCorner::Count; i++) { + if (radii[i] != aOther.radii[i]) return false; + } + return true; + } + + void Scale(Float aXScale, Float aYScale) { + for (int i = 0; i < RectCorner::Count; i++) { + radii[i].Scale(aXScale, aYScale); + } + } + + const Size TopLeft() const { return radii[RectCorner::TopLeft]; } + Size& TopLeft() { return radii[RectCorner::TopLeft]; } + + const Size TopRight() const { return radii[RectCorner::TopRight]; } + Size& TopRight() { return radii[RectCorner::TopRight]; } + + const Size BottomRight() const { return radii[RectCorner::BottomRight]; } + Size& BottomRight() { return radii[RectCorner::BottomRight]; } + + const Size BottomLeft() const { return radii[RectCorner::BottomLeft]; } + Size& BottomLeft() { return radii[RectCorner::BottomLeft]; } +}; + +/** + * Appends a path represending a rounded rectangle to the path being built by + * aPathBuilder. + * + * aRect The rectangle to append. + * aCornerRadii Contains the radii of the top-left, top-right, bottom-right + * and bottom-left corners, in that order. + * aDrawClockwise If set to true, the path will start at the left of the top + * left edge and draw clockwise. If set to false the path will + * start at the right of the top left edge and draw counter- + * clockwise. + */ +GFX2D_API void AppendRoundedRectToPath(PathBuilder* aPathBuilder, + const Rect& aRect, + const RectCornerRadii& aRadii, + bool aDrawClockwise = true); + +inline already_AddRefed<Path> MakePathForRoundedRect(const DrawTarget& aDrawTarget, + const Rect& aRect, + const RectCornerRadii& aRadii, + bool aDrawClockwise = true) +{ + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + AppendRoundedRectToPath(builder, aRect, aRadii, aDrawClockwise); + return builder->Finish(); +} + +/** + * Appends a path represending an ellipse to the path being built by + * aPathBuilder. + * + * The ellipse extends aDimensions.width / 2.0 in the horizontal direction + * from aCenter, and aDimensions.height / 2.0 in the vertical direction. + */ +GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder, + const Point& aCenter, + const Size& aDimensions); + +inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget, + const Point& aCenter, + const Size& aDimensions) +{ + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + AppendEllipseToPath(builder, aCenter, aDimensions); + return builder->Finish(); +} + +/** + * If aDrawTarget's transform only contains a translation, and if this line is + * a horizontal or vertical line, this function will snap the line's vertices + * to align with the device pixel grid so that stroking the line with a one + * pixel wide stroke will result in a crisp line that is not antialiased over + * two pixels across its width. + * + * @return Returns true if this function snaps aRect's vertices, else returns + * false. + */ +GFX2D_API bool SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2, + const DrawTarget& aDrawTarget, + Float aLineWidth); + +/** + * This function paints each edge of aRect separately, snapping the edges using + * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths + * helps ensure not only that the stroke spans a single row of device pixels if + * possible, but also that the ends of stroke dashes start and end on device + * pixels too. + */ +GFX2D_API void StrokeSnappedEdgesOfRect(const Rect& aRect, + DrawTarget& aDrawTarget, + const ColorPattern& aColor, + const StrokeOptions& aStrokeOptions); + +/** + * Return the margin, in device space, by which a stroke can extend beyond the + * rendered shape. + * @param aStrokeOptions The stroke options that the stroke is drawn with. + * @param aTransform The user space to device space transform. + * @return The stroke margin. + */ +GFX2D_API Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform); + +extern UserDataKey sDisablePixelSnapping; + +/** + * If aDrawTarget's transform only contains a translation or, if + * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this + * function will convert aRect to device space and snap it to device pixels. + * This function returns true if aRect is modified, otherwise it returns false. + * + * Note that the snapping is such that filling the rect using a DrawTarget + * which has the identity matrix as its transform will result in crisp edges. + * (That is, aRect will have integer values, aligning its edges between pixel + * boundaries.) If on the other hand you stroking the rect with an odd valued + * stroke width then the edges of the stroke will be antialiased (assuming an + * AntialiasMode that does antialiasing). + * + * Empty snaps are those which result in a rectangle of 0 area. If they are + * disallowed, an axis is left unsnapped if the rounding process results in a + * length of 0. + */ +inline bool UserToDevicePixelSnapped(Rect& aRect, const DrawTarget& aDrawTarget, + bool aAllowScaleOr90DegreeRotate = false, + bool aAllowEmptySnaps = true) +{ + if (aDrawTarget.GetUserData(&sDisablePixelSnapping)) { + return false; + } + + Matrix mat = aDrawTarget.GetTransform(); + + const Float epsilon = 0.0000001f; +#define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) + if (!aAllowScaleOr90DegreeRotate && + (!WITHIN_E(mat._11, 1.f) || !WITHIN_E(mat._22, 1.f) || + !WITHIN_E(mat._12, 0.f) || !WITHIN_E(mat._21, 0.f))) { + // We have non-translation, but only translation is allowed. + return false; + } +#undef WITHIN_E + + Point p1 = mat.TransformPoint(aRect.TopLeft()); + Point p2 = mat.TransformPoint(aRect.TopRight()); + Point p3 = mat.TransformPoint(aRect.BottomRight()); + + // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, + // two opposite corners define the entire rectangle. So check if + // the axis-aligned rectangle with opposite corners p1 and p3 + // define an axis-aligned rectangle whose other corners are p2 and p4. + // We actually only need to check one of p2 and p4, since an affine + // transform maps parallelograms to parallelograms. + if (p2 == Point(p1.x, p3.y) || p2 == Point(p3.x, p1.y)) { + Point p1r = p1; + Point p3r = p3; + p1r.Round(); + p3r.Round(); + if (aAllowEmptySnaps || p1r.x != p3r.x) { + p1.x = p1r.x; + p3.x = p3r.x; + } + if (aAllowEmptySnaps || p1r.y != p3r.y) { + p1.y = p1r.y; + p3.y = p3r.y; + } + + aRect.MoveTo(Point(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); + aRect.SizeTo(Size(std::max(p1.x, p3.x) - aRect.X(), + std::max(p1.y, p3.y) - aRect.Y())); + return true; + } + + return false; +} + +/** + * This function has the same behavior as UserToDevicePixelSnapped except that + * aRect is not transformed to device space. + */ +inline bool MaybeSnapToDevicePixels(Rect& aRect, const DrawTarget& aDrawTarget, + bool aAllowScaleOr90DegreeRotate = false, + bool aAllowEmptySnaps = true) +{ + if (UserToDevicePixelSnapped(aRect, aDrawTarget, + aAllowScaleOr90DegreeRotate, aAllowEmptySnaps)) { + // Since UserToDevicePixelSnapped returned true we know there is no + // rotation/skew in 'mat', so we can just use TransformBounds() here. + Matrix mat = aDrawTarget.GetTransform(); + mat.Invert(); + aRect = mat.TransformBounds(aRect); + return true; + } + return false; +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATHHELPERS_H_ */ diff --git a/gfx/2d/PathRecording.cpp b/gfx/2d/PathRecording.cpp new file mode 100644 index 000000000..5884a0de9 --- /dev/null +++ b/gfx/2d/PathRecording.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathRecording.h" +#include "DrawEventRecorder.h" + +namespace mozilla { +namespace gfx { + +using namespace std; + +void +PathBuilderRecording::MoveTo(const Point &aPoint) +{ + PathOp op; + op.mType = PathOp::OP_MOVETO; + op.mP1 = aPoint; + mPathOps.push_back(op); + mPathBuilder->MoveTo(aPoint); +} + +void +PathBuilderRecording::LineTo(const Point &aPoint) +{ + PathOp op; + op.mType = PathOp::OP_LINETO; + op.mP1 = aPoint; + mPathOps.push_back(op); + mPathBuilder->LineTo(aPoint); +} + +void +PathBuilderRecording::BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) +{ + PathOp op; + op.mType = PathOp::OP_BEZIERTO; + op.mP1 = aCP1; + op.mP2 = aCP2; + op.mP3 = aCP3; + mPathOps.push_back(op); + mPathBuilder->BezierTo(aCP1, aCP2, aCP3); +} + +void +PathBuilderRecording::QuadraticBezierTo(const Point &aCP1, const Point &aCP2) +{ + PathOp op; + op.mType = PathOp::OP_QUADRATICBEZIERTO; + op.mP1 = aCP1; + op.mP2 = aCP2; + mPathOps.push_back(op); + mPathBuilder->QuadraticBezierTo(aCP1, aCP2); +} + +void +PathBuilderRecording::Close() +{ + PathOp op; + op.mType = PathOp::OP_CLOSE; + mPathOps.push_back(op); + mPathBuilder->Close(); +} + +Point +PathBuilderRecording::CurrentPoint() const +{ + return mPathBuilder->CurrentPoint(); +} + +already_AddRefed<Path> +PathBuilderRecording::Finish() +{ + RefPtr<Path> path = mPathBuilder->Finish(); + return MakeAndAddRef<PathRecording>(path, mPathOps, mFillRule); +} + +PathRecording::~PathRecording() +{ + for (size_t i = 0; i < mStoredRecorders.size(); i++) { + mStoredRecorders[i]->RemoveStoredObject(this); + mStoredRecorders[i]->RecordEvent(RecordedPathDestruction(this)); + } +} + +already_AddRefed<PathBuilder> +PathRecording::CopyToBuilder(FillRule aFillRule) const +{ + RefPtr<PathBuilder> pathBuilder = mPath->CopyToBuilder(aFillRule); + RefPtr<PathBuilderRecording> recording = new PathBuilderRecording(pathBuilder, aFillRule); + recording->mPathOps = mPathOps; + return recording.forget(); +} + +already_AddRefed<PathBuilder> +PathRecording::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const +{ + RefPtr<PathBuilder> pathBuilder = mPath->TransformedCopyToBuilder(aTransform, aFillRule); + RefPtr<PathBuilderRecording> recording = new PathBuilderRecording(pathBuilder, aFillRule); + typedef std::vector<PathOp> pathOpVec; + for (pathOpVec::const_iterator iter = mPathOps.begin(); iter != mPathOps.end(); iter++) { + PathOp newPathOp; + newPathOp.mType = iter->mType; + if (sPointCount[newPathOp.mType] >= 1) { + newPathOp.mP1 = aTransform.TransformPoint(iter->mP1); + } + if (sPointCount[newPathOp.mType] >= 2) { + newPathOp.mP2 = aTransform.TransformPoint(iter->mP2); + } + if (sPointCount[newPathOp.mType] >= 3) { + newPathOp.mP3 = aTransform.TransformPoint(iter->mP3); + } + recording->mPathOps.push_back(newPathOp); + } + return recording.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathRecording.h b/gfx/2d/PathRecording.h new file mode 100644 index 000000000..7faaa716f --- /dev/null +++ b/gfx/2d/PathRecording.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHRECORDING_H_ +#define MOZILLA_GFX_PATHRECORDING_H_ + +#include "2D.h" +#include <vector> +#include <ostream> + +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +struct PathOp +{ + enum OpType { + OP_MOVETO = 0, + OP_LINETO, + OP_BEZIERTO, + OP_QUADRATICBEZIERTO, + OP_CLOSE + }; + + OpType mType; + Point mP1; + Point mP2; + Point mP3; +}; + +const int32_t sPointCount[] = { 1, 1, 3, 2, 0, 0 }; + +class PathRecording; +class DrawEventRecorderPrivate; + +class PathBuilderRecording : public PathBuilder +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderRecording) + PathBuilderRecording(PathBuilder *aBuilder, FillRule aFillRule) + : mPathBuilder(aBuilder), mFillRule(aFillRule) + { + } + + /* 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); + /* Add a linesegment to the current figure */ + virtual void LineTo(const Point &aPoint); + /* Add a cubic bezier curve to the current figure */ + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + /* Add a quadratic bezier curve to the current figure */ + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + /* 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(); + + /* Add an arc to the current figure */ + virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) { + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, + aAntiClockwise); + } + + /* Point the current subpath is at - or where the next subpath will start + * if there is no active subpath. + */ + virtual Point CurrentPoint() const; + + virtual already_AddRefed<Path> Finish(); + + virtual BackendType GetBackendType() const { return BackendType::RECORDING; } + +private: + friend class PathRecording; + + RefPtr<PathBuilder> mPathBuilder; + FillRule mFillRule; + std::vector<PathOp> mPathOps; +}; + +class PathRecording : public Path +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathRecording) + PathRecording(Path *aPath, const std::vector<PathOp> aOps, FillRule aFillRule) + : mPath(aPath), mPathOps(aOps), mFillRule(aFillRule) + { + } + + ~PathRecording(); + + virtual BackendType GetBackendType() const { return BackendType::RECORDING; } + 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 + { return mPath->ContainsPoint(aPoint, aTransform); } + virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const + { return mPath->StrokeContainsPoint(aStrokeOptions, aPoint, aTransform); } + + virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const + { return mPath->GetBounds(aTransform); } + + virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform = Matrix()) const + { return mPath->GetStrokedBounds(aStrokeOptions, aTransform); } + + virtual void StreamToSink(PathSink *aSink) const { mPath->StreamToSink(aSink); } + + virtual FillRule GetFillRule() const { return mFillRule; } + + void StorePath(std::ostream &aStream) const; + static void ReadPathToBuilder(std::istream &aStream, PathBuilder *aBuilder); + +private: + friend class DrawTargetRecording; + friend class RecordedPathCreation; + + RefPtr<Path> mPath; + std::vector<PathOp> mPathOps; + FillRule mFillRule; + + // 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 000000000..44329bb76 --- /dev/null +++ b/gfx/2d/PathSkia.cpp @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathSkia.h" +#include <math.h> +#include "DrawTargetSkia.h" +#include "Logging.h" +#include "HelpersSkia.h" +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +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(SkPath::kWinding_FillType); + } else { + mPath.setFillType(SkPath::kEvenOdd_FillType); + } +} + +void +PathBuilderSkia::MoveTo(const Point &aPoint) +{ + mPath.moveTo(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y)); +} + +void +PathBuilderSkia::LineTo(const Point &aPoint) +{ + if (!mPath.countPoints()) { + MoveTo(aPoint); + } else { + mPath.lineTo(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y)); + } +} + +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)); +} + +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)); +} + +void +PathBuilderSkia::Close() +{ + mPath.close(); +} + +void +PathBuilderSkia::Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) +{ + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise); +} + +Point +PathBuilderSkia::CurrentPoint() const +{ + int pointCount = mPath.countPoints(); + if (!pointCount) { + return Point(0, 0); + } + SkPoint point = mPath.getPoint(pointCount - 1); + return Point(SkScalarToFloat(point.fX), SkScalarToFloat(point.fY)); +} + +already_AddRefed<Path> +PathBuilderSkia::Finish() +{ + return MakeAndAddRef<PathSkia>(mPath, mFillRule); +} + +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 +{ + return MakeAndAddRef<PathBuilderSkia>(aTransform, mPath, aFillRule); +} + +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::StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const +{ + if (!mPath.isFinite()) { + return false; + } + + SkPaint paint; + if (!StrokeOptionsToPaint(paint, aStrokeOptions)) { + return false; + } + + SkPath strokePath; + paint.getFillPath(mPath, &strokePath); + + return SkPathContainsPoint(strokePath, aPoint, aTransform); +} + +Rect +PathSkia::GetBounds(const Matrix &aTransform) const +{ + if (!mPath.isFinite()) { + return Rect(); + } + + Rect bounds = SkRectToRect(mPath.getBounds()); + return aTransform.TransformBounds(bounds); +} + +Rect +PathSkia::GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform) const +{ + if (!mPath.isFinite()) { + return Rect(); + } + + SkPaint paint; + if (!StrokeOptionsToPaint(paint, aStrokeOptions)) { + return Rect(); + } + + SkPath result; + paint.getFillPath(mPath, &result); + + Rect bounds = SkRectToRect(result.getBounds()); + return aTransform.TransformBounds(bounds); +} + +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::kClose_Verb: + aSink->Close(); + break; + default: + MOZ_ASSERT(false); + // Unexpected verb found in path! + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathSkia.h b/gfx/2d/PathSkia.h new file mode 100644 index 000000000..aec06bb2e --- /dev/null +++ b/gfx/2d/PathSkia.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATH_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) + PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath, FillRule aFillRule); + explicit PathBuilderSkia(FillRule aFillRule); + + virtual void MoveTo(const Point &aPoint); + virtual void LineTo(const Point &aPoint); + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + virtual void Close(); + virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false); + virtual Point CurrentPoint() const; + virtual already_AddRefed<Path> Finish(); + + void AppendPath(const SkPath &aPath); + + virtual BackendType GetBackendType() const { return BackendType::SKIA; } + +private: + + void SetFillRule(FillRule aFillRule); + + SkPath mPath; + FillRule mFillRule; +}; + +class PathSkia : public Path +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathSkia) + PathSkia(SkPath& aPath, FillRule aFillRule) + : mFillRule(aFillRule) + { + mPath.swap(aPath); + } + + virtual BackendType GetBackendType() const { return BackendType::SKIA; } + + virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const; + virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform, + FillRule aFillRule) const; + + virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const; + + virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions, + const Point &aPoint, + const Matrix &aTransform) const; + + virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const; + + virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform = Matrix()) const; + + virtual void StreamToSink(PathSink *aSink) const; + + virtual FillRule GetFillRule() const { return mFillRule; } + + const SkPath& GetPath() const { return mPath; } + +private: + friend class DrawTargetSkia; + + SkPath mPath; + FillRule mFillRule; +}; + +} // 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 000000000..763b30a6f --- /dev/null +++ b/gfx/2d/PatternHelpers.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_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 +{ +public: + explicit GeneralPattern() + : mPattern(nullptr) + {} + + GeneralPattern(const GeneralPattern& aOther) + : mPattern(nullptr) + {} + + ~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::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 Color &aColor) { + MOZ_ASSERT(!mPattern); + mPattern = new (mColorPattern.addr()) ColorPattern(aColor); + return mColorPattern.addr(); + } + + LinearGradientPattern* InitLinearGradientPattern(const Point &aBegin, + const Point &aEnd, + GradientStops *aStops, + const Matrix &aMatrix = Matrix()) { + MOZ_ASSERT(!mPattern); + mPattern = new (mLinearGradientPattern.addr()) + LinearGradientPattern(aBegin, aEnd, aStops, aMatrix); + return mLinearGradientPattern.addr(); + } + + RadialGradientPattern* InitRadialGradientPattern(const Point &aCenter1, + const Point &aCenter2, + Float aRadius1, + Float aRadius2, + GradientStops *aStops, + const Matrix &aMatrix = Matrix()) { + MOZ_ASSERT(!mPattern); + mPattern = new (mRadialGradientPattern.addr()) + RadialGradientPattern(aCenter1, aCenter2, aRadius1, aRadius2, aStops, aMatrix); + return mRadialGradientPattern.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<SurfacePattern> mSurfacePattern; + }; + Pattern *mPattern; +}; + +} // 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 000000000..f66967622 --- /dev/null +++ b/gfx/2d/Point.h @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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/TypeTraits.h" + +#include <cmath> + +namespace mozilla { + +template <typename> struct IsPixel; + +namespace gfx { + +// This should only be used by the typedefs below. +struct UnknownUnits {}; + +} // namespace gfx + +template<> struct IsPixel<gfx::UnknownUnits> : TrueType {}; + +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 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() {} + constexpr IntPointTyped(ToInt aX, ToInt aY) : Super(Coord(aX.value), Coord(aY.value)) {} + + static IntPointTyped<units> Round(float aX, float aY) { + return IntPointTyped(int32_t(floorf(aX + 0.5)), int32_t(floorf(aY + 0.5))); + } + + static IntPointTyped<units> Ceil(float aX, float aY) { + return IntPointTyped(int32_t(ceil(aX)), int32_t(ceil(aY))); + } + + static IntPointTyped<units> Floor(float aX, float aY) { + return IntPointTyped(int32_t(floorf(aX)), int32_t(floorf(aY))); + } + + static IntPointTyped<units> Truncate(float aX, float aY) { + return IntPointTyped(int32_t(aX), int32_t(aY)); + } + + static IntPointTyped<units> Round(const PointTyped<units, float>& aPoint); + static IntPointTyped<units> Ceil(const PointTyped<units, float>& aPoint); + static IntPointTyped<units> Floor(const PointTyped<units, float>& aPoint); + static IntPointTyped<units> 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<units> FromUnknownPoint(const IntPointTyped<UnknownUnits>& aPoint) { + return IntPointTyped<units>(aPoint.x, aPoint.y); + } + + IntPointTyped<UnknownUnits> ToUnknownPoint() const { + return IntPointTyped<UnknownUnits>(this->x, this->y); + } +}; +typedef IntPointTyped<UnknownUnits> IntPoint; + +template<class units, class F = Float> +struct 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() {} + 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)) {} + + // 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() {} + 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() {} + Point4DTyped(F aX, F aY, F aZ, F aW) : Super(aX, aY, aZ, aW) {} + + // 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() { + return PointTyped<units, F>(this->x / this->w, this->y / this->w); + } +}; +typedef Point4DTyped<UnknownUnits> Point4D; +typedef Point4DTyped<UnknownUnits, double> PointDouble4D; + +template<class units> +struct 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() {} + constexpr IntSizeTyped(ToInt aWidth, ToInt aHeight) : Super(aWidth.value, aHeight.value) {} + + static IntSizeTyped<units> Round(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(floorf(aWidth + 0.5)), int32_t(floorf(aHeight + 0.5))); + } + + static IntSizeTyped<units> Truncate(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(aWidth), int32_t(aHeight)); + } + + static IntSizeTyped<units> Ceil(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(ceil(aWidth)), int32_t(ceil(aHeight))); + } + + static IntSizeTyped<units> Floor(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(floorf(aWidth)), int32_t(floorf(aHeight))); + } + + static IntSizeTyped<units> Round(const SizeTyped<units, float>& aSize); + static IntSizeTyped<units> Ceil(const SizeTyped<units, float>& aSize); + static IntSizeTyped<units> Floor(const SizeTyped<units, float>& aSize); + static IntSizeTyped<units> Truncate(const SizeTyped<units, float>& aSize); + + // XXX When all of the code is ported, the following functions to convert to and from + // unknown types should be removed. + + static IntSizeTyped<units> FromUnknownSize(const IntSizeTyped<UnknownUnits>& aSize) { + return IntSizeTyped<units>(aSize.width, aSize.height); + } + + IntSizeTyped<UnknownUnits> ToUnknownSize() const { + return IntSizeTyped<UnknownUnits>(this->width, this->height); + } +}; +typedef IntSizeTyped<UnknownUnits> IntSize; + +template<class units, class F = Float> +struct SizeTyped : + public BaseSize< F, SizeTyped<units> >, + public units { + static_assert(IsPixel<units>::value, + "'units' must be a coordinate system tag"); + + typedef BaseSize< F, SizeTyped<units, F> > Super; + + constexpr SizeTyped() : Super() {} + 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 000000000..e1738684c --- /dev/null +++ b/gfx/2d/Polygon.h @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_POLYGON_H +#define MOZILLA_GFX_POLYGON_H + +#include "Matrix.h" +#include "mozilla/Move.h" +#include "nsTArray.h" +#include "Point.h" +#include "Triangle.h" + +#include <initializer_list> + +namespace mozilla { +namespace gfx { + +// Polygon3DTyped stores the points of a convex planar polygon. +template<class Units> +class Polygon3DTyped { +public: + Polygon3DTyped() {} + + explicit Polygon3DTyped(const std::initializer_list<Point3DTyped<Units>>& aPoints, + Point3DTyped<Units> aNormal = + Point3DTyped<Units>(0.0f, 0.0f, 1.0f)) + : mNormal(aNormal), mPoints(aPoints) + { +#ifdef DEBUG + EnsurePlanarPolygon(); +#endif + } + + explicit Polygon3DTyped(nsTArray<Point3DTyped<Units>>&& aPoints, + Point3DTyped<Units> aNormal = + Point3DTyped<Units>(0.0f, 0.0f, 1.0f)) + : mNormal(aNormal), mPoints(Move(aPoints)) + { +#ifdef DEBUG + EnsurePlanarPolygon(); +#endif + } + + explicit Polygon3DTyped(const nsTArray<Point3DTyped<Units>>& aPoints, + Point3DTyped<Units> aNormal = + Point3DTyped<Units>(0.0f, 0.0f, 1.0f)) + : mNormal(aNormal), mPoints(aPoints) + { +#ifdef DEBUG + EnsurePlanarPolygon(); +#endif + } + + RectTyped<Units> BoundingBox() const + { + float minX, maxX, minY, maxY; + minX = maxX = mPoints[0].x; + minY = maxY = mPoints[0].y; + + for (const Point3DTyped<Units>& 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); + } + + nsTArray<float> + CalculateDotProducts(const Polygon3DTyped<Units>& aPlane, + size_t& aPos, size_t& aNeg) const + { + // Point classification might produce incorrect results due to numerical + // inaccuracies. Using an epsilon value makes the splitting plane "thicker". + const float epsilon = 0.05f; + + MOZ_ASSERT(!aPlane.GetPoints().IsEmpty()); + const Point3DTyped<Units>& planeNormal = aPlane.GetNormal(); + const Point3DTyped<Units>& planePoint = aPlane[0]; + + aPos = aNeg = 0; + nsTArray<float> dotProducts; + for (const Point3DTyped<Units>& point : mPoints) { + float dot = (point - planePoint).DotProduct(planeNormal); + + if (dot > epsilon) { + aPos++; + } else if (dot < -epsilon) { + aNeg++; + } else { + // The point is within the thick plane. + dot = 0.0f; + } + + dotProducts.AppendElement(dot); + } + + return dotProducts; + } + + // Clips the polygon against the given 2D rectangle. + Polygon3DTyped<Units> ClipPolygon(const RectTyped<Units>& aRect) const + { + Polygon3DTyped<Units> polygon(mPoints, mNormal); + + // Left edge + ClipPolygonWithEdge(polygon, aRect.BottomLeft(), aRect.TopLeft()); + + // Bottom edge + ClipPolygonWithEdge(polygon, aRect.BottomRight(), aRect.BottomLeft()); + + // Right edge + ClipPolygonWithEdge(polygon, aRect.TopRight(), aRect.BottomRight()); + + // Top edge + ClipPolygonWithEdge(polygon, aRect.TopLeft(), aRect.TopRight()); + + return polygon; + } + + const Point3DTyped<Units>& GetNormal() const + { + return mNormal; + } + + const nsTArray<Point3DTyped<Units>>& GetPoints() const + { + return mPoints; + } + + const Point3DTyped<Units>& operator[](size_t aIndex) const + { + MOZ_ASSERT(mPoints.Length() > aIndex); + return mPoints[aIndex]; + } + + void SplitPolygon(const Polygon3DTyped<Units>& aSplittingPlane, + const nsTArray<float>& aDots, + nsTArray<Point3DTyped<Units>>& aBackPoints, + nsTArray<Point3DTyped<Units>>& aFrontPoints) const + { + static const auto Sign = [](const float& f) { + if (f > 0.0f) return 1; + if (f < 0.0f) return -1; + return 0; + }; + + const Point3DTyped<Units>& normal = aSplittingPlane.GetNormal(); + const size_t pointCount = mPoints.Length(); + + for (size_t i = 0; i < pointCount; ++i) { + size_t j = (i + 1) % pointCount; + + const Point3DTyped<Units>& a = mPoints[i]; + const Point3DTyped<Units>& b = mPoints[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 Point3DTyped<Units> ab = b - a; + const float dotAB = ab.DotProduct(normal); + const float t = -dotA / dotAB; + const Point3DTyped<Units> p = a + (ab * t); + + // Add the intersection point to both polygons. + aBackPoints.AppendElement(p); + aFrontPoints.AppendElement(p); + } + } + } + + nsTArray<TriangleTyped<Units>> ToTriangles() const + { + nsTArray<TriangleTyped<Units>> triangles; + + if (mPoints.Length() < 3) { + return triangles; + } + + 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(Move(triangle)); + } + + return triangles; + } + + void TransformToLayerSpace(const Matrix4x4Typed<Units, Units>& aTransform) + { + TransformPoints(aTransform); + mNormal = Point3DTyped<Units>(0.0f, 0.0f, 1.0f); + } + + void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform) + { + TransformPoints(aTransform); + + // Normal vectors should be transformed using inverse transpose. + mNormal = aTransform.Inverse().Transpose().TransformPoint(mNormal); + } + +private: + void ClipPolygonWithEdge(Polygon3DTyped<Units>& aPolygon, + const PointTyped<Units>& aFirst, + const PointTyped<Units>& aSecond) const + { + const Point3DTyped<Units> a(aFirst.x, aFirst.y, 0.0f); + const Point3DTyped<Units> b(aSecond.x, aSecond.y, 0.0f); + const Point3DTyped<Units> normal(b.y - a.y, a.x - b.x, 0.0f); + Polygon3DTyped<Units> plane({a, b}, normal); + + size_t pos, neg; + nsTArray<float> dots = aPolygon.CalculateDotProducts(plane, pos, neg); + + nsTArray<Point3DTyped<Units>> backPoints, frontPoints; + aPolygon.SplitPolygon(plane, dots, backPoints, frontPoints); + + // Only use the points that are behind the clipping plane. + aPolygon = Polygon3DTyped<Units>(Move(backPoints), aPolygon.GetNormal()); + } + +#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. + Point3DTyped<Units> normal; + + for (size_t i = 1; i < mPoints.Length() - 1; ++i) { + normal += + (mPoints[i] - mPoints[0]).CrossProduct(mPoints[i + 1] - mPoints[0]); + } + + // 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 Point3DTyped<Units>& point : mPoints) { + float d = normal.DotProduct(point - mPoints[0]); + MOZ_ASSERT(std::abs(d) < epsilon); + } + } +#endif + void TransformPoints(const Matrix4x4Typed<Units, Units>& aTransform) + { + for (Point3DTyped<Units>& point : mPoints) { + point = aTransform.TransformPoint(point); + } + } + + Point3DTyped<Units> mNormal; + nsTArray<Point3DTyped<Units>> mPoints; +}; + +typedef Polygon3DTyped<UnknownUnits> Polygon3D; + +} // 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 000000000..f56fcb77c --- /dev/null +++ b/gfx/2d/QuartzSupport.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef 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 000000000..464db5ece --- /dev/null +++ b/gfx/2d/QuartzSupport.mm @@ -0,0 +1,625 @@ +/* -*- 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> +#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(); +} + +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); + MacIOSurfaceLib::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB, + GL_RGBA, aWidth * intScaleFactor, + aHeight * intScaleFactor, + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + mIOSurface->mIOSurfacePtr, 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); + MacIOSurfaceLib::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB, + GL_RGBA, mIOSurface->GetDevicePixelWidth(), + mIOSurface->GetDevicePixelHeight(), + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + mIOSurface->mIOSurfacePtr, 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(); + double scaleFactor = surf->GetContentsScaleFactor(); + size_t intScaleFactor = ceil(surf->GetContentsScaleFactor()); + CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(ioData, + ioData, ioHeight*intScaleFactor*(bytesPerRow)*4, + nullptr); //No release callback + if (!dataProvider) { + surf->Unlock(); + return NS_ERROR_FAILURE; + } + + CGImageRef cgImage = ::CGImageCreate(ioWidth * intScaleFactor, + ioHeight * intScaleFactor, 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 * scaleFactor, + aY * scaleFactor, + aWidth * scaleFactor, + aHeight * scaleFactor)); + if (!subImage) { + ::CGImageRelease(cgImage); + surf->Unlock(); + return NS_ERROR_FAILURE; + } + + ::CGContextScaleCTM(aContext, 1.0f, -1.0f); + ::CGContextDrawImage(aContext, + CGRectMake(aX * scaleFactor, + (-(CGFloat)aY - (CGFloat)aHeight) * scaleFactor, + aWidth * scaleFactor, + aHeight * scaleFactor), + 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 000000000..63842d827 --- /dev/null +++ b/gfx/2d/Quaternion.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Quaternion.h" +#include "Matrix.h" +#include "Tools.h" +#include <algorithm> +#include <ostream> +#include <math.h> + +using namespace std; + +namespace mozilla { +namespace gfx { + +std::ostream& +operator<<(std::ostream& aStream, const Quaternion& aQuat) +{ + return aStream << "< " << aQuat.x << " " << aQuat.y << " " << aQuat.z << " " << aQuat.w << ">"; +} + +void +Quaternion::SetFromRotationMatrix(const Matrix4x4& m) +{ + // see http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + const Float trace = m._11 + m._22 + m._33; + if (trace > 0.0) { + const Float s = 0.5f / sqrt(trace + 1.0f); + w = 0.25f / s; + x = (m._32 - m._23) * s; + y = (m._13 - m._31) * s; + z = (m._21 - m._12) * s; + } else if (m._11 > m._22 && m._11 > m._33) { + const Float s = 2.0f * sqrt(1.0f + m._11 - m._22 - m._33); + w = (m._32 - m._23) / s; + x = 0.25f * s; + y = (m._12 + m._21) / s; + z = (m._13 + m._31) / s; + } else if (m._22 > m._33) { + const Float s = 2.0 * sqrt(1.0f + m._22 - m._11 - m._33); + w = (m._13 - m._31) / s; + x = (m._12 + m._21) / s; + y = 0.25f * s; + z = (m._23 + m._32) / s; + } else { + const Float s = 2.0 * sqrt(1.0f + m._33 - m._11 - m._22); + w = (m._21 - m._12) / s; + x = (m._13 + m._31) / s; + y = (m._23 + m._32) / s; + z = 0.25f * s; + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Quaternion.h b/gfx/2d/Quaternion.h new file mode 100644 index 000000000..f14d11348 --- /dev/null +++ b/gfx/2d/Quaternion.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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 { + +class Quaternion +{ +public: + Quaternion() + : x(0.0f), y(0.0f), z(0.0f), w(1.0f) + {} + + Quaternion(Float aX, Float aY, Float aZ, Float aW) + : x(aX), y(aY), z(aZ), w(aW) + {} + + + Quaternion(const Quaternion& aOther) + { + memcpy(this, &aOther, sizeof(*this)); + } + + Float x, y, z, w; + + friend std::ostream& operator<<(std::ostream& aStream, const Quaternion& aQuat); + + void Set(Float aX, Float aY, Float aZ, Float aW) + { + x = aX; y = aY; z = aZ; w = aW; + } + + // Assumes upper 3x3 of aMatrix is a pure rotation matrix (no scaling) + void SetFromRotationMatrix(const Matrix4x4& aMatrix); + + // result = this * aQuat + Quaternion operator*(const Quaternion &aQuat) const + { + Quaternion o; + const Float 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; + } + + Quaternion& operator*=(const Quaternion &aQuat) + { + *this = *this * aQuat; + return *this; + } + + Float Length() const + { + return sqrt(x*x + y*y + z*z + w*w); + } + + Quaternion& Conjugate() + { + x *= -1.f; y *= -1.f; z *= -1.f; + return *this; + } + + Quaternion& Normalize() + { + Float 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; + } + + Quaternion& Invert() + { + return Conjugate().Normalize(); + } + + Point3D RotatePoint(const Point3D& aPoint) { + Float uvx = Float(2.0) * (y*aPoint.z - z*aPoint.y); + Float uvy = Float(2.0) * (z*aPoint.x - x*aPoint.z); + Float uvz = Float(2.0) * (x*aPoint.y - y*aPoint.x); + + return Point3D(aPoint.x + w*uvx + y*uvz - z*uvy, + aPoint.y + w*uvy + z*uvx - x*uvz, + aPoint.z + w*uvz + x*uvy - y*uvx); + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/RadialGradientEffectD2D1.cpp b/gfx/2d/RadialGradientEffectD2D1.cpp new file mode 100644 index 000000000..8f929d8e9 --- /dev/null +++ b/gfx/2d/RadialGradientEffectD2D1.cpp @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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; + } + + Color 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(); +} + +} +} diff --git a/gfx/2d/RadialGradientEffectD2D1.h b/gfx/2d/RadialGradientEffectD2D1.h new file mode 100644 index 000000000..baa02aafb --- /dev/null +++ b/gfx/2d/RadialGradientEffectD2D1.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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); +}; + +} +} +#undef SIMPLE_PROP + +#endif diff --git a/gfx/2d/RecordedEvent.cpp b/gfx/2d/RecordedEvent.cpp new file mode 100644 index 000000000..3bfc5c8f6 --- /dev/null +++ b/gfx/2d/RecordedEvent.cpp @@ -0,0 +1,1883 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 { + +using namespace std; + +static std::string NameFromBackend(BackendType aType) +{ + switch (aType) { + case BackendType::NONE: + return "None"; + case BackendType::DIRECT2D: + return "Direct2D"; + default: + return "Unknown"; + } +} + +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(); +} + +#define LOAD_EVENT_TYPE(_typeenum, _class) \ + case _typeenum: return new _class(aStream) + +RecordedEvent * +RecordedEvent::LoadEventFromStream(std::istream &aStream, EventType aType) +{ + switch (aType) { + LOAD_EVENT_TYPE(DRAWTARGETCREATION, RecordedDrawTargetCreation); + LOAD_EVENT_TYPE(DRAWTARGETDESTRUCTION, RecordedDrawTargetDestruction); + LOAD_EVENT_TYPE(FILLRECT, RecordedFillRect); + LOAD_EVENT_TYPE(STROKERECT, RecordedStrokeRect); + LOAD_EVENT_TYPE(STROKELINE, RecordedStrokeLine); + LOAD_EVENT_TYPE(CLEARRECT, RecordedClearRect); + LOAD_EVENT_TYPE(COPYSURFACE, RecordedCopySurface); + LOAD_EVENT_TYPE(SETTRANSFORM, RecordedSetTransform); + LOAD_EVENT_TYPE(PUSHCLIPRECT, RecordedPushClipRect); + LOAD_EVENT_TYPE(PUSHCLIP, RecordedPushClip); + LOAD_EVENT_TYPE(POPCLIP, RecordedPopClip); + LOAD_EVENT_TYPE(FILL, RecordedFill); + LOAD_EVENT_TYPE(FILLGLYPHS, RecordedFillGlyphs); + LOAD_EVENT_TYPE(MASK, RecordedMask); + LOAD_EVENT_TYPE(STROKE, RecordedStroke); + LOAD_EVENT_TYPE(DRAWSURFACE, RecordedDrawSurface); + LOAD_EVENT_TYPE(DRAWSURFACEWITHSHADOW, RecordedDrawSurfaceWithShadow); + LOAD_EVENT_TYPE(DRAWFILTER, RecordedDrawFilter); + LOAD_EVENT_TYPE(PATHCREATION, RecordedPathCreation); + LOAD_EVENT_TYPE(PATHDESTRUCTION, RecordedPathDestruction); + LOAD_EVENT_TYPE(SOURCESURFACECREATION, RecordedSourceSurfaceCreation); + LOAD_EVENT_TYPE(SOURCESURFACEDESTRUCTION, RecordedSourceSurfaceDestruction); + LOAD_EVENT_TYPE(FILTERNODECREATION, RecordedFilterNodeCreation); + LOAD_EVENT_TYPE(FILTERNODEDESTRUCTION, RecordedFilterNodeDestruction); + LOAD_EVENT_TYPE(GRADIENTSTOPSCREATION, RecordedGradientStopsCreation); + LOAD_EVENT_TYPE(GRADIENTSTOPSDESTRUCTION, RecordedGradientStopsDestruction); + LOAD_EVENT_TYPE(SNAPSHOT, RecordedSnapshot); + LOAD_EVENT_TYPE(SCALEDFONTCREATION, RecordedScaledFontCreation); + LOAD_EVENT_TYPE(SCALEDFONTDESTRUCTION, RecordedScaledFontDestruction); + LOAD_EVENT_TYPE(MASKSURFACE, RecordedMaskSurface); + LOAD_EVENT_TYPE(FILTERNODESETATTRIBUTE, RecordedFilterNodeSetAttribute); + LOAD_EVENT_TYPE(FILTERNODESETINPUT, RecordedFilterNodeSetInput); + LOAD_EVENT_TYPE(CREATESIMILARDRAWTARGET, RecordedCreateSimilarDrawTarget); + LOAD_EVENT_TYPE(FONTDATA, RecordedFontData); + LOAD_EVENT_TYPE(FONTDESC, RecordedFontDescriptor); + LOAD_EVENT_TYPE(PUSHLAYER, RecordedPushLayer); + LOAD_EVENT_TYPE(POPLAYER, RecordedPopLayer); + default: + return nullptr; + } +} + +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 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"; + default: + return "Unknown"; + } +} + +void +RecordedEvent::RecordPatternData(std::ostream &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::SURFACE: + { + WriteElement(aStream, *reinterpret_cast<const SurfacePatternStorage*>(&aPattern.mStorage)); + return; + } + default: + return; + } +} + +void +RecordedEvent::ReadPatternData(std::istream &aStream, PatternStorage &aPattern) const +{ + ReadElement(aStream, aPattern.mType); + + 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::SURFACE: + { + ReadElement(aStream, *reinterpret_cast<SurfacePatternStorage*>(&aPattern.mStorage)); + return; + } + default: + return; + } +} + +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::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; + } + } +} + +void +RecordedEvent::RecordStrokeOptions(std::ostream &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); +} + +void +RecordedEvent::ReadStrokeOptions(std::istream &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); + ReadElement(aStream, joinStyle); + ReadElement(aStream, capStyle); + // 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) { + return; + } + + mDashPatternStorage.resize(aStrokeOptions.mDashLength); + aStrokeOptions.mDashPattern = &mDashPatternStorage.front(); + aStream.read((char*)aStrokeOptions.mDashPattern, sizeof(Float) * aStrokeOptions.mDashLength); +} + +void +RecordedEvent::OutputSimplePatternInfo(const PatternStorage &aStorage, std::stringstream &aOutput) const +{ + switch (aStorage.mType) { + case PatternType::COLOR: + { + const Color color = reinterpret_cast<const ColorPatternStorage*>(&aStorage.mStorage)->mColor; + aOutput << "Color: (" << 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::SURFACE: + { + const SurfacePatternStorage *store = + reinterpret_cast<const SurfacePatternStorage*>(&aStorage.mStorage); + aOutput << "Surface (0x" << store->mSurface << ")"; + return; + } + } +} + +RecordedDrawingEvent::RecordedDrawingEvent(EventType aType, std::istream &aStream) + : RecordedEvent(aType) +{ + ReadElement(aStream, mDT); +} + +void +RecordedDrawingEvent::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mDT); +} + +ReferencePtr +RecordedDrawingEvent::GetObjectRef() const +{ + return mDT; +} + +bool +RecordedDrawTargetCreation::PlayEvent(Translator *aTranslator) const +{ + RefPtr<DrawTarget> newDT = + aTranslator->CreateDrawTarget(mRefPtr, 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; + } + + if (mHasExistingData) { + Rect dataRect(0, 0, mExistingData->GetSize().width, mExistingData->GetSize().height); + newDT->DrawSurface(mExistingData, dataRect, dataRect); + } + + return true; +} + +void +RecordedDrawTargetCreation::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mBackendType); + WriteElement(aStream, mSize); + WriteElement(aStream, mFormat); + WriteElement(aStream, mHasExistingData); + + if (mHasExistingData) { + MOZ_ASSERT(mExistingData); + MOZ_ASSERT(mExistingData->GetSize() == mSize); + RefPtr<DataSourceSurface> dataSurf = mExistingData->GetDataSurface(); + for (int y = 0; y < mSize.height; y++) { + aStream.write((const char*)dataSurf->GetData() + y * dataSurf->Stride(), + BytesPerPixel(mFormat) * mSize.width); + } + } +} + +RecordedDrawTargetCreation::RecordedDrawTargetCreation(istream &aStream) + : RecordedEvent(DRAWTARGETCREATION) + , mExistingData(nullptr) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mBackendType); + ReadElement(aStream, mSize); + ReadElement(aStream, mFormat); + ReadElement(aStream, mHasExistingData); + + if (mHasExistingData) { + RefPtr<DataSourceSurface> dataSurf = Factory::CreateDataSourceSurface(mSize, mFormat); + if (!dataSurf) { + gfxWarning() << "RecordedDrawTargetCreation had to reset mHasExistingData"; + mHasExistingData = false; + return; + } + + for (int y = 0; y < mSize.height; y++) { + aStream.read((char*)dataSurf->GetData() + y * dataSurf->Stride(), + BytesPerPixel(mFormat) * mSize.width); + } + mExistingData = dataSurf; + } +} + +void +RecordedDrawTargetCreation::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] DrawTarget Creation (Type: " << NameFromBackend(mBackendType) << ", Size: " << mSize.width << "x" << mSize.height << ")"; +} + + +bool +RecordedDrawTargetDestruction::PlayEvent(Translator *aTranslator) const +{ + aTranslator->RemoveDrawTarget(mRefPtr); + return true; +} + +void +RecordedDrawTargetDestruction::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); +} + +RecordedDrawTargetDestruction::RecordedDrawTargetDestruction(istream &aStream) + : RecordedEvent(DRAWTARGETDESTRUCTION) +{ + ReadElement(aStream, mRefPtr); +} + +void +RecordedDrawTargetDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] DrawTarget Destruction"; +} + +bool +RecordedCreateSimilarDrawTarget::PlayEvent(Translator *aTranslator) const +{ + RefPtr<DrawTarget> newDT = + aTranslator->GetReferenceDrawTarget()->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; +} + +void +RecordedCreateSimilarDrawTarget::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mSize); + WriteElement(aStream, mFormat); +} + +RecordedCreateSimilarDrawTarget::RecordedCreateSimilarDrawTarget(istream &aStream) + : RecordedEvent(CREATESIMILARDRAWTARGET) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mSize); + ReadElement(aStream, mFormat); +} + +void +RecordedCreateSimilarDrawTarget::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] CreateSimilarDrawTarget (Size: " << mSize.width << "x" << mSize.height << ")"; +} + +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, + mTranslator->LookupGradientStops(storage->mStops), + 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, + mTranslator->LookupGradientStops(storage->mStops), + storage->mMatrix); + return mPattern; + } + default: + return new (mColPat) ColorPattern(Color()); + } + + return mPattern; + } + + union { + char mColPat[sizeof(ColorPattern)]; + char mLinGradPat[sizeof(LinearGradientPattern)]; + char mRadGradPat[sizeof(RadialGradientPattern)]; + char mSurfPat[sizeof(SurfacePattern)]; + }; + + PatternStorage *mStorage; + Pattern *mPattern; + Translator *mTranslator; +}; + +bool +RecordedFillRect::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->FillRect(mRect, *GenericPattern(mPattern, aTranslator), mOptions); + return true; +} + +void +RecordedFillRect::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mRect); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); +} + +RecordedFillRect::RecordedFillRect(istream &aStream) + : RecordedDrawingEvent(FILLRECT, aStream) +{ + ReadElement(aStream, mRect); + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mPattern); +} + +void +RecordedFillRect::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] FillRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height << ") "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +bool +RecordedStrokeRect::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->StrokeRect(mRect, *GenericPattern(mPattern, aTranslator), mStrokeOptions, mOptions); + return true; +} + +void +RecordedStrokeRect::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mRect); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +RecordedStrokeRect::RecordedStrokeRect(istream &aStream) + : RecordedDrawingEvent(STROKERECT, aStream) +{ + ReadElement(aStream, mRect); + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +void +RecordedStrokeRect::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] StrokeRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height + << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +bool +RecordedStrokeLine::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->StrokeLine(mBegin, mEnd, *GenericPattern(mPattern, aTranslator), mStrokeOptions, mOptions); + return true; +} + +void +RecordedStrokeLine::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mBegin); + WriteElement(aStream, mEnd); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +RecordedStrokeLine::RecordedStrokeLine(istream &aStream) + : RecordedDrawingEvent(STROKELINE, aStream) +{ + ReadElement(aStream, mBegin); + ReadElement(aStream, mEnd); + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +void +RecordedStrokeLine::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] StrokeLine (" << mBegin.x << ", " << mBegin.y << " - " << mEnd.x << ", " << mEnd.y + << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +bool +RecordedFill::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->Fill(aTranslator->LookupPath(mPath), *GenericPattern(mPattern, aTranslator), mOptions); + return true; +} + +RecordedFill::RecordedFill(istream &aStream) + : RecordedDrawingEvent(FILL, aStream) +{ + ReadElement(aStream, mPath); + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mPattern); +} + +void +RecordedFill::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mPath); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); +} + +void +RecordedFill::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] Fill (" << mPath << ") "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +RecordedFillGlyphs::~RecordedFillGlyphs() +{ + delete [] mGlyphs; +} + +bool +RecordedFillGlyphs::PlayEvent(Translator *aTranslator) const +{ + GlyphBuffer buffer; + buffer.mGlyphs = mGlyphs; + buffer.mNumGlyphs = mNumGlyphs; + aTranslator->LookupDrawTarget(mDT)->FillGlyphs(aTranslator->LookupScaledFont(mScaledFont), buffer, *GenericPattern(mPattern, aTranslator), mOptions); + return true; +} + +RecordedFillGlyphs::RecordedFillGlyphs(istream &aStream) + : RecordedDrawingEvent(FILLGLYPHS, aStream) +{ + ReadElement(aStream, mScaledFont); + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadElement(aStream, mNumGlyphs); + mGlyphs = new Glyph[mNumGlyphs]; + aStream.read((char*)mGlyphs, sizeof(Glyph) * mNumGlyphs); +} + +void +RecordedFillGlyphs::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mScaledFont); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + WriteElement(aStream, mNumGlyphs); + aStream.write((char*)mGlyphs, sizeof(Glyph) * mNumGlyphs); +} + +void +RecordedFillGlyphs::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] FillGlyphs (" << mScaledFont << ") "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +bool +RecordedMask::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->Mask(*GenericPattern(mSource, aTranslator), *GenericPattern(mMask, aTranslator), mOptions); + return true; +} + +RecordedMask::RecordedMask(istream &aStream) + : RecordedDrawingEvent(MASK, aStream) +{ + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mSource); + ReadPatternData(aStream, mMask); +} + +void +RecordedMask::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mSource); + RecordPatternData(aStream, mMask); +} + +void +RecordedMask::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] Mask (Source: "; + OutputSimplePatternInfo(mSource, aStringStream); + aStringStream << " Mask: "; + OutputSimplePatternInfo(mMask, aStringStream); +} + +bool +RecordedStroke::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->Stroke(aTranslator->LookupPath(mPath), *GenericPattern(mPattern, aTranslator), mStrokeOptions, mOptions); + return true; +} + +void +RecordedStroke::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mPath); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +RecordedStroke::RecordedStroke(istream &aStream) + : RecordedDrawingEvent(STROKE, aStream) +{ + ReadElement(aStream, mPath); + ReadElement(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +void +RecordedStroke::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] Stroke ("<< mPath << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +bool +RecordedClearRect::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->ClearRect(mRect); + return true; +} + +void +RecordedClearRect::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mRect); +} + +RecordedClearRect::RecordedClearRect(istream &aStream) + : RecordedDrawingEvent(CLEARRECT, aStream) +{ + ReadElement(aStream, mRect); +} + +void +RecordedClearRect::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT<< "] ClearRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height << ") "; +} + +bool +RecordedCopySurface::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->CopySurface(aTranslator->LookupSourceSurface(mSourceSurface), + mSourceRect, mDest); + return true; +} + +void +RecordedCopySurface::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mSourceSurface); + WriteElement(aStream, mSourceRect); + WriteElement(aStream, mDest); +} + +RecordedCopySurface::RecordedCopySurface(istream &aStream) + : RecordedDrawingEvent(COPYSURFACE, aStream) +{ + ReadElement(aStream, mSourceSurface); + ReadElement(aStream, mSourceRect); + ReadElement(aStream, mDest); +} + +void +RecordedCopySurface::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT<< "] CopySurface (" << mSourceSurface << ")"; +} + +bool +RecordedPushClip::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->PushClip(aTranslator->LookupPath(mPath)); + return true; +} + +void +RecordedPushClip::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mPath); +} + +RecordedPushClip::RecordedPushClip(istream &aStream) + : RecordedDrawingEvent(PUSHCLIP, aStream) +{ + ReadElement(aStream, mPath); +} + +void +RecordedPushClip::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] PushClip (" << mPath << ") "; +} + +bool +RecordedPushClipRect::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->PushClipRect(mRect); + return true; +} + +void +RecordedPushClipRect::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mRect); +} + +RecordedPushClipRect::RecordedPushClipRect(istream &aStream) + : RecordedDrawingEvent(PUSHCLIPRECT, aStream) +{ + ReadElement(aStream, mRect); +} + +void +RecordedPushClipRect::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] PushClipRect (" << mRect.x << ", " << mRect.y << " - " << mRect.width << " x " << mRect.height << ") "; +} + +bool +RecordedPopClip::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->PopClip(); + return true; +} + +void +RecordedPopClip::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); +} + +RecordedPopClip::RecordedPopClip(istream &aStream) + : RecordedDrawingEvent(POPCLIP, aStream) +{ +} + +void +RecordedPopClip::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] PopClip"; +} + +bool +RecordedPushLayer::PlayEvent(Translator *aTranslator) const +{ + SourceSurface* mask = mMask ? aTranslator->LookupSourceSurface(mMask) + : nullptr; + aTranslator->LookupDrawTarget(mDT)-> + PushLayer(mOpaque, mOpacity, mask, mMaskTransform, mBounds, mCopyBackground); + return true; +} + +void +RecordedPushLayer::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mOpaque); + WriteElement(aStream, mOpacity); + WriteElement(aStream, mMask); + WriteElement(aStream, mMaskTransform); + WriteElement(aStream, mBounds); + WriteElement(aStream, mCopyBackground); +} + +RecordedPushLayer::RecordedPushLayer(istream &aStream) + : RecordedDrawingEvent(PUSHLAYER, aStream) +{ + ReadElement(aStream, mOpaque); + ReadElement(aStream, mOpacity); + ReadElement(aStream, mMask); + ReadElement(aStream, mMaskTransform); + ReadElement(aStream, mBounds); + ReadElement(aStream, mCopyBackground); +} + +void +RecordedPushLayer::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] PushPLayer (Opaque=" << mOpaque << + ", Opacity=" << mOpacity << ", Mask Ref=" << mMask << ") "; +} + +bool +RecordedPopLayer::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->PopLayer(); + return true; +} + +void +RecordedPopLayer::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); +} + +RecordedPopLayer::RecordedPopLayer(istream &aStream) + : RecordedDrawingEvent(POPLAYER, aStream) +{ +} + +void +RecordedPopLayer::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] PopLayer"; +} + +bool +RecordedSetTransform::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)->SetTransform(mTransform); + return true; +} + +void +RecordedSetTransform::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mTransform); +} + +RecordedSetTransform::RecordedSetTransform(istream &aStream) + : RecordedDrawingEvent(SETTRANSFORM, aStream) +{ + ReadElement(aStream, mTransform); +} + +void +RecordedSetTransform::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] SetTransform [ " << mTransform._11 << " " << mTransform._12 << " ; " << + mTransform._21 << " " << mTransform._22 << " ; " << mTransform._31 << " " << mTransform._32 << " ]"; +} + +bool +RecordedDrawSurface::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)-> + DrawSurface(aTranslator->LookupSourceSurface(mRefSource), mDest, mSource, + mDSOptions, mOptions); + return true; +} + +void +RecordedDrawSurface::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mRefSource); + WriteElement(aStream, mDest); + WriteElement(aStream, mSource); + WriteElement(aStream, mDSOptions); + WriteElement(aStream, mOptions); +} + +RecordedDrawSurface::RecordedDrawSurface(istream &aStream) + : RecordedDrawingEvent(DRAWSURFACE, aStream) +{ + ReadElement(aStream, mRefSource); + ReadElement(aStream, mDest); + ReadElement(aStream, mSource); + ReadElement(aStream, mDSOptions); + ReadElement(aStream, mOptions); +} + +void +RecordedDrawSurface::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] DrawSurface (" << mRefSource << ")"; +} + +bool +RecordedDrawFilter::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)-> + DrawFilter(aTranslator->LookupFilterNode(mNode), mSourceRect, + mDestPoint, mOptions); + return true; +} + +void +RecordedDrawFilter::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mNode); + WriteElement(aStream, mSourceRect); + WriteElement(aStream, mDestPoint); + WriteElement(aStream, mOptions); +} + +RecordedDrawFilter::RecordedDrawFilter(istream &aStream) + : RecordedDrawingEvent(DRAWFILTER, aStream) +{ + ReadElement(aStream, mNode); + ReadElement(aStream, mSourceRect); + ReadElement(aStream, mDestPoint); + ReadElement(aStream, mOptions); +} + +void +RecordedDrawFilter::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] DrawFilter (" << mNode << ")"; +} + +bool +RecordedDrawSurfaceWithShadow::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)-> + DrawSurfaceWithShadow(aTranslator->LookupSourceSurface(mRefSource), + mDest, mColor, mOffset, mSigma, mOp); + return true; +} + +void +RecordedDrawSurfaceWithShadow::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + WriteElement(aStream, mRefSource); + WriteElement(aStream, mDest); + WriteElement(aStream, mColor); + WriteElement(aStream, mOffset); + WriteElement(aStream, mSigma); + WriteElement(aStream, mOp); +} + +RecordedDrawSurfaceWithShadow::RecordedDrawSurfaceWithShadow(istream &aStream) + : RecordedDrawingEvent(DRAWSURFACEWITHSHADOW, aStream) +{ + ReadElement(aStream, mRefSource); + ReadElement(aStream, mDest); + ReadElement(aStream, mColor); + ReadElement(aStream, mOffset); + ReadElement(aStream, mSigma); + ReadElement(aStream, mOp); +} + +void +RecordedDrawSurfaceWithShadow::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mDT << "] DrawSurfaceWithShadow (" << mRefSource << ") Color: (" << + mColor.r << ", " << mColor.g << ", " << mColor.b << ", " << mColor.a << ")"; +} + +RecordedPathCreation::RecordedPathCreation(PathRecording *aPath) + : RecordedEvent(PATHCREATION), mRefPtr(aPath), mFillRule(aPath->mFillRule), mPathOps(aPath->mPathOps) +{ +} + +RecordedPathCreation::~RecordedPathCreation() +{ +} + +bool +RecordedPathCreation::PlayEvent(Translator *aTranslator) const +{ + RefPtr<PathBuilder> builder = + aTranslator->GetReferenceDrawTarget()->CreatePathBuilder(mFillRule); + + for (size_t i = 0; i < mPathOps.size(); i++) { + const PathOp &op = mPathOps[i]; + switch (op.mType) { + case PathOp::OP_MOVETO: + builder->MoveTo(op.mP1); + break; + case PathOp::OP_LINETO: + builder->LineTo(op.mP1); + break; + case PathOp::OP_BEZIERTO: + builder->BezierTo(op.mP1, op.mP2, op.mP3); + break; + case PathOp::OP_QUADRATICBEZIERTO: + builder->QuadraticBezierTo(op.mP1, op.mP2); + break; + case PathOp::OP_CLOSE: + builder->Close(); + break; + } + } + + RefPtr<Path> path = builder->Finish(); + aTranslator->AddPath(mRefPtr, path); + return true; +} + +void +RecordedPathCreation::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, uint64_t(mPathOps.size())); + WriteElement(aStream, mFillRule); + typedef std::vector<PathOp> pathOpVec; + for (pathOpVec::const_iterator iter = mPathOps.begin(); iter != mPathOps.end(); iter++) { + WriteElement(aStream, iter->mType); + if (sPointCount[iter->mType] >= 1) { + WriteElement(aStream, iter->mP1); + } + if (sPointCount[iter->mType] >= 2) { + WriteElement(aStream, iter->mP2); + } + if (sPointCount[iter->mType] >= 3) { + WriteElement(aStream, iter->mP3); + } + } + +} + +RecordedPathCreation::RecordedPathCreation(istream &aStream) + : RecordedEvent(PATHCREATION) +{ + uint64_t size; + + ReadElement(aStream, mRefPtr); + ReadElement(aStream, size); + ReadElement(aStream, mFillRule); + + for (uint64_t i = 0; i < size; i++) { + PathOp newPathOp; + ReadElement(aStream, newPathOp.mType); + if (sPointCount[newPathOp.mType] >= 1) { + ReadElement(aStream, newPathOp.mP1); + } + if (sPointCount[newPathOp.mType] >= 2) { + ReadElement(aStream, newPathOp.mP2); + } + if (sPointCount[newPathOp.mType] >= 3) { + ReadElement(aStream, newPathOp.mP3); + } + + mPathOps.push_back(newPathOp); + } + +} + +void +RecordedPathCreation::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] Path created (OpCount: " << mPathOps.size() << ")"; +} +bool +RecordedPathDestruction::PlayEvent(Translator *aTranslator) const +{ + aTranslator->RemovePath(mRefPtr); + return true; +} + +void +RecordedPathDestruction::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); +} + +RecordedPathDestruction::RecordedPathDestruction(istream &aStream) + : RecordedEvent(PATHDESTRUCTION) +{ + ReadElement(aStream, mRefPtr); +} + +void +RecordedPathDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] Path Destroyed"; +} + +RecordedSourceSurfaceCreation::~RecordedSourceSurfaceCreation() +{ + if (mDataOwned) { + delete [] mData; + } +} + +bool +RecordedSourceSurfaceCreation::PlayEvent(Translator *aTranslator) const +{ + if (!mData) { + return false; + } + + RefPtr<SourceSurface> src = aTranslator->GetReferenceDrawTarget()-> + CreateSourceSurfaceFromData(mData, mSize, mSize.width * BytesPerPixel(mFormat), mFormat); + aTranslator->AddSourceSurface(mRefPtr, src); + return true; +} + +void +RecordedSourceSurfaceCreation::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mSize); + WriteElement(aStream, mFormat); + MOZ_ASSERT(mData); + for (int y = 0; y < mSize.height; y++) { + aStream.write((const char*)mData + y * mStride, BytesPerPixel(mFormat) * mSize.width); + } +} + +RecordedSourceSurfaceCreation::RecordedSourceSurfaceCreation(istream &aStream) + : RecordedEvent(SOURCESURFACECREATION), mDataOwned(true) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mSize); + ReadElement(aStream, mFormat); + mData = (uint8_t*)new (fallible) char[mSize.width * mSize.height * BytesPerPixel(mFormat)]; + if (!mData) { + gfxWarning() << "RecordedSourceSurfaceCreation failed to allocate data"; + } else { + aStream.read((char*)mData, mSize.width * mSize.height * BytesPerPixel(mFormat)); + } +} + +void +RecordedSourceSurfaceCreation::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] SourceSurface created (Size: " << mSize.width << "x" << mSize.height << ")"; +} + +bool +RecordedSourceSurfaceDestruction::PlayEvent(Translator *aTranslator) const +{ + aTranslator->RemoveSourceSurface(mRefPtr); + return true; +} + +void +RecordedSourceSurfaceDestruction::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); +} + +RecordedSourceSurfaceDestruction::RecordedSourceSurfaceDestruction(istream &aStream) + : RecordedEvent(SOURCESURFACEDESTRUCTION) +{ + ReadElement(aStream, mRefPtr); +} + +void +RecordedSourceSurfaceDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] SourceSurface Destroyed"; +} + +RecordedFilterNodeCreation::~RecordedFilterNodeCreation() +{ +} + +bool +RecordedFilterNodeCreation::PlayEvent(Translator *aTranslator) const +{ + RefPtr<FilterNode> node = aTranslator->GetReferenceDrawTarget()-> + CreateFilter(mType); + aTranslator->AddFilterNode(mRefPtr, node); + return true; +} + +void +RecordedFilterNodeCreation::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mType); +} + +RecordedFilterNodeCreation::RecordedFilterNodeCreation(istream &aStream) + : RecordedEvent(FILTERNODECREATION) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mType); +} + +void +RecordedFilterNodeCreation::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] FilterNode created (Type: " << int(mType) << ")"; +} + +bool +RecordedFilterNodeDestruction::PlayEvent(Translator *aTranslator) const +{ + aTranslator->RemoveFilterNode(mRefPtr); + return true; +} + +void +RecordedFilterNodeDestruction::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); +} + +RecordedFilterNodeDestruction::RecordedFilterNodeDestruction(istream &aStream) + : RecordedEvent(FILTERNODEDESTRUCTION) +{ + ReadElement(aStream, mRefPtr); +} + +void +RecordedFilterNodeDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] FilterNode Destroyed"; +} + +RecordedGradientStopsCreation::~RecordedGradientStopsCreation() +{ + if (mDataOwned) { + delete [] mStops; + } +} + +bool +RecordedGradientStopsCreation::PlayEvent(Translator *aTranslator) const +{ + RefPtr<GradientStops> src = aTranslator->GetReferenceDrawTarget()-> + CreateGradientStops(mStops, mNumStops, mExtendMode); + aTranslator->AddGradientStops(mRefPtr, src); + return true; +} + +void +RecordedGradientStopsCreation::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mExtendMode); + WriteElement(aStream, mNumStops); + aStream.write((const char*)mStops, mNumStops * sizeof(GradientStop)); +} + +RecordedGradientStopsCreation::RecordedGradientStopsCreation(istream &aStream) + : RecordedEvent(GRADIENTSTOPSCREATION), mDataOwned(true) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mExtendMode); + ReadElement(aStream, mNumStops); + mStops = new GradientStop[mNumStops]; + + aStream.read((char*)mStops, mNumStops * sizeof(GradientStop)); +} + +void +RecordedGradientStopsCreation::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] GradientStops created (Stops: " << mNumStops << ")"; +} + +bool +RecordedGradientStopsDestruction::PlayEvent(Translator *aTranslator) const +{ + aTranslator->RemoveGradientStops(mRefPtr); + return true; +} + +void +RecordedGradientStopsDestruction::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); +} + +RecordedGradientStopsDestruction::RecordedGradientStopsDestruction(istream &aStream) + : RecordedEvent(GRADIENTSTOPSDESTRUCTION) +{ + ReadElement(aStream, mRefPtr); +} + +void +RecordedGradientStopsDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] GradientStops Destroyed"; +} + +bool +RecordedSnapshot::PlayEvent(Translator *aTranslator) const +{ + RefPtr<SourceSurface> src = aTranslator->LookupDrawTarget(mDT)->Snapshot(); + aTranslator->AddSourceSurface(mRefPtr, src); + return true; +} + +void +RecordedSnapshot::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mDT); +} + +RecordedSnapshot::RecordedSnapshot(istream &aStream) + : RecordedEvent(SNAPSHOT) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mDT); +} + +void +RecordedSnapshot::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] Snapshot Created (DT: " << mDT << ")"; +} + +RecordedFontData::~RecordedFontData() +{ + delete[] mData; +} + +bool +RecordedFontData::PlayEvent(Translator *aTranslator) const +{ + RefPtr<NativeFontResource> fontResource = + Factory::CreateNativeFontResource(mData, mFontDetails.size, + aTranslator->GetDesiredFontType()); + if (!fontResource) { + return false; + } + + aTranslator->AddNativeFontResource(mFontDetails.fontDataKey, fontResource); + return true; +} + +void +RecordedFontData::RecordToStream(std::ostream &aStream) const +{ + MOZ_ASSERT(mGetFontFileDataSucceeded); + + WriteElement(aStream, mFontDetails.fontDataKey); + WriteElement(aStream, mFontDetails.size); + aStream.write((const char*)mData, mFontDetails.size); +} + +void +RecordedFontData::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "Font Data of size " << mFontDetails.size; +} + +void +RecordedFontData::SetFontData(const uint8_t *aData, uint32_t aSize, uint32_t aIndex, Float aGlyphSize) +{ + mData = new uint8_t[aSize]; + memcpy(mData, aData, aSize); + mFontDetails.fontDataKey = SFNTData::GetUniqueKey(aData, aSize); + mFontDetails.size = aSize; + mFontDetails.index = aIndex; + mFontDetails.glyphSize = aGlyphSize; +} + +bool +RecordedFontData::GetFontDetails(RecordedFontDetails& fontDetails) +{ + if (!mGetFontFileDataSucceeded) { + return false; + } + + fontDetails.fontDataKey = mFontDetails.fontDataKey; + fontDetails.size = mFontDetails.size; + fontDetails.glyphSize = mFontDetails.glyphSize; + fontDetails.index = mFontDetails.index; + return true; +} + +RecordedFontData::RecordedFontData(istream &aStream) + : RecordedEvent(FONTDATA) +{ + ReadElement(aStream, mFontDetails.fontDataKey); + ReadElement(aStream, mFontDetails.size); + mData = new uint8_t[mFontDetails.size]; + aStream.read((char*)mData, mFontDetails.size); +} + +RecordedFontDescriptor::~RecordedFontDescriptor() +{ +} + +bool +RecordedFontDescriptor::PlayEvent(Translator *aTranslator) const +{ + MOZ_ASSERT(mType == FontType::GDI); + + NativeFont nativeFont; + nativeFont.mType = (NativeFontType)mType; + nativeFont.mFont = (void*)&mData[0]; + + RefPtr<ScaledFont> font = + Factory::CreateScaledFontForNativeFont(nativeFont, mFontSize); + +#ifdef USE_CAIRO_SCALED_FONT + static_cast<ScaledFontBase*>(font.get())->PopulateCairoScaledFont(); +#endif + + aTranslator->AddScaledFont(mRefPtr, font); + return true; +} + +void +RecordedFontDescriptor::RecordToStream(std::ostream &aStream) const +{ + MOZ_ASSERT(mHasDesc); + WriteElement(aStream, mType); + WriteElement(aStream, mFontSize); + WriteElement(aStream, mRefPtr); + WriteElement(aStream, (size_t)mData.size()); + aStream.write((char*)&mData[0], mData.size()); +} + +void +RecordedFontDescriptor::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] Font Descriptor"; +} + +void +RecordedFontDescriptor::SetFontDescriptor(const uint8_t* aData, uint32_t aSize, Float aFontSize) +{ + mData.assign(aData, aData + aSize); + mFontSize = aFontSize; +} + +RecordedFontDescriptor::RecordedFontDescriptor(istream &aStream) + : RecordedEvent(FONTDATA) +{ + ReadElement(aStream, mType); + ReadElement(aStream, mFontSize); + ReadElement(aStream, mRefPtr); + + size_t size; + ReadElement(aStream, size); + mData.resize(size); + aStream.read((char*)&mData[0], size); +} + +bool +RecordedScaledFontCreation::PlayEvent(Translator *aTranslator) const +{ + NativeFontResource *fontResource = aTranslator->LookupNativeFontResource(mFontDataKey); + if (!fontResource) { + gfxDevCrash(LogReason::NativeFontResourceNotFound) << + "NativeFontResource lookup failed for key |" << hexa(mFontDataKey) << "|."; + return false; + } + + RefPtr<ScaledFont> scaledFont = + fontResource->CreateScaledFont(mIndex, mGlyphSize, mInstanceData.data(), mInstanceData.size()); + aTranslator->AddScaledFont(mRefPtr, scaledFont); + return true; +} + +void +RecordedScaledFontCreation::RecordToStream(std::ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mFontDataKey); + WriteElement(aStream, mIndex); + WriteElement(aStream, mGlyphSize); + WriteElement(aStream, (size_t)mInstanceData.size()); + aStream.write((char*)mInstanceData.data(), mInstanceData.size()); +} + +void +RecordedScaledFontCreation::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] ScaledFont Created"; +} + +void +RecordedScaledFontCreation::SetFontInstanceData(const uint8_t *aData, uint32_t aSize) +{ + mInstanceData.assign(aData, aData + aSize); +} + +RecordedScaledFontCreation::RecordedScaledFontCreation(istream &aStream) + : RecordedEvent(SCALEDFONTCREATION) +{ + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mFontDataKey); + ReadElement(aStream, mIndex); + ReadElement(aStream, mGlyphSize); + + size_t size; + ReadElement(aStream, size); + mInstanceData.resize(size); + aStream.read((char*)mInstanceData.data(), size); +} + +bool +RecordedScaledFontDestruction::PlayEvent(Translator *aTranslator) const +{ + aTranslator->RemoveScaledFont(mRefPtr); + return true; +} + +void +RecordedScaledFontDestruction::RecordToStream(ostream &aStream) const +{ + WriteElement(aStream, mRefPtr); +} + +RecordedScaledFontDestruction::RecordedScaledFontDestruction(istream &aStream) + : RecordedEvent(SCALEDFONTDESTRUCTION) +{ + ReadElement(aStream, mRefPtr); +} + +void +RecordedScaledFontDestruction::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mRefPtr << "] ScaledFont Destroyed"; +} + +bool +RecordedMaskSurface::PlayEvent(Translator *aTranslator) const +{ + aTranslator->LookupDrawTarget(mDT)-> + MaskSurface(*GenericPattern(mPattern, aTranslator), + aTranslator->LookupSourceSurface(mRefMask), + mOffset, mOptions); + return true; +} + +void +RecordedMaskSurface::RecordToStream(ostream &aStream) const +{ + RecordedDrawingEvent::RecordToStream(aStream); + RecordPatternData(aStream, mPattern); + WriteElement(aStream, mRefMask); + WriteElement(aStream, mOffset); + WriteElement(aStream, mOptions); +} + +RecordedMaskSurface::RecordedMaskSurface(istream &aStream) + : RecordedDrawingEvent(MASKSURFACE, aStream) +{ + ReadPatternData(aStream, mPattern); + ReadElement(aStream, mRefMask); + ReadElement(aStream, mOffset); + ReadElement(aStream, mOptions); +} + +void +RecordedMaskSurface::OutputSimpleEventInfo(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); +} + +bool +RecordedFilterNodeSetAttribute::PlayEvent(Translator *aTranslator) const +{ +#define REPLAY_SET_ATTRIBUTE(type, argtype) \ + case ARGTYPE_##argtype: \ + ReplaySetAttribute(aTranslator->LookupFilterNode(mNode), 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(Color, COLOR); + case ARGTYPE_FLOAT_ARRAY: + aTranslator->LookupFilterNode(mNode)->SetAttribute( + mIndex, + reinterpret_cast<const Float*>(&mPayload.front()), + mPayload.size() / sizeof(Float)); + break; + } + + return true; +} + +void +RecordedFilterNodeSetAttribute::RecordToStream(ostream &aStream) const +{ + RecordedEvent::RecordToStream(aStream); + WriteElement(aStream, mNode); + WriteElement(aStream, mIndex); + WriteElement(aStream, mArgType); + WriteElement(aStream, uint64_t(mPayload.size())); + aStream.write((const char*)&mPayload.front(), mPayload.size()); +} + +RecordedFilterNodeSetAttribute::RecordedFilterNodeSetAttribute(istream &aStream) + : RecordedEvent(FILTERNODESETATTRIBUTE) +{ + ReadElement(aStream, mNode); + ReadElement(aStream, mIndex); + ReadElement(aStream, mArgType); + uint64_t size; + ReadElement(aStream, size); + mPayload.resize(size_t(size)); + aStream.read((char*)&mPayload.front(), size); +} + +void +RecordedFilterNodeSetAttribute::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ")"; +} + +bool +RecordedFilterNodeSetInput::PlayEvent(Translator *aTranslator) const +{ + if (mInputFilter) { + aTranslator->LookupFilterNode(mNode)->SetInput( + mIndex, aTranslator->LookupFilterNode(mInputFilter)); + } else { + aTranslator->LookupFilterNode(mNode)->SetInput( + mIndex, aTranslator->LookupSourceSurface(mInputSurface)); + } + + return true; +} + +void +RecordedFilterNodeSetInput::RecordToStream(ostream &aStream) const +{ + RecordedEvent::RecordToStream(aStream); + WriteElement(aStream, mNode); + WriteElement(aStream, mIndex); + WriteElement(aStream, mInputFilter); + WriteElement(aStream, mInputSurface); +} + +RecordedFilterNodeSetInput::RecordedFilterNodeSetInput(istream &aStream) + : RecordedEvent(FILTERNODESETINPUT) +{ + ReadElement(aStream, mNode); + ReadElement(aStream, mIndex); + ReadElement(aStream, mInputFilter); + ReadElement(aStream, mInputSurface); +} + +void +RecordedFilterNodeSetInput::OutputSimpleEventInfo(stringstream &aStringStream) const +{ + aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ", "; + + if (mInputFilter) { + aStringStream << "Filter: " << mInputFilter; + } else { + aStringStream << "Surface: " << mInputSurface; + } + + aStringStream << ")"; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h new file mode 100644 index 000000000..bf660ba24 --- /dev/null +++ b/gfx/2d/RecordedEvent.h @@ -0,0 +1,1281 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECORDEDEVENT_H_ +#define MOZILLA_GFX_RECORDEDEVENT_H_ + +#include "2D.h" +#include <ostream> +#include <sstream> +#include <cstring> +#include <vector> + +namespace mozilla { +namespace gfx { + +struct PathOp; +class PathRecording; + +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 = 6; +// A change in minor revision means additions of new events. New streams will +// not play in older players. +const uint16_t kMinorRevision = 0; + +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; + uint32_t size; + uint32_t index; + Float glyphSize; +}; + +// 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() {} + + 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 GradientStops *LookupGradientStops(ReferencePtr aRefPtr) = 0; + virtual ScaledFont *LookupScaledFont(ReferencePtr aRefPtr) = 0; + virtual NativeFontResource *LookupNativeFontResource(uint64_t aKey) = 0; + 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; + 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 AddNativeFontResource(uint64_t aKey, + NativeFontResource *aNativeFontResource) = 0; + + virtual already_AddRefed<DrawTarget> CreateDrawTarget(ReferencePtr aRefPtr, + const IntSize &aSize, + SurfaceFormat aFormat); + virtual DrawTarget *GetReferenceDrawTarget() = 0; + virtual FontType GetDesiredFontType() = 0; +}; + +struct ColorPatternStorage +{ + Color 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 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 mSurface[sizeof(SurfacePatternStorage)]; + }; +}; + +class RecordedEvent { +public: + enum EventType { + DRAWTARGETCREATION = 0, + DRAWTARGETDESTRUCTION, + FILLRECT, + STROKERECT, + STROKELINE, + CLEARRECT, + COPYSURFACE, + SETTRANSFORM, + PUSHCLIP, + PUSHCLIPRECT, + POPCLIP, + FILL, + FILLGLYPHS, + MASK, + STROKE, + DRAWSURFACE, + DRAWSURFACEWITHSHADOW, + PATHCREATION, + PATHDESTRUCTION, + SOURCESURFACECREATION, + SOURCESURFACEDESTRUCTION, + GRADIENTSTOPSCREATION, + GRADIENTSTOPSDESTRUCTION, + SNAPSHOT, + SCALEDFONTCREATION, + SCALEDFONTDESTRUCTION, + MASKSURFACE, + FILTERNODECREATION, + FILTERNODEDESTRUCTION, + DRAWFILTER, + FILTERNODESETATTRIBUTE, + FILTERNODESETINPUT, + CREATESIMILARDRAWTARGET, + FONTDATA, + FONTDESC, + PUSHLAYER, + POPLAYER, + }; + static const uint32_t kTotalEventTypes = RecordedEvent::FILTERNODESETINPUT + 1; + + virtual ~RecordedEvent() {} + + 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 {} + + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const { } + + void RecordPatternData(std::ostream &aStream, const PatternStorage &aPatternStorage) const; + void ReadPatternData(std::istream &aStream, PatternStorage &aPatternStorage) const; + void StorePattern(PatternStorage &aDestination, const Pattern &aSource) const; + void RecordStrokeOptions(std::ostream &aStream, const StrokeOptions &aStrokeOptions) const; + void ReadStrokeOptions(std::istream &aStream, StrokeOptions &aStrokeOptions); + + virtual std::string GetName() const = 0; + + virtual ReferencePtr GetObjectRef() const = 0; + + virtual ReferencePtr GetDestinedDT() { return nullptr; } + + void OutputSimplePatternInfo(const PatternStorage &aStorage, std::stringstream &aOutput) const; + + static RecordedEvent *LoadEventFromStream(std::istream &aStream, EventType aType); + + EventType GetType() { return (EventType)mType; } +protected: + friend class DrawEventRecorderPrivate; + + MOZ_IMPLICIT RecordedEvent(int32_t aType) : mType(aType) + {} + + int32_t mType; + std::vector<Float> mDashPatternStorage; +}; + +class RecordedDrawingEvent : public RecordedEvent +{ +public: + virtual ReferencePtr GetDestinedDT() { return mDT; } + +protected: + RecordedDrawingEvent(EventType aType, DrawTarget *aTarget) + : RecordedEvent(aType), mDT(aTarget) + { + } + + RecordedDrawingEvent(EventType aType, std::istream &aStream); + virtual void RecordToStream(std::ostream &aStream) const; + + virtual ReferencePtr GetObjectRef() const; + + ReferencePtr mDT; +}; + +class RecordedDrawTargetCreation : public RecordedEvent { +public: + RecordedDrawTargetCreation(ReferencePtr aRefPtr, BackendType aType, const IntSize &aSize, SurfaceFormat aFormat, + bool aHasExistingData = false, SourceSurface *aExistingData = nullptr) + : RecordedEvent(DRAWTARGETCREATION), mRefPtr(aRefPtr), mBackendType(aType), mSize(aSize), mFormat(aFormat) + , mHasExistingData(aHasExistingData), mExistingData(aExistingData) + {} + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "DrawTarget Creation"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } + + ReferencePtr mRefPtr; + BackendType mBackendType; + IntSize mSize; + SurfaceFormat mFormat; + bool mHasExistingData; + RefPtr<SourceSurface> mExistingData; + +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedDrawTargetCreation(std::istream &aStream); +}; + +class RecordedDrawTargetDestruction : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedDrawTargetDestruction(ReferencePtr aRefPtr) + : RecordedEvent(DRAWTARGETDESTRUCTION), mRefPtr(aRefPtr) + {} + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "DrawTarget Destruction"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } + + ReferencePtr mRefPtr; + + BackendType mBackendType; +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedDrawTargetDestruction(std::istream &aStream); +}; + +class RecordedCreateSimilarDrawTarget : public RecordedEvent +{ +public: + RecordedCreateSimilarDrawTarget(ReferencePtr aRefPtr, const IntSize &aSize, + SurfaceFormat aFormat) + : RecordedEvent(CREATESIMILARDRAWTARGET) + , mRefPtr(aRefPtr) , mSize(aSize), mFormat(aFormat) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "CreateSimilarDrawTarget"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } + + ReferencePtr mRefPtr; + IntSize mSize; + SurfaceFormat mFormat; + +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedCreateSimilarDrawTarget(std::istream &aStream); +}; + +class RecordedFillRect : public RecordedDrawingEvent { +public: + RecordedFillRect(DrawTarget *aDT, const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions) + : RecordedDrawingEvent(FILLRECT, aDT), mRect(aRect), mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "FillRect"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedFillRect(std::istream &aStream); + + Rect mRect; + PatternStorage mPattern; + DrawOptions mOptions; +}; + +class RecordedStrokeRect : public RecordedDrawingEvent { +public: + RecordedStrokeRect(DrawTarget *aDT, const Rect &aRect, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) + : RecordedDrawingEvent(STROKERECT, aDT), mRect(aRect), + mStrokeOptions(aStrokeOptions), mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "StrokeRect"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedStrokeRect(std::istream &aStream); + + Rect mRect; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedStrokeLine : public RecordedDrawingEvent { +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), + mStrokeOptions(aStrokeOptions), mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "StrokeLine"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedStrokeLine(std::istream &aStream); + + Point mBegin; + Point mEnd; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedFill : public RecordedDrawingEvent { +public: + RecordedFill(DrawTarget *aDT, ReferencePtr aPath, const Pattern &aPattern, const DrawOptions &aOptions) + : RecordedDrawingEvent(FILL, aDT), mPath(aPath), mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Fill"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedFill(std::istream &aStream); + + ReferencePtr mPath; + PatternStorage mPattern; + DrawOptions mOptions; +}; + +class RecordedFillGlyphs : public RecordedDrawingEvent { +public: + RecordedFillGlyphs(DrawTarget *aDT, ReferencePtr aScaledFont, const Pattern &aPattern, const DrawOptions &aOptions, + const Glyph *aGlyphs, uint32_t aNumGlyphs) + : RecordedDrawingEvent(FILLGLYPHS, aDT), mScaledFont(aScaledFont), mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + mNumGlyphs = aNumGlyphs; + mGlyphs = new Glyph[aNumGlyphs]; + memcpy(mGlyphs, aGlyphs, sizeof(Glyph) * aNumGlyphs); + } + virtual ~RecordedFillGlyphs(); + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "FillGlyphs"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedFillGlyphs(std::istream &aStream); + + ReferencePtr mScaledFont; + PatternStorage mPattern; + DrawOptions mOptions; + Glyph *mGlyphs; + uint32_t mNumGlyphs; +}; + +class RecordedMask : public RecordedDrawingEvent { +public: + RecordedMask(DrawTarget *aDT, const Pattern &aSource, const Pattern &aMask, const DrawOptions &aOptions) + : RecordedDrawingEvent(MASK, aDT), mOptions(aOptions) + { + StorePattern(mSource, aSource); + StorePattern(mMask, aMask); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Mask"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedMask(std::istream &aStream); + + PatternStorage mSource; + PatternStorage mMask; + DrawOptions mOptions; +}; + +class RecordedStroke : public RecordedDrawingEvent { +public: + RecordedStroke(DrawTarget *aDT, ReferencePtr aPath, const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) + : RecordedDrawingEvent(STROKE, aDT), mPath(aPath), + mStrokeOptions(aStrokeOptions), mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Stroke"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedStroke(std::istream &aStream); + + ReferencePtr mPath; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedClearRect : public RecordedDrawingEvent { +public: + RecordedClearRect(DrawTarget *aDT, const Rect &aRect) + : RecordedDrawingEvent(CLEARRECT, aDT), mRect(aRect) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "ClearRect"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedClearRect(std::istream &aStream); + + Rect mRect; +}; + +class RecordedCopySurface : public RecordedDrawingEvent { +public: + RecordedCopySurface(DrawTarget *aDT, ReferencePtr aSourceSurface, + const IntRect &aSourceRect, const IntPoint &aDest) + : RecordedDrawingEvent(COPYSURFACE, aDT), mSourceSurface(aSourceSurface), + mSourceRect(aSourceRect), mDest(aDest) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "CopySurface"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedCopySurface(std::istream &aStream); + + ReferencePtr mSourceSurface; + IntRect mSourceRect; + IntPoint mDest; +}; + +class RecordedPushClip : public RecordedDrawingEvent { +public: + RecordedPushClip(DrawTarget *aDT, ReferencePtr aPath) + : RecordedDrawingEvent(PUSHCLIP, aDT), mPath(aPath) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "PushClip"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedPushClip(std::istream &aStream); + + ReferencePtr mPath; +}; + +class RecordedPushClipRect : public RecordedDrawingEvent { +public: + RecordedPushClipRect(DrawTarget *aDT, const Rect &aRect) + : RecordedDrawingEvent(PUSHCLIPRECT, aDT), mRect(aRect) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "PushClipRect"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedPushClipRect(std::istream &aStream); + + Rect mRect; +}; + +class RecordedPopClip : public RecordedDrawingEvent { +public: + MOZ_IMPLICIT RecordedPopClip(DrawTarget *aDT) + : RecordedDrawingEvent(POPCLIP, aDT) + {} + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "PopClip"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedPopClip(std::istream &aStream); +}; + +class RecordedPushLayer : public RecordedDrawingEvent { +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) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "PushLayer"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedPushLayer(std::istream &aStream); + + bool mOpaque; + Float mOpacity; + ReferencePtr mMask; + Matrix mMaskTransform; + IntRect mBounds; + bool mCopyBackground; +}; + +class RecordedPopLayer : public RecordedDrawingEvent { +public: + MOZ_IMPLICIT RecordedPopLayer(DrawTarget* aDT) + : RecordedDrawingEvent(POPLAYER, aDT) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "PopLayer"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedPopLayer(std::istream &aStream); +}; + +class RecordedSetTransform : public RecordedDrawingEvent { +public: + RecordedSetTransform(DrawTarget *aDT, const Matrix &aTransform) + : RecordedDrawingEvent(SETTRANSFORM, aDT), mTransform(aTransform) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "SetTransform"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedSetTransform(std::istream &aStream); + + Matrix mTransform; +}; + +class RecordedDrawSurface : public RecordedDrawingEvent { +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) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "DrawSurface"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedDrawSurface(std::istream &aStream); + + ReferencePtr mRefSource; + Rect mDest; + Rect mSource; + DrawSurfaceOptions mDSOptions; + DrawOptions mOptions; +}; + +class RecordedDrawSurfaceWithShadow : public RecordedDrawingEvent { +public: + RecordedDrawSurfaceWithShadow(DrawTarget *aDT, ReferencePtr aRefSource, const Point &aDest, + const Color &aColor, const Point &aOffset, + Float aSigma, CompositionOp aOp) + : RecordedDrawingEvent(DRAWSURFACEWITHSHADOW, aDT), mRefSource(aRefSource), mDest(aDest) + , mColor(aColor), mOffset(aOffset), mSigma(aSigma), mOp(aOp) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "DrawSurfaceWithShadow"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedDrawSurfaceWithShadow(std::istream &aStream); + + ReferencePtr mRefSource; + Point mDest; + Color mColor; + Point mOffset; + Float mSigma; + CompositionOp mOp; +}; + +class RecordedDrawFilter : public RecordedDrawingEvent { +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) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "DrawFilter"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedDrawFilter(std::istream &aStream); + + ReferencePtr mNode; + Rect mSourceRect; + Point mDestPoint; + DrawOptions mOptions; +}; + +class RecordedPathCreation : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedPathCreation(PathRecording *aPath); + ~RecordedPathCreation(); + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Path Creation"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + FillRule mFillRule; + std::vector<PathOp> mPathOps; + + MOZ_IMPLICIT RecordedPathCreation(std::istream &aStream); +}; + +class RecordedPathDestruction : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedPathDestruction(PathRecording *aPath) + : RecordedEvent(PATHDESTRUCTION), mRefPtr(aPath) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Path Destruction"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + MOZ_IMPLICIT RecordedPathDestruction(std::istream &aStream); +}; + +class RecordedSourceSurfaceCreation : public RecordedEvent { +public: + RecordedSourceSurfaceCreation(ReferencePtr aRefPtr, uint8_t *aData, int32_t aStride, + const IntSize &aSize, SurfaceFormat aFormat) + : RecordedEvent(SOURCESURFACECREATION), mRefPtr(aRefPtr), mData(aData) + , mStride(aStride), mSize(aSize), mFormat(aFormat), mDataOwned(false) + { + } + + ~RecordedSourceSurfaceCreation(); + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "SourceSurface Creation"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + uint8_t *mData; + int32_t mStride; + IntSize mSize; + SurfaceFormat mFormat; + bool mDataOwned; + + MOZ_IMPLICIT RecordedSourceSurfaceCreation(std::istream &aStream); +}; + +class RecordedSourceSurfaceDestruction : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedSourceSurfaceDestruction(ReferencePtr aRefPtr) + : RecordedEvent(SOURCESURFACEDESTRUCTION), mRefPtr(aRefPtr) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "SourceSurface Destruction"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + MOZ_IMPLICIT RecordedSourceSurfaceDestruction(std::istream &aStream); +}; + +class RecordedFilterNodeCreation : public RecordedEvent { +public: + RecordedFilterNodeCreation(ReferencePtr aRefPtr, FilterType aType) + : RecordedEvent(FILTERNODECREATION), mRefPtr(aRefPtr), mType(aType) + { + } + + ~RecordedFilterNodeCreation(); + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "FilterNode Creation"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + FilterType mType; + + MOZ_IMPLICIT RecordedFilterNodeCreation(std::istream &aStream); +}; + +class RecordedFilterNodeDestruction : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedFilterNodeDestruction(ReferencePtr aRefPtr) + : RecordedEvent(FILTERNODEDESTRUCTION), mRefPtr(aRefPtr) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "FilterNode Destruction"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + MOZ_IMPLICIT RecordedFilterNodeDestruction(std::istream &aStream); +}; + +class RecordedGradientStopsCreation : public RecordedEvent { +public: + RecordedGradientStopsCreation(ReferencePtr aRefPtr, GradientStop *aStops, + uint32_t aNumStops, ExtendMode aExtendMode) + : RecordedEvent(GRADIENTSTOPSCREATION), mRefPtr(aRefPtr), mStops(aStops) + , mNumStops(aNumStops), mExtendMode(aExtendMode), mDataOwned(false) + { + } + + ~RecordedGradientStopsCreation(); + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "GradientStops Creation"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + GradientStop *mStops; + uint32_t mNumStops; + ExtendMode mExtendMode; + bool mDataOwned; + + MOZ_IMPLICIT RecordedGradientStopsCreation(std::istream &aStream); +}; + +class RecordedGradientStopsDestruction : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedGradientStopsDestruction(ReferencePtr aRefPtr) + : RecordedEvent(GRADIENTSTOPSDESTRUCTION), mRefPtr(aRefPtr) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "GradientStops Destruction"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + MOZ_IMPLICIT RecordedGradientStopsDestruction(std::istream &aStream); +}; + +class RecordedSnapshot : public RecordedEvent { +public: + RecordedSnapshot(ReferencePtr aRefPtr, DrawTarget *aDT) + : RecordedEvent(SNAPSHOT), mRefPtr(aRefPtr), mDT(aDT) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Snapshot"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + ReferencePtr mDT; + + MOZ_IMPLICIT RecordedSnapshot(std::istream &aStream); +}; + +class RecordedFontData : public RecordedEvent { +public: + + static void FontDataProc(const uint8_t *aData, uint32_t aSize, + uint32_t aIndex, Float aGlyphSize, void* aBaton) + { + auto recordedFontData = static_cast<RecordedFontData*>(aBaton); + recordedFontData->SetFontData(aData, aSize, aIndex, aGlyphSize); + } + + explicit RecordedFontData(ScaledFont *aScaledFont) + : RecordedEvent(FONTDATA), mData(nullptr) + { + mGetFontFileDataSucceeded = aScaledFont->GetFontFileData(&FontDataProc, this); + } + + ~RecordedFontData(); + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Font Data"; } + virtual ReferencePtr GetObjectRef() const { return nullptr; }; + + void SetFontData(const uint8_t *aData, uint32_t aSize, uint32_t aIndex, + Float aGlyphSize); + + bool GetFontDetails(RecordedFontDetails& fontDetails); + +private: + friend class RecordedEvent; + + uint8_t *mData; + RecordedFontDetails mFontDetails; + + bool mGetFontFileDataSucceeded = false; + + MOZ_IMPLICIT RecordedFontData(std::istream &aStream); +}; + +class RecordedFontDescriptor : public RecordedEvent { +public: + + static void FontDescCb(const uint8_t *aData, uint32_t aSize, + Float aFontSize, void* aBaton) + { + auto recordedFontDesc = static_cast<RecordedFontDescriptor*>(aBaton); + recordedFontDesc->SetFontDescriptor(aData, aSize, aFontSize); + } + + explicit RecordedFontDescriptor(ScaledFont* aScaledFont) + : RecordedEvent(FONTDESC) + , mType(aScaledFont->GetType()) + , mRefPtr(aScaledFont) + { + mHasDesc = aScaledFont->GetFontDescriptor(FontDescCb, this); + } + + ~RecordedFontDescriptor(); + + bool IsValid() const { return mHasDesc; } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "Font Desc"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } + +private: + friend class RecordedEvent; + + void SetFontDescriptor(const uint8_t* aData, uint32_t aSize, Float aFontSize); + + bool mHasDesc; + + FontType mType; + Float mFontSize; + std::vector<uint8_t> mData; + ReferencePtr mRefPtr; + + MOZ_IMPLICIT RecordedFontDescriptor(std::istream &aStream); +}; + +class RecordedScaledFontCreation : public RecordedEvent { +public: + + static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize, void* aBaton) + { + auto recordedScaledFontCreation = static_cast<RecordedScaledFontCreation*>(aBaton); + recordedScaledFontCreation->SetFontInstanceData(aData, aSize); + } + + RecordedScaledFontCreation(ScaledFont* aScaledFont, + RecordedFontDetails aFontDetails) + : RecordedEvent(SCALEDFONTCREATION), mRefPtr(aScaledFont) + , mFontDataKey(aFontDetails.fontDataKey) + , mGlyphSize(aFontDetails.glyphSize) , mIndex(aFontDetails.index) + { + aScaledFont->GetFontInstanceData(FontInstanceDataProc, this); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "ScaledFont Creation"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } + + void SetFontInstanceData(const uint8_t *aData, uint32_t aSize); + +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + uint64_t mFontDataKey; + Float mGlyphSize; + uint32_t mIndex; + std::vector<uint8_t> mInstanceData; + + MOZ_IMPLICIT RecordedScaledFontCreation(std::istream &aStream); +}; + +class RecordedScaledFontDestruction : public RecordedEvent { +public: + MOZ_IMPLICIT RecordedScaledFontDestruction(ReferencePtr aRefPtr) + : RecordedEvent(SCALEDFONTDESTRUCTION), mRefPtr(aRefPtr) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "ScaledFont Destruction"; } + virtual ReferencePtr GetObjectRef() const { return mRefPtr; } +private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + MOZ_IMPLICIT RecordedScaledFontDestruction(std::istream &aStream); +}; + +class RecordedMaskSurface : public RecordedDrawingEvent { +public: + RecordedMaskSurface(DrawTarget *aDT, const Pattern &aPattern, ReferencePtr aRefMask, + const Point &aOffset, const DrawOptions &aOptions) + : RecordedDrawingEvent(MASKSURFACE, aDT), mRefMask(aRefMask), mOffset(aOffset) + , mOptions(aOptions) + { + StorePattern(mPattern, aPattern); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "MaskSurface"; } +private: + friend class RecordedEvent; + + MOZ_IMPLICIT RecordedMaskSurface(std::istream &aStream); + + PatternStorage mPattern; + ReferencePtr mRefMask; + Point mOffset; + DrawOptions mOptions; +}; + +class RecordedFilterNodeSetAttribute : public RecordedEvent +{ +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) + : RecordedEvent(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) + : RecordedEvent(FILTERNODESETATTRIBUTE), mNode(aNode), mIndex(aIndex), mArgType(ARGTYPE_FLOAT_ARRAY) + { + mPayload.resize(sizeof(Float) * aSize); + memcpy(&mPayload.front(), aFloat, sizeof(Float) * aSize); + } + + virtual bool PlayEvent(Translator *aTranslator) const; + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "SetAttribute"; } + + virtual ReferencePtr GetObjectRef() const { return mNode; } + +private: + friend class RecordedEvent; + + ReferencePtr mNode; + + uint32_t mIndex; + ArgType mArgType; + std::vector<uint8_t> mPayload; + + MOZ_IMPLICIT RecordedFilterNodeSetAttribute(std::istream &aStream); +}; + +class RecordedFilterNodeSetInput : public RecordedEvent +{ +public: + RecordedFilterNodeSetInput(FilterNode* aNode, uint32_t aIndex, FilterNode* aInputNode) + : RecordedEvent(FILTERNODESETINPUT), mNode(aNode), mIndex(aIndex) + , mInputFilter(aInputNode), mInputSurface(nullptr) + { + } + + RecordedFilterNodeSetInput(FilterNode *aNode, uint32_t aIndex, SourceSurface *aInputSurface) + : RecordedEvent(FILTERNODESETINPUT), mNode(aNode), mIndex(aIndex) + , mInputFilter(nullptr), mInputSurface(aInputSurface) + { + } + + virtual bool PlayEvent(Translator *aTranslator) const; + virtual void RecordToStream(std::ostream &aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const; + + virtual std::string GetName() const { return "SetInput"; } + + virtual ReferencePtr GetObjectRef() const { return mNode; } + +private: + friend class RecordedEvent; + + ReferencePtr mNode; + uint32_t mIndex; + ReferencePtr mInputFilter; + ReferencePtr mInputSurface; + + MOZ_IMPLICIT RecordedFilterNodeSetInput(std::istream &aStream); +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/RecordingTypes.h b/gfx/2d/RecordingTypes.h new file mode 100644 index 000000000..93fc67a68 --- /dev/null +++ b/gfx/2d/RecordingTypes.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECORDINGTYPES_H_ +#define MOZILLA_GFX_RECORDINGTYPES_H_ + +#include <ostream> + +namespace mozilla { +namespace gfx { + +template<class T> +struct ElementStreamFormat +{ + static void Write(std::ostream &aStream, const T &aElement) + { + aStream.write(reinterpret_cast<const char*>(&aElement), sizeof(T)); + } + static void Read(std::istream &aStream, T &aElement) + { + aStream.read(reinterpret_cast<char *>(&aElement), sizeof(T)); + } +}; + +template<class T> +void WriteElement(std::ostream &aStream, const T &aElement) +{ + ElementStreamFormat<T>::Write(aStream, aElement); +} +template<class T> +void ReadElement(std::istream &aStream, T &aElement) +{ + ElementStreamFormat<T>::Read(aStream, aElement); +} + +} // 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 000000000..942cb200d --- /dev/null +++ b/gfx/2d/Rect.h @@ -0,0 +1,334 @@ +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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> + +namespace mozilla { + +template <typename> struct IsPixel; + +namespace gfx { + +template<class units, class F> struct RectTyped; + +template<class units> +struct 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() {} + 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> >, + 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)) {} +}; +typedef MarginTyped<UnknownUnits> Margin; +typedef MarginTyped<UnknownUnits, double> MarginDouble; + +template<class units> +IntMarginTyped<units> RoundedToInt(const MarginTyped<units>& aMargin) +{ + return IntMarginTyped<units>(int32_t(floorf(aMargin.top + 0.5f)), + int32_t(floorf(aMargin.right + 0.5f)), + int32_t(floorf(aMargin.bottom + 0.5f)), + int32_t(floorf(aMargin.left + 0.5f))); +} + +template<class units> +struct 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() {} + 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. + MOZ_MUST_USE 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. + MOZ_MUST_USE 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->width = xMost - this->x; + this->height = yMost - this->y; + } + +}; +typedef IntRectTyped<UnknownUnits> IntRect; + +template<class units, class F = Float> +struct 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() {} + 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); + } +}; +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> +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 function for intersecting two rectangles wrapped in Maybes. +template <typename T> +Maybe<T> +IntersectMaybeRects(const Maybe<T>& a, const Maybe<T>& b) +{ + if (!a) { + return b; + } else if (!b) { + return a; + } else { + return Some(a->Intersect(*b)); + } +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_RECT_H_ */ diff --git a/gfx/2d/SFNTData.cpp b/gfx/2d/SFNTData.cpp new file mode 100644 index 000000000..fd29f6694 --- /dev/null +++ b/gfx/2d/SFNTData.cpp @@ -0,0 +1,251 @@ +/* -*- 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 "BigEndianInts.h" +#include "Logging.h" +#include "mozilla/HashFunctions.h" +#include "SFNTNameTable.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) + { + } + + bool GetU16FullName(mozilla::u16string& aU16FullName) + { + const TableDirEntry* dirEntry = + GetDirEntry(TRUETYPE_TAG('n', 'a', 'm', 'e')); + if (!dirEntry) { + gfxWarning() << "Name table entry not found."; + return false; + } + + UniquePtr<SFNTNameTable> nameTable = + SFNTNameTable::Create((mFontData + dirEntry->offset), dirEntry->length); + if (!nameTable) { + return false; + } + + return nameTable->GetU16FullName(aU16FullName); + } + +private: + + const TableDirEntry* + GetDirEntry(const uint32_t aTag) + { + 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 Move(sfntData); + } + + UniquePtr<SFNTData> sfntData(new SFNTData); + if (!sfntData->AddFont(aFontData, aDataLength, 0)) { + return nullptr; + } + + return Move(sfntData); +} + +/* static */ +uint64_t +SFNTData::GetUniqueKey(const uint8_t *aFontData, uint32_t aDataLength) +{ + uint64_t hash; + UniquePtr<SFNTData> sfntData = SFNTData::Create(aFontData, aDataLength); + mozilla::u16string firstName; + if (sfntData && sfntData->GetU16FullName(0, firstName)) { + hash = HashString(firstName.c_str(), firstName.length()); + } else { + gfxWarning() << "Failed to get name from font data hashing whole font."; + hash = HashString(aFontData, aDataLength); + } + + return hash << 32 | aDataLength;; +} + +SFNTData::~SFNTData() +{ + for (size_t i = 0; i < mFonts.length(); ++i) { + delete mFonts[i]; + } +} + +bool +SFNTData::GetU16FullName(uint32_t aIndex, mozilla::u16string& aU16FullName) +{ + if (aIndex >= mFonts.length()) { + gfxWarning() << "aIndex to font data too high."; + return false; + } + + return mFonts[aIndex]->GetU16FullName(aU16FullName); +} + +bool +SFNTData::GetU16FullNames(Vector<mozilla::u16string>& aU16FullNames) +{ + bool fontFound = false; + for (size_t i = 0; i < mFonts.length(); ++i) { + mozilla::u16string name; + if (mFonts[i]->GetU16FullName(name)) { + fontFound = true; + } + if (!aU16FullNames.append(Move(name))) { + return false; + } + } + + return fontFound; +} + +bool +SFNTData::GetIndexForU16Name(const mozilla::u16string& aU16FullName, + uint32_t* aIndex, size_t aTruncatedLen) +{ + for (size_t i = 0; i < mFonts.length(); ++i) { + mozilla::u16string name; + if (!mFonts[i]->GetU16FullName(name)) { + continue; + } + + if (aTruncatedLen) { + MOZ_ASSERT(aU16FullName.length() <= aTruncatedLen); + name = name.substr(0, aTruncatedLen); + } + + if (name == aU16FullName) { + *aIndex = i; + return true; + } + } + + return false; +} + +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)); +} + +} // gfx +} // mozilla diff --git a/gfx/2d/SFNTData.h b/gfx/2d/SFNTData.h new file mode 100644 index 000000000..e10d63cae --- /dev/null +++ b/gfx/2d/SFNTData.h @@ -0,0 +1,95 @@ +/* -*- 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" +#include "u16string.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. + * + * @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); + + ~SFNTData(); + + /** + * Gets the full name from the name table of the font corresponding to the + * index. If the full name string is not present it will use the family space + * concatenated with the style. + * This will only read names that are already UTF16. + * + * @param aFontData SFNT data. + * @param aDataLength length of aFontData. + * @param aU16FullName string to be populated with the full name. + * @return true if the full name is successfully read. + */ + bool GetU16FullName(uint32_t aIndex, mozilla::u16string& aU16FullName); + + /** + * Populate a Vector with the first UTF16 full name from each name table of + * the fonts. If the full name string is not present it will use the family + * space concatenated with the style. + * This will only read names that are already UTF16. + * + * @param aU16FullNames the Vector to be populated. + * @return true if at least one name found otherwise false. + */ + bool GetU16FullNames(Vector<mozilla::u16string>& aU16FullNames); + + /** + * Returns the index for the first UTF16 name matching aU16FullName. + * + * @param aU16FullName full name to find. + * @param aIndex out param for the index if found. + * @param aTruncatedLen length to truncate the compared font name to. + * @return true if the full name is successfully read. + */ + bool GetIndexForU16Name(const mozilla::u16string& aU16FullName, uint32_t* aIndex, + size_t aTruncatedLen = 0); + +private: + + SFNTData() {} + + bool AddFont(const uint8_t *aFontData, uint32_t aDataLength, + uint32_t aOffset); + + // Internal representation of single font in font file. + class Font; + + Vector<Font*> mFonts; +}; + +} // gfx +} // mozilla + +#endif // mozilla_gfx_SFNTData_h diff --git a/gfx/2d/SFNTNameTable.cpp b/gfx/2d/SFNTNameTable.cpp new file mode 100644 index 000000000..a30c6bd97 --- /dev/null +++ b/gfx/2d/SFNTNameTable.cpp @@ -0,0 +1,357 @@ +/* -*- 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 "SFNTNameTable.h" + +#include "BigEndianInts.h" +#include "Logging.h" +#include "mozilla/Move.h" + +#if defined(XP_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#endif + +namespace mozilla { +namespace gfx { + +static const BigEndianUint16 FORMAT_0 = 0; + +static const BigEndianUint16 NAME_ID_FAMILY = 1; +static const BigEndianUint16 NAME_ID_STYLE = 2; +static const BigEndianUint16 NAME_ID_FULL = 4; + +static const BigEndianUint16 PLATFORM_ID_UNICODE = 0; +static const BigEndianUint16 PLATFORM_ID_MAC = 1; +static const BigEndianUint16 PLATFORM_ID_MICROSOFT = 3; + +static const BigEndianUint16 ENCODING_ID_MICROSOFT_SYMBOL = 0; +static const BigEndianUint16 ENCODING_ID_MICROSOFT_UNICODEBMP = 1; +static const BigEndianUint16 ENCODING_ID_MICROSOFT_UNICODEFULL = 10; + +static const BigEndianUint16 ENCODING_ID_MAC_ROMAN = 0; + +static const BigEndianUint16 LANG_ID_MAC_ENGLISH = 0; + +static const BigEndianUint16 LANG_ID_MICROSOFT_EN_US = 0x0409; + +#pragma pack(push, 1) + +// Name table has a header, followed by name records, followed by string data. +struct NameHeader +{ + BigEndianUint16 format; // Format selector (=0). + BigEndianUint16 count; // Number of name records. + BigEndianUint16 stringOffset; // Offset to string storage from start of table. +}; + +struct NameRecord +{ + BigEndianUint16 platformID; + BigEndianUint16 encodingID; // Platform-specific encoding ID + BigEndianUint16 languageID; + BigEndianUint16 nameID; + BigEndianUint16 length; // String length in bytes. + BigEndianUint16 offset; // String offset from start of storage in bytes. +}; + +#pragma pack(pop) + +enum ENameDecoder : int +{ + eNameDecoderUTF16, +#if defined(XP_MACOSX) + eNameDecoderMacRoman, +#endif + eNameDecoderNone +}; + +/* static */ +UniquePtr<SFNTNameTable> +SFNTNameTable::Create(const uint8_t *aNameData, uint32_t aDataLength) +{ + MOZ_ASSERT(aNameData); + + if (aDataLength < sizeof(NameHeader)) { + gfxWarning() << "Name data too short to contain NameHeader."; + return nullptr; + } + + const NameHeader *nameHeader = reinterpret_cast<const NameHeader*>(aNameData); + if (nameHeader->format != FORMAT_0) { + gfxWarning() << "Only Name Table Format 0 is supported."; + return nullptr; + } + + uint16_t stringOffset = nameHeader->stringOffset; + + if (stringOffset != + sizeof(NameHeader) + (nameHeader->count * sizeof(NameRecord))) { + gfxWarning() << "Name table string offset is incorrect."; + return nullptr; + } + + if (aDataLength < stringOffset) { + gfxWarning() << "Name data too short to contain name records."; + return nullptr; + } + + return UniquePtr<SFNTNameTable>( + new SFNTNameTable(nameHeader, aNameData, aDataLength)); +} + +SFNTNameTable::SFNTNameTable(const NameHeader *aNameHeader, + const uint8_t *aNameData, uint32_t aDataLength) + : mFirstRecord(reinterpret_cast<const NameRecord*>(aNameData + + sizeof(NameHeader))) + , mEndOfRecords(mFirstRecord + aNameHeader->count) + , mStringData(aNameData + aNameHeader->stringOffset) + , mStringDataLength(aDataLength - aNameHeader->stringOffset) +{ + MOZ_ASSERT(reinterpret_cast<const uint8_t*>(aNameHeader) == aNameData); +} + +static bool +IsUTF16Encoding(const NameRecord *aNameRecord) +{ + if (aNameRecord->platformID == PLATFORM_ID_MICROSOFT && + (aNameRecord->encodingID == ENCODING_ID_MICROSOFT_UNICODEBMP || + aNameRecord->encodingID == ENCODING_ID_MICROSOFT_SYMBOL)) { + return true; + } + + if (aNameRecord->platformID == PLATFORM_ID_UNICODE) { + return true; + } + + return false; +} + +#if defined(XP_MACOSX) +static bool +IsMacRomanEncoding(const NameRecord *aNameRecord) +{ + if (aNameRecord->platformID == PLATFORM_ID_MAC && + aNameRecord->encodingID == ENCODING_ID_MAC_ROMAN) { + return true; + } + + return false; +} +#endif + +static NameRecordMatchers* +CreateCanonicalMatchers(const BigEndianUint16& aNameID) +{ + // For Windows, we return only Microsoft platform name record + // matchers. On Mac, we return matchers for both Microsoft platform + // records and Mac platform records. + NameRecordMatchers *matchers = new NameRecordMatchers(); + +#if defined(XP_MACOSX) + // First, look for the English name. + if (!matchers->append( + [=](const NameRecord *aNameRecord) { + if (aNameRecord->nameID == aNameID && + aNameRecord->languageID == LANG_ID_MAC_ENGLISH && + aNameRecord->platformID == PLATFORM_ID_MAC && + IsMacRomanEncoding(aNameRecord)) { + return eNameDecoderMacRoman; + } else { + return eNameDecoderNone; + } + })) { + MOZ_CRASH(); + } + + // Second, look for all languages. + if (!matchers->append( + [=](const NameRecord *aNameRecord) { + if (aNameRecord->nameID == aNameID && + aNameRecord->platformID == PLATFORM_ID_MAC && + IsMacRomanEncoding(aNameRecord)) { + return eNameDecoderMacRoman; + } else { + return eNameDecoderNone; + } + })) { + MOZ_CRASH(); + } +#endif /* defined(XP_MACOSX) */ + + // First, look for the English name (this will normally succeed). + if (!matchers->append( + [=](const NameRecord *aNameRecord) { + if (aNameRecord->nameID == aNameID && + aNameRecord->languageID == LANG_ID_MICROSOFT_EN_US && + aNameRecord->platformID == PLATFORM_ID_MICROSOFT && + IsUTF16Encoding(aNameRecord)) { + return eNameDecoderUTF16; + } else { + return eNameDecoderNone; + } + })) { + MOZ_CRASH(); + } + + // Second, look for all languages. + if (!matchers->append( + [=](const NameRecord *aNameRecord) { + if (aNameRecord->nameID == aNameID && + aNameRecord->platformID == PLATFORM_ID_MICROSOFT && + IsUTF16Encoding(aNameRecord)) { + return eNameDecoderUTF16; + } else { + return eNameDecoderNone; + } + })) { + MOZ_CRASH(); + } + + return matchers; +} + +static const NameRecordMatchers& +FullNameMatchers() +{ + static const NameRecordMatchers *sFullNameMatchers = + CreateCanonicalMatchers(NAME_ID_FULL); + return *sFullNameMatchers; +} + +static const NameRecordMatchers& +FamilyMatchers() +{ + static const NameRecordMatchers *sFamilyMatchers = + CreateCanonicalMatchers(NAME_ID_FAMILY); + return *sFamilyMatchers; +} + +static const NameRecordMatchers& +StyleMatchers() +{ + static const NameRecordMatchers *sStyleMatchers = + CreateCanonicalMatchers(NAME_ID_STYLE); + return *sStyleMatchers; +} + +bool +SFNTNameTable::GetU16FullName(mozilla::u16string& aU16FullName) +{ + if (ReadU16Name(FullNameMatchers(), aU16FullName)) { + return true; + } + + // If the full name record doesn't exist create the name from the family space + // concatenated with the style. + mozilla::u16string familyName; + if (!ReadU16Name(FamilyMatchers(), familyName)) { + return false; + } + + mozilla::u16string styleName; + if (!ReadU16Name(StyleMatchers(), styleName)) { + return false; + } + + aU16FullName.assign(Move(familyName)); + aU16FullName.append(u" "); + aU16FullName.append(styleName); + return true; +} + +bool +SFNTNameTable::ReadU16Name(const NameRecordMatchers& aMatchers, + mozilla::u16string& aU16Name) +{ + MOZ_ASSERT(!aMatchers.empty()); + + for (size_t i = 0; i < aMatchers.length(); ++i) { + const NameRecord* record = mFirstRecord; + while (record != mEndOfRecords) { + switch (aMatchers[i](record)) { + case eNameDecoderUTF16: + return ReadU16NameFromU16Record(record, aU16Name); +#if defined(XP_MACOSX) + case eNameDecoderMacRoman: + return ReadU16NameFromMacRomanRecord(record, aU16Name); +#endif + case eNameDecoderNone: + break; + default: + MOZ_CRASH("Invalid matcher encoding type"); + break; + } + ++record; + } + } + + return false; +} + +bool +SFNTNameTable::ReadU16NameFromU16Record(const NameRecord *aNameRecord, + mozilla::u16string& aU16Name) +{ + uint32_t offset = aNameRecord->offset; + uint32_t length = aNameRecord->length; + if (mStringDataLength < offset + length) { + gfxWarning() << "Name data too short to contain name string."; + return false; + } + + const uint8_t *startOfName = mStringData + offset; + size_t actualLength = length / sizeof(char16_t); + UniquePtr<char16_t[]> nameData(new char16_t[actualLength]); + NativeEndian::copyAndSwapFromBigEndian(nameData.get(), startOfName, + actualLength); + + aU16Name.assign(nameData.get(), actualLength); + return true; +} + +#if defined(XP_MACOSX) +bool +SFNTNameTable::ReadU16NameFromMacRomanRecord(const NameRecord *aNameRecord, + mozilla::u16string& aU16Name) +{ + uint32_t offset = aNameRecord->offset; + uint32_t length = aNameRecord->length; + if (mStringDataLength < offset + length) { + gfxWarning() << "Name data too short to contain name string."; + return false; + } + if (length > INT_MAX) { + gfxWarning() << "Name record too long to decode."; + return false; + } + + // pointer to the Mac Roman encoded string in the name record + const uint8_t *encodedStr = mStringData + offset; + + CFStringRef cfString; + cfString = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, encodedStr, + length, kCFStringEncodingMacRoman, + false, kCFAllocatorNull); + + // length (in UTF-16 code pairs) of the decoded string + CFIndex decodedLength = CFStringGetLength(cfString); + + // temporary buffer + UniquePtr<UniChar[]> u16Buffer = MakeUnique<UniChar[]>(decodedLength); + + CFStringGetCharacters(cfString, CFRangeMake(0, decodedLength), + u16Buffer.get()); + + CFRelease(cfString); + + aU16Name.assign(reinterpret_cast<char16_t*>(u16Buffer.get()), decodedLength); + + return true; +} +#endif + +} // gfx +} // mozilla diff --git a/gfx/2d/SFNTNameTable.h b/gfx/2d/SFNTNameTable.h new file mode 100644 index 000000000..3735cca96 --- /dev/null +++ b/gfx/2d/SFNTNameTable.h @@ -0,0 +1,73 @@ +/* -*- 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_SFNTNameTable_h +#define mozilla_gfx_SFNTNameTable_h + +#include "mozilla/Function.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "u16string.h" + +namespace mozilla { +namespace gfx { + +struct NameHeader; +struct NameRecord; +enum ENameDecoder : int; + +typedef Vector<function<ENameDecoder(const NameRecord*)>> NameRecordMatchers; + +class SFNTNameTable final +{ +public: + + /** + * Creates a SFNTNameTable if the header data is valid. Note that the data is + * NOT copied, so must exist for the lifetime of the table. + * + * @param aNameData the Name Table data. + * @param aDataLength length + * @return UniquePtr to a SFNTNameTable or nullptr if the header is invalid. + */ + static UniquePtr<SFNTNameTable> Create(const uint8_t *aNameData, + uint32_t aDataLength); + + /** + * Gets the full name from the name table. If the full name string is not + * present it will use the family space concatenated with the style. + * This will only read names that are already UTF16 or Mac OS Roman. + * + * @param aU16FullName string to be populated with the full name. + * @return true if the full name is successfully read. + */ + bool GetU16FullName(mozilla::u16string& aU16FullName); + +private: + + SFNTNameTable(const NameHeader *aNameHeader, const uint8_t *aNameData, + uint32_t aDataLength); + + bool ReadU16Name(const NameRecordMatchers& aMatchers, mozilla::u16string& aU16Name); + + bool ReadU16NameFromU16Record(const NameRecord *aNameRecord, + mozilla::u16string& aU16Name); + +#if defined(XP_MACOSX) + bool ReadU16NameFromMacRomanRecord(const NameRecord *aNameRecord, + mozilla::u16string& aU16Name); +#endif + + const NameRecord *mFirstRecord; + const NameRecord *mEndOfRecords; + const uint8_t *mStringData; + const uint32_t mStringDataLength; +}; + +} // gfx +} // mozilla + +#endif // mozilla_gfx_SFNTNameTable_h diff --git a/gfx/2d/SIMD.h b/gfx/2d/SIMD.h new file mode 100644 index 000000000..6bf53a38e --- /dev/null +++ b/gfx/2d/SIMD.h @@ -0,0 +1,1180 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_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 000000000..61b53d86e --- /dev/null +++ b/gfx/2d/SSEHelpers.h @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 000000000..7b18903e8 --- /dev/null +++ b/gfx/2d/SVGTurbulenceRenderer-inl.h @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "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) +{ + InitFromSeed(aSeed); + if (Stitch) { + AdjustBaseFrequencyForStitch(aTileRect); + mStitchInfo = CreateStitchInfo(aTileRect); + } +} + +template<typename T> +static void +Swap(T& a, T& b) { + T c = a; + a = b; + b = c; +} + +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; + 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 (aValue >= 0) { + return aValue; + } + return aValue + ceilf(-aValue / aIncrementSize) * aIncrementSize; +} + +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(basePeriod.width / mBaseFrequency.width, + 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; + } + + uint8_t* targetData = target->GetData(); + uint32_t stride = target->Stride(); + + 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.cpp b/gfx/2d/Scale.cpp new file mode 100644 index 000000000..f1fec03d0 --- /dev/null +++ b/gfx/2d/Scale.cpp @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "Scale.h" + +#ifdef USE_SKIA +#include "HelpersSkia.h" +#include "skia/include/core/SkBitmap.h" +#include "image_operations.h" +#endif + +namespace mozilla { +namespace gfx { + +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) +{ +#ifdef USE_SKIA + SkBitmap imgSrc; + imgSrc.installPixels(MakeSkiaImageInfo(IntSize(srcWidth, srcHeight), format), + srcData, srcStride); + + // Rescaler is compatible with 32 bpp only. Convert to RGB32 if needed. + if (imgSrc.colorType() != kBGRA_8888_SkColorType) { + imgSrc.copyTo(&imgSrc, kBGRA_8888_SkColorType); + } + + // This returns an SkBitmap backed by dstData; since it also wrote to dstData, + // we don't need to look at that SkBitmap. + SkBitmap result = skia::ImageOperations::Resize(imgSrc, + skia::ImageOperations::RESIZE_BEST, + dstWidth, dstHeight, + dstData); + + return !result.isNull(); +#else + return false; +#endif +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Scale.h b/gfx/2d/Scale.h new file mode 100644 index 000000000..75465da38 --- /dev/null +++ b/gfx/2d/Scale.h @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 000000000..acf5bb739 --- /dev/null +++ b/gfx/2d/ScaleFactor.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEFACTOR_H_ +#define MOZILLA_GFX_SCALEFACTOR_H_ + +#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); + } + + 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); + } +}; + +} // 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 000000000..6de32d7f2 --- /dev/null +++ b/gfx/2d/ScaleFactors2D.h @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEFACTORS2D_H_ +#define MOZILLA_GFX_SCALEFACTORS2D_H_ + +#include <ostream> + +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/gfx/ScaleFactor.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> +struct ScaleFactors2D { + float xScale; + float yScale; + + constexpr ScaleFactors2D() : xScale(1.0), yScale(1.0) {} + constexpr ScaleFactors2D(const ScaleFactors2D<src, dst>& aCopy) + : xScale(aCopy.xScale), yScale(aCopy.yScale) {} + constexpr ScaleFactors2D(float aXScale, float aYScale) + : xScale(aXScale), yScale(aYScale) {} + // Layout code often uses gfxSize to represent a pair of x/y scales. + explicit constexpr ScaleFactors2D(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 ScaleFactors2D(const ScaleFactor<src, dst>& aScale) + : xScale(aScale.scale), yScale(aScale.scale) {} + + bool AreScalesSame() const { + return FuzzyEqualsMultiplicative(xScale, yScale); + } + + // Convert to a ScaleFactor. Asserts that the scales are, in fact, equal. + ScaleFactor<src, dst> ToScaleFactor() const { + MOZ_ASSERT(AreScalesSame()); + return ScaleFactor<src, dst>(xScale); + } + + bool operator==(const ScaleFactors2D<src, dst>& aOther) const { + return xScale == aOther.xScale && yScale == aOther.yScale; + } + + bool operator!=(const ScaleFactors2D<src, dst>& aOther) const { + return !(*this == aOther); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const ScaleFactors2D<src, dst>& aScale) { + if (aScale.AreScalesSame()) { + return aStream << aScale.xScale; + } else { + return aStream << '(' << aScale.xScale << ',' << aScale.yScale << ')'; + } + } + + template<class other> + ScaleFactors2D<other, dst> operator/(const ScaleFactors2D<src, other>& aOther) const { + return ScaleFactors2D<other, dst>(xScale / aOther.xScale, yScale / aOther.yScale); + } + + template<class other> + ScaleFactors2D<src, other> operator/(const ScaleFactors2D<other, dst>& aOther) const { + return ScaleFactors2D<src, other>(xScale / aOther.xScale, yScale / aOther.yScale); + } + + template<class other> + ScaleFactors2D<src, other> operator*(const ScaleFactors2D<dst, other>& aOther) const { + return ScaleFactors2D<src, other>(xScale * aOther.xScale, yScale * aOther.yScale); + } + + template<class other> + ScaleFactors2D<other, dst> operator*(const ScaleFactors2D<other, src>& aOther) const { + return ScaleFactors2D<other, dst>(xScale * aOther.xScale, yScale * aOther.yScale); + } + + template<class other> + ScaleFactors2D<src, other> operator*(const ScaleFactor<dst, other>& aOther) const { + return *this * ScaleFactors2D<dst, other>(aOther); + } + + template<class other> + ScaleFactors2D<other, dst> operator*(const ScaleFactor<other, src>& aOther) const { + return *this * ScaleFactors2D<other, src>(aOther); + } + + template<class other> + ScaleFactors2D<src, other> operator/(const ScaleFactor<other, dst>& aOther) const { + return *this / ScaleFactors2D<other, dst>(aOther); + } + + template<class other> + ScaleFactors2D<other, dst> operator/(const ScaleFactor<src, other>& aOther) const { + return *this / ScaleFactors2D<src, other>(aOther); + } + + template<class other> + friend ScaleFactors2D<other, dst> operator*(const ScaleFactor<other, src>& aA, + const ScaleFactors2D<src, dst>& aB) { + return ScaleFactors2D<other, src>(aA) * aB; + } + + template<class other> + friend ScaleFactors2D<other, src> operator/(const ScaleFactor<other, dst>& aA, + const ScaleFactors2D<src, dst>& aB) { + return ScaleFactors2D<other, src>(aA) / aB; + } + + // Divide two scales of the same units, yielding a scale with no units, + // represented as a gfxSize. This can mean e.g. the cahnge in a particular + // scale from one frame to the next. + gfxSize operator/(const ScaleFactors2D& aOther) const { + return gfxSize(xScale / aOther.xScale, yScale / aOther.yScale); + } +}; + +} // 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 000000000..ca9b2a188 --- /dev/null +++ b/gfx/2d/ScaledFontBase.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontBase.h" + +#ifdef USE_SKIA +#include "PathSkia.h" +#include "skia/include/core/SkPaint.h" +#endif + +#ifdef USE_CAIRO +#include "PathCairo.h" +#include "DrawTargetCairo.h" +#include "HelpersCairo.h" +#endif + +#include <vector> +#include <cmath> + +using namespace std; + +namespace mozilla { +namespace gfx { + +ScaledFontBase::~ScaledFontBase() +{ +#ifdef USE_SKIA + SkSafeUnref(mTypeface); +#endif +#ifdef USE_CAIRO_SCALED_FONT + cairo_scaled_font_destroy(mScaledFont); +#endif +} + +ScaledFontBase::ScaledFontBase(Float aSize) + : mSize(aSize) +{ +#ifdef USE_SKIA + mTypeface = nullptr; +#endif +#ifdef USE_CAIRO_SCALED_FONT + mScaledFont = nullptr; +#endif +} + +#ifdef USE_CAIRO_SCALED_FONT +bool +ScaledFontBase::PopulateCairoScaledFont() +{ + cairo_font_face_t* cairoFontFace = GetCairoFontFace(); + if (!cairoFontFace) { + return false; + } + + cairo_matrix_t sizeMatrix; + cairo_matrix_t identityMatrix; + + cairo_matrix_init_scale(&sizeMatrix, mSize, mSize); + cairo_matrix_init_identity(&identityMatrix); + + cairo_font_options_t *fontOptions = cairo_font_options_create(); + + mScaledFont = cairo_scaled_font_create(cairoFontFace, &sizeMatrix, + &identityMatrix, fontOptions); + + cairo_font_options_destroy(fontOptions); + cairo_font_face_destroy(cairoFontFace); + + return (cairo_scaled_font_status(mScaledFont) == CAIRO_STATUS_SUCCESS); +} +#endif + +#ifdef USE_SKIA +SkPath +ScaledFontBase::GetSkiaPathForGlyphs(const GlyphBuffer &aBuffer) +{ + SkTypeface *typeFace = GetSkTypeface(); + MOZ_ASSERT(typeFace); + + SkPaint paint; + paint.setTypeface(sk_ref_sp(typeFace)); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setTextSize(SkFloatToScalar(mSize)); + + std::vector<uint16_t> indices; + std::vector<SkPoint> offsets; + indices.resize(aBuffer.mNumGlyphs); + offsets.resize(aBuffer.mNumGlyphs); + + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + indices[i] = aBuffer.mGlyphs[i].mIndex; + offsets[i].fX = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.x); + offsets[i].fY = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.y); + } + + SkPath path; + paint.getPosTextPath(&indices.front(), aBuffer.mNumGlyphs*2, &offsets.front(), &path); + return path; +} +#endif + +already_AddRefed<Path> +ScaledFontBase::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) +{ +#ifdef USE_SKIA + if (aTarget->GetBackendType() == BackendType::SKIA) { + SkPath path = GetSkiaPathForGlyphs(aBuffer); + return MakeAndAddRef<PathSkia>(path, FillRule::FILL_WINDING); + } +#endif +#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 + return nullptr; +} + +void +ScaledFontBase::CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint) +{ + BackendType backendType = aBuilder->GetBackendType(); +#ifdef USE_SKIA + if (backendType == BackendType::SKIA) { + PathBuilderSkia *builder = static_cast<PathBuilderSkia*>(aBuilder); + builder->AppendPath(GetSkiaPathForGlyphs(aBuffer)); + return; + } +#endif +#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 +} + +void +ScaledFontBase::GetGlyphDesignMetrics(const uint16_t* aGlyphs, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) +{ +#ifdef USE_CAIRO_SCALED_FONT + if (mScaledFont) { + for (uint32_t i = 0; i < aNumGlyphs; i++) { + cairo_glyph_t glyph; + cairo_text_extents_t extents; + glyph.index = aGlyphs[i]; + glyph.x = 0; + glyph.y = 0; + + cairo_scaled_font_glyph_extents(mScaledFont, &glyph, 1, &extents); + + aGlyphMetrics[i].mXBearing = extents.x_bearing; + aGlyphMetrics[i].mXAdvance = extents.x_advance; + aGlyphMetrics[i].mYBearing = extents.y_bearing; + aGlyphMetrics[i].mYAdvance = extents.y_advance; + aGlyphMetrics[i].mWidth = extents.width; + aGlyphMetrics[i].mHeight = extents.height; + + cairo_font_options_t *options = cairo_font_options_create(); + cairo_scaled_font_get_font_options(mScaledFont, options); + + if (cairo_font_options_get_antialias(options) != CAIRO_ANTIALIAS_NONE) { + if (cairo_scaled_font_get_type(mScaledFont) == CAIRO_FONT_TYPE_WIN32) { + if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) { + aGlyphMetrics[i].mWidth -= 3.0f; + aGlyphMetrics[i].mXBearing += 1.0f; + } + } +#if defined(MOZ2D_HAS_MOZ_CAIRO) && defined(CAIRO_HAS_DWRITE_FONT) + else if (cairo_scaled_font_get_type(mScaledFont) == CAIRO_FONT_TYPE_DWRITE) { + if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) { + aGlyphMetrics[i].mWidth -= 2.0f; + aGlyphMetrics[i].mXBearing += 1.0f; + } + } +#endif + } + cairo_font_options_destroy(options); + } + + } +#endif + + // Don't know how to get the glyph metrics... + MOZ_CRASH("The specific backend type is not supported for GetGlyphDesignMetrics."); +} + + +#ifdef USE_CAIRO_SCALED_FONT +void +ScaledFontBase::SetCairoScaledFont(cairo_scaled_font_t* font) +{ + MOZ_ASSERT(!mScaledFont); + + if (font == mScaledFont) + return; + + if (mScaledFont) + cairo_scaled_font_destroy(mScaledFont); + + mScaledFont = font; + cairo_scaled_font_reference(mScaledFont); +} +#endif + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontBase.h b/gfx/2d/ScaledFontBase.h new file mode 100644 index 000000000..e4bb4f2f8 --- /dev/null +++ b/gfx/2d/ScaledFontBase.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTBASE_H_ +#define MOZILLA_GFX_SCALEDFONTBASE_H_ + +#include "2D.h" + +// Skia uses cairo_scaled_font_t as the internal font type in ScaledFont +#if defined(USE_SKIA) || defined(USE_CAIRO) +#define USE_CAIRO_SCALED_FONT +#endif + +#ifdef USE_SKIA +#include "skia/include/core/SkPath.h" +#include "skia/include/core/SkTypeface.h" +#endif +#ifdef USE_CAIRO_SCALED_FONT +#include "cairo.h" +#endif + +namespace mozilla { +namespace gfx { + +class ScaledFontBase : public ScaledFont +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontBase) + explicit ScaledFontBase(Float aSize); + virtual ~ScaledFontBase(); + + virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget); + + virtual void CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint); + + virtual void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics); + + float GetSize() { return mSize; } + +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface() { return mTypeface; } +#endif + + // Not true, but required to instantiate a ScaledFontBase. + virtual FontType GetType() const { return FontType::SKIA; } + +#ifdef USE_CAIRO_SCALED_FONT + bool PopulateCairoScaledFont(); + cairo_scaled_font_t* GetCairoScaledFont() { return mScaledFont; } + void SetCairoScaledFont(cairo_scaled_font_t* font); +#endif + +protected: + friend class DrawTargetSkia; +#ifdef USE_SKIA + SkTypeface* mTypeface; + SkPath GetSkiaPathForGlyphs(const GlyphBuffer &aBuffer); +#endif +#ifdef USE_CAIRO_SCALED_FONT + // Overridders should ensure the cairo_font_face_t has been addrefed. + virtual cairo_font_face_t* GetCairoFontFace() { return nullptr; } + cairo_scaled_font_t* mScaledFont; +#endif + Float mSize; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTBASE_H_ */ diff --git a/gfx/2d/ScaledFontCairo.cpp b/gfx/2d/ScaledFontCairo.cpp new file mode 100644 index 000000000..b8b52145e --- /dev/null +++ b/gfx/2d/ScaledFontCairo.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontCairo.h" +#include "Logging.h" + +#if defined(USE_SKIA) && defined(MOZ_ENABLE_FREETYPE) +#include "skia/include/ports/SkTypeface_cairo.h" +#endif + +namespace mozilla { +namespace gfx { + +// On Linux and Android our "platform" font is a cairo_scaled_font_t and we use +// an SkFontHost implementation that allows Skia to render using this. +// This is mainly because FT_Face is not good for sharing between libraries, which +// is a requirement when we consider runtime switchable backends and so on +ScaledFontCairo::ScaledFontCairo(cairo_scaled_font_t* aScaledFont, Float aSize) + : ScaledFontBase(aSize) +{ + SetCairoScaledFont(aScaledFont); +} + +#if defined(USE_SKIA) && defined(MOZ_ENABLE_FREETYPE) +SkTypeface* ScaledFontCairo::GetSkTypeface() +{ + if (!mTypeface) { + mTypeface = SkCreateTypefaceFromCairoFTFont(mScaledFont); + } + + return mTypeface; +} +#endif + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontCairo.h b/gfx/2d/ScaledFontCairo.h new file mode 100644 index 000000000..7862fa1a0 --- /dev/null +++ b/gfx/2d/ScaledFontCairo.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTCAIRO_H_ +#define MOZILLA_GFX_SCALEDFONTCAIRO_H_ + +#include "ScaledFontBase.h" + +#include "cairo.h" + +namespace mozilla { +namespace gfx { + +class ScaledFontCairo : public ScaledFontBase +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontCairo) + + ScaledFontCairo(cairo_scaled_font_t* aScaledFont, Float aSize); + +#if defined(USE_SKIA) && defined(MOZ_ENABLE_FREETYPE) + virtual SkTypeface* GetSkTypeface(); +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTCAIRO_H_ */ diff --git a/gfx/2d/ScaledFontDWrite.cpp b/gfx/2d/ScaledFontDWrite.cpp new file mode 100644 index 000000000..dc8e586f5 --- /dev/null +++ b/gfx/2d/ScaledFontDWrite.cpp @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetD2D1.h" +#include "ScaledFontDWrite.h" +#include "PathD2D.h" +#include "gfxFont.h" + +using namespace std; + +#ifdef USE_SKIA +#include "PathSkia.h" +#include "skia/include/core/SkPaint.h" +#include "skia/include/core/SkPath.h" +#include "skia/include/ports/SkTypeface_win.h" +#endif + +#include <vector> + +#ifdef USE_CAIRO_SCALED_FONT +#include "cairo-win32.h" +#endif + +#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; +} + +static inline DWRITE_FONT_STRETCH +DWriteFontStretchFromStretch(int16_t aStretch) +{ + switch (aStretch) { + case NS_FONT_STRETCH_ULTRA_CONDENSED: + return DWRITE_FONT_STRETCH_ULTRA_CONDENSED; + case NS_FONT_STRETCH_EXTRA_CONDENSED: + return DWRITE_FONT_STRETCH_EXTRA_CONDENSED; + case NS_FONT_STRETCH_CONDENSED: + return DWRITE_FONT_STRETCH_CONDENSED; + case NS_FONT_STRETCH_SEMI_CONDENSED: + return DWRITE_FONT_STRETCH_SEMI_CONDENSED; + case NS_FONT_STRETCH_NORMAL: + return DWRITE_FONT_STRETCH_NORMAL; + case NS_FONT_STRETCH_SEMI_EXPANDED: + return DWRITE_FONT_STRETCH_SEMI_EXPANDED; + case NS_FONT_STRETCH_EXPANDED: + return DWRITE_FONT_STRETCH_EXPANDED; + case NS_FONT_STRETCH_EXTRA_EXPANDED: + return DWRITE_FONT_STRETCH_EXTRA_EXPANDED; + case NS_FONT_STRETCH_ULTRA_EXPANDED: + return DWRITE_FONT_STRETCH_ULTRA_EXPANDED; + default: + return DWRITE_FONT_STRETCH_UNDEFINED; + } +} + +ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace *aFontFace, Float aSize, + bool aUseEmbeddedBitmap, bool aForceGDIMode, + const gfxFontStyle* aStyle) + : ScaledFontBase(aSize) + , mFontFace(aFontFace) + , mUseEmbeddedBitmap(aUseEmbeddedBitmap) + , mForceGDIMode(aForceGDIMode) +{ + mStyle = SkFontStyle(aStyle->weight, + DWriteFontStretchFromStretch(aStyle->stretch), + aStyle->style == NS_FONT_STYLE_NORMAL ? + SkFontStyle::kUpright_Slant : SkFontStyle::kItalic_Slant); +} + +already_AddRefed<Path> +ScaledFontDWrite::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) +{ + if (aTarget->GetBackendType() != BackendType::DIRECT2D && aTarget->GetBackendType() != BackendType::DIRECT2D1_1) { + return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget); + } + + RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder(); + + PathBuilderD2D *pathBuilderD2D = + static_cast<PathBuilderD2D*>(pathBuilder.get()); + + CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink()); + + return pathBuilder->Finish(); +} + + +#ifdef USE_SKIA +SkTypeface* +ScaledFontDWrite::GetSkTypeface() +{ + if (!mTypeface) { + IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory(); + if (!factory) { + return nullptr; + } + + mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, mForceGDIMode); + } + return mTypeface; +} +#endif + +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::GetGlyphDesignMetrics(const uint16_t* aGlyphs, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) +{ + DWRITE_FONT_METRICS fontMetrics; + mFontFace->GetMetrics(&fontMetrics); + + vector<DWRITE_GLYPH_METRICS> metrics(aNumGlyphs); + mFontFace->GetDesignGlyphMetrics(aGlyphs, aNumGlyphs, &metrics.front()); + + Float designUnitCorrection = 1.f / fontMetrics.designUnitsPerEm; + + for (uint32_t i = 0; i < aNumGlyphs; i++) { + aGlyphMetrics[i].mXBearing = metrics[i].leftSideBearing * designUnitCorrection * mSize; + aGlyphMetrics[i].mXAdvance = metrics[i].advanceWidth * designUnitCorrection * mSize; + aGlyphMetrics[i].mYBearing = metrics[i].topSideBearing * designUnitCorrection * mSize; + aGlyphMetrics[i].mYAdvance = metrics[i].advanceHeight * designUnitCorrection * mSize; + aGlyphMetrics[i].mWidth = (metrics[i].advanceHeight - metrics[i].topSideBearing - metrics[i].bottomSideBearing) * + designUnitCorrection * mSize; + aGlyphMetrics[i].mHeight = (metrics[i].topSideBearing - metrics[i].verticalOriginY) * designUnitCorrection * mSize; + } +} + +void +ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer &aBuffer, ID2D1GeometrySink *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 +ScaledFontDWrite::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) +{ + UINT32 fileCount = 0; + mFontFace->GetFiles(&fileCount, nullptr); + + if (fileCount > 1) { + MOZ_ASSERT(false); + return false; + } + + RefPtr<IDWriteFontFile> file; + mFontFace->GetFiles(&fileCount, getter_AddRefs(file)); + + 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. + file->GetReferenceKey(&referenceKey, &refKeySize); + + RefPtr<IDWriteFontFileLoader> loader; + file->GetLoader(getter_AddRefs(loader)); + + RefPtr<IDWriteFontFileStream> stream; + loader->CreateStreamFromKey(referenceKey, refKeySize, getter_AddRefs(stream)); + + UINT64 fileSize64; + stream->GetFileSize(&fileSize64); + if (fileSize64 > UINT32_MAX) { + MOZ_ASSERT(false); + return false; + } + + uint32_t fileSize = static_cast<uint32_t>(fileSize64); + const void *fragmentStart; + void *context; + stream->ReadFileFragment(&fragmentStart, 0, fileSize, &context); + + aDataCallback((uint8_t*)fragmentStart, fileSize, mFontFace->GetIndex(), mSize, aBaton); + + stream->ReleaseFileFragment(context); + + return true; +} + +AntialiasMode +ScaledFontDWrite::GetDefaultAAMode() +{ + AntialiasMode defaultMode = GetSystemDefaultAAMode(); + + if (defaultMode == AntialiasMode::GRAY) { + if (!DoGrayscale(mFontFace, mSize)) { + defaultMode = AntialiasMode::NONE; + } + } + return defaultMode; +} + +#ifdef USE_CAIRO_SCALED_FONT +cairo_font_face_t* +ScaledFontDWrite::GetCairoFontFace() +{ + if (!mFontFace) { + return nullptr; + } + + return cairo_dwrite_font_face_create_for_dwrite_fontface(nullptr, mFontFace); +} +#endif + +} +} diff --git a/gfx/2d/ScaledFontDWrite.h b/gfx/2d/ScaledFontDWrite.h new file mode 100644 index 000000000..fc9a26c42 --- /dev/null +++ b/gfx/2d/ScaledFontDWrite.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTDWRITE_H_ +#define MOZILLA_GFX_SCALEDFONTDWRITE_H_ + +#include <dwrite.h> +#include "ScaledFontBase.h" + +struct ID2D1GeometrySink; +struct gfxFontStyle; + +namespace mozilla { +namespace gfx { + +class ScaledFontDWrite final : public ScaledFontBase +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontDwrite) + ScaledFontDWrite(IDWriteFontFace *aFont, Float aSize) + : ScaledFontBase(aSize) + , mFontFace(aFont) + , mUseEmbeddedBitmap(false) + , mForceGDIMode(false) + {} + + ScaledFontDWrite(IDWriteFontFace *aFontFace, Float aSize, bool aUseEmbeddedBitmap, + bool aForceGDIMode, const gfxFontStyle* aStyle); + + virtual FontType GetType() const { return FontType::DWRITE; } + + virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget); + virtual void CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint); + + void CopyGlyphsToSink(const GlyphBuffer &aBuffer, ID2D1GeometrySink *aSink); + + virtual void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics); + + virtual bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton); + + virtual AntialiasMode GetDefaultAAMode() override; + + bool UseEmbeddedBitmaps() { return mUseEmbeddedBitmap; } + bool ForceGDIMode() { return mForceGDIMode; } + +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface(); + SkFontStyle mStyle; +#endif + + RefPtr<IDWriteFontFace> mFontFace; + bool mUseEmbeddedBitmap; + bool mForceGDIMode; + +protected: +#ifdef USE_CAIRO_SCALED_FONT + cairo_font_face_t* GetCairoFontFace() override; +#endif +}; + +class GlyphRenderingOptionsDWrite : public GlyphRenderingOptions +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GlyphRenderingOptionsDWrite) + GlyphRenderingOptionsDWrite(IDWriteRenderingParams *aParams) + : mParams(aParams) + { + } + + virtual FontType GetType() const { return FontType::DWRITE; } + +private: + friend class DrawTargetD2D; + friend class DrawTargetD2D1; + + RefPtr<IDWriteRenderingParams> mParams; +}; + +} +} + +#endif /* MOZILLA_GFX_SCALEDFONTDWRITE_H_ */ diff --git a/gfx/2d/ScaledFontFontconfig.cpp b/gfx/2d/ScaledFontFontconfig.cpp new file mode 100644 index 000000000..d4751f86d --- /dev/null +++ b/gfx/2d/ScaledFontFontconfig.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontFontconfig.h" +#include "Logging.h" + +#ifdef USE_SKIA +#include "skia/include/ports/SkTypeface_cairo.h" +#endif + +namespace mozilla { +namespace gfx { + +// On Linux and Android our "platform" font is a cairo_scaled_font_t and we use +// an SkFontHost implementation that allows Skia to render using this. +// This is mainly because FT_Face is not good for sharing between libraries, which +// is a requirement when we consider runtime switchable backends and so on +ScaledFontFontconfig::ScaledFontFontconfig(cairo_scaled_font_t* aScaledFont, + FcPattern* aPattern, + Float aSize) + : ScaledFontBase(aSize), + mPattern(aPattern) +{ + SetCairoScaledFont(aScaledFont); + FcPatternReference(aPattern); +} + +ScaledFontFontconfig::~ScaledFontFontconfig() +{ + FcPatternDestroy(mPattern); +} + +#ifdef USE_SKIA +SkTypeface* ScaledFontFontconfig::GetSkTypeface() +{ + if (!mTypeface) { + mTypeface = SkCreateTypefaceFromCairoFTFontWithFontconfig(mScaledFont, mPattern); + } + + return mTypeface; +} +#endif + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontFontconfig.h b/gfx/2d/ScaledFontFontconfig.h new file mode 100644 index 000000000..4d4e8217d --- /dev/null +++ b/gfx/2d/ScaledFontFontconfig.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ +#define MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ + +#include "ScaledFontBase.h" + +#include <fontconfig/fontconfig.h> +#include <cairo.h> + +namespace mozilla { +namespace gfx { + +class ScaledFontFontconfig : public ScaledFontBase +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontFontconfig) + ScaledFontFontconfig(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize); + ~ScaledFontFontconfig(); + + virtual FontType GetType() const { return FontType::FONTCONFIG; } + +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface(); +#endif + +private: + FcPattern* mPattern; +}; + +} +} + +#endif /* MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ */ diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp new file mode 100644 index 000000000..6baf25782 --- /dev/null +++ b/gfx/2d/ScaledFontMac.cpp @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontMac.h" +#ifdef USE_SKIA +#include "PathSkia.h" +#include "skia/include/core/SkPaint.h" +#include "skia/include/core/SkPath.h" +#include "skia/include/ports/SkTypeface_mac.h" +#endif +#include <vector> +#include <dlfcn.h> +#ifdef MOZ_WIDGET_UIKIT +#include <CoreFoundation/CoreFoundation.h> +#endif + +#ifdef MOZ_WIDGET_COCOA +// prototype for private API +extern "C" { +CGPathRef CGFontGetGlyphPath(CGFontRef fontRef, CGAffineTransform *textTransform, int unknown, CGGlyph glyph); +}; +#endif + +#ifdef USE_CAIRO_SCALED_FONT +#include "cairo-quartz.h" +#endif + +namespace mozilla { +namespace gfx { + +ScaledFontMac::CTFontDrawGlyphsFuncT* ScaledFontMac::CTFontDrawGlyphsPtr = nullptr; +bool ScaledFontMac::sSymbolLookupDone = false; + +ScaledFontMac::ScaledFontMac(CGFontRef aFont, Float aSize) + : ScaledFontBase(aSize) +{ + if (!sSymbolLookupDone) { + CTFontDrawGlyphsPtr = + (CTFontDrawGlyphsFuncT*)dlsym(RTLD_DEFAULT, "CTFontDrawGlyphs"); + sSymbolLookupDone = true; + } + + // XXX: should we be taking a reference + mFont = CGFontRetain(aFont); + if (CTFontDrawGlyphsPtr != nullptr) { + // only create mCTFont if we're going to be using the CTFontDrawGlyphs API + mCTFont = CTFontCreateWithGraphicsFont(aFont, aSize, nullptr, nullptr); + } else { + mCTFont = nullptr; + } +} + +ScaledFontMac::~ScaledFontMac() +{ + if (mCTFont) { + CFRelease(mCTFont); + } + CGFontRelease(mFont); +} + +#ifdef USE_SKIA +SkTypeface* ScaledFontMac::GetSkTypeface() +{ + if (!mTypeface) { + if (mCTFont) { + mTypeface = SkCreateTypefaceFromCTFont(mCTFont); + } else { + CTFontRef fontFace = CTFontCreateWithGraphicsFont(mFont, mSize, nullptr, nullptr); + mTypeface = SkCreateTypefaceFromCTFont(fontFace); + CFRelease(fontFace); + } + } + return mTypeface; +} +#endif + +// 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); +} + +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+3) & ~3) / sizeof(uint32_t); + while (table < end) { + if (skipChecksumAdjust && (table - tableStart) == 2) { + table++; + } else { + sum += CFSwapInt32BigToHost(*table++); + } + } + return sum; +} + +struct TableRecord { + uint32_t tag; + uint32_t checkSum; + uint32_t offset; + uint32_t length; + CFDataRef data; +}; + +int maxPow2LessThan(int a) +{ + int x = 1; + int shift = 0; + while ((x<<(shift+1)) < a) { + shift++; + } + return shift; +} + +struct writeBuf +{ + 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 +ScaledFontMac::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); + records[i].tag = tag; + records[i].offset = offset; + records[i].data = 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; + } + 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)); + buf.writeElement(CFSwapInt16HostToBig((1<<maxPow2LessThan(count))*16)); + buf.writeElement(CFSwapInt16HostToBig(maxPow2LessThan(count))); + buf.writeElement(CFSwapInt16HostToBig(count*16-((1<<maxPow2LessThan(count))*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; + } + buf.writeMem(CFDataGetBytePtr(records[i].data), CFDataGetLength(records[i].data)); + 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, mSize, aBaton); + + return true; + +} + +#ifdef USE_CAIRO_SCALED_FONT +cairo_font_face_t* +ScaledFontMac::GetCairoFontFace() +{ + MOZ_ASSERT(mFont); + return cairo_quartz_font_face_create_for_cgfont(mFont); +} +#endif + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontMac.h b/gfx/2d/ScaledFontMac.h new file mode 100644 index 000000000..c141f96b2 --- /dev/null +++ b/gfx/2d/ScaledFontMac.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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 { + +class GlyphRenderingOptionsCG : public GlyphRenderingOptions +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GlyphRenderingOptionsCG, override) + + explicit GlyphRenderingOptionsCG(const Color &aFontSmoothingBackgroundColor) + : mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor) + {} + + const Color &FontSmoothingBackgroundColor() const { return mFontSmoothingBackgroundColor; } + + virtual FontType GetType() const override { return FontType::MAC; } + +private: + Color mFontSmoothingBackgroundColor; +}; + +class ScaledFontMac : public ScaledFontBase +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontMac) + ScaledFontMac(CGFontRef aFont, Float aSize); + virtual ~ScaledFontMac(); + + virtual FontType GetType() const { return FontType::MAC; } +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface(); +#endif + virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget); + virtual bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton); + +#ifdef USE_CAIRO_SCALED_FONT + cairo_font_face_t* GetCairoFontFace(); +#endif + +private: + friend class DrawTargetSkia; + CGFontRef mFont; + CTFontRef mCTFont; // only created if CTFontDrawGlyphs is available, otherwise null + + typedef void (CTFontDrawGlyphsFuncT)(CTFontRef, + const CGGlyph[], const CGPoint[], + size_t, CGContextRef); + + static bool sSymbolLookupDone; + +public: + // function pointer for CTFontDrawGlyphs, if available; + // initialized the first time a ScaledFontMac is created, + // so it will be valid by the time DrawTargetCG wants to use it + static CTFontDrawGlyphsFuncT* CTFontDrawGlyphsPtr; +}; + +} // 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 000000000..2ebae21e5 --- /dev/null +++ b/gfx/2d/ScaledFontWin.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontWin.h" + +#include "AutoHelpersWin.h" +#include "Logging.h" +#include "nsString.h" + +#ifdef USE_SKIA +#include "skia/include/ports/SkTypeface_win.h" +#endif + +#ifdef USE_CAIRO_SCALED_FONT +#include "cairo-win32.h" +#endif + +#include "HelpersWinFonts.h" + +namespace mozilla { +namespace gfx { + +ScaledFontWin::ScaledFontWin(const LOGFONT* aFont, Float aSize) + : ScaledFontBase(aSize) + , mLogFont(*aFont) +{ +} + +bool +ScaledFontWin::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, mSize, aBaton); + return true; +} + +bool +ScaledFontWin::GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) +{ + aCb(reinterpret_cast<uint8_t*>(&mLogFont), sizeof(mLogFont), aBaton); + return true; +} + +bool +ScaledFontWin::GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) +{ + aCb(reinterpret_cast<uint8_t*>(&mLogFont), sizeof(mLogFont), mSize, aBaton); + return true; +} + +AntialiasMode +ScaledFontWin::GetDefaultAAMode() +{ + return GetSystemDefaultAAMode(); +} + +#ifdef USE_SKIA +SkTypeface* ScaledFontWin::GetSkTypeface() +{ + if (!mTypeface) { + mTypeface = SkCreateTypefaceFromLOGFONT(mLogFont); + } + return mTypeface; +} +#endif + +#ifdef USE_CAIRO_SCALED_FONT +cairo_font_face_t* +ScaledFontWin::GetCairoFontFace() +{ + if (mLogFont.lfFaceName[0] == 0) { + return nullptr; + } + return cairo_win32_font_face_create_for_logfontw(&mLogFont); +} +#endif + +} +} diff --git a/gfx/2d/ScaledFontWin.h b/gfx/2d/ScaledFontWin.h new file mode 100644 index 000000000..c07b263d7 --- /dev/null +++ b/gfx/2d/ScaledFontWin.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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) + ScaledFontWin(const LOGFONT* aFont, Float aSize); + + virtual FontType GetType() const { return FontType::GDI; } + + bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) override; + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + virtual bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override; + virtual AntialiasMode GetDefaultAAMode() override; + +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface(); +#endif + +protected: +#ifdef USE_CAIRO_SCALED_FONT + cairo_font_face_t* GetCairoFontFace() override; +#endif + +private: +#ifdef USE_SKIA + friend class DrawTargetSkia; +#endif + LOGFONT mLogFont; +}; + +} +} + +#endif /* MOZILLA_GFX_SCALEDFONTWIN_H_ */ diff --git a/gfx/2d/ShadersD2D.fx b/gfx/2d/ShadersD2D.fx new file mode 100644 index 000000000..df4ced845 --- /dev/null +++ b/gfx/2d/ShadersD2D.fx @@ -0,0 +1,746 @@ +// 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; +} + +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 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; +} + +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 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 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 000000000..9cc65a09b --- /dev/null +++ b/gfx/2d/ShadersD2D.h @@ -0,0 +1,16012 @@ +#if 0
+//
+// FX Version: fx_4_0
+// Child effect (requires effect pool): false
+//
+// 4 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
+}
+
+//
+// 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);
+};
+
+//
+// 8 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 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, 116, 210,
+ 237, 43, 26, 169, 147, 99,
+ 62, 90, 128, 241, 238, 193,
+ 236, 181, 1, 0, 0, 0,
+ 242, 19, 1, 0, 1, 0,
+ 0, 0, 36, 0, 0, 0,
+ 70, 88, 49, 48, 198, 19,
+ 1, 0, 1, 16, 255, 254,
+ 4, 0, 0, 0, 16, 0,
+ 0, 0, 13, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 8, 0, 0, 0, 62, 7,
+ 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, 32, 0,
+ 0, 0, 32, 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, 84,
+ 101, 120, 116, 117, 114, 101,
+ 50, 68, 0, 175, 1, 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,
+ 229, 1, 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, 87, 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, 167, 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,
+ 232, 4, 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, 68, 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, 72, 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, 164, 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, 28, 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, 120, 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, 1, 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, 93, 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, 91, 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, 159,
+ 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, 142, 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,
+ 210, 118, 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, 167, 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, 235, 133, 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, 226, 143,
+ 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,
+ 38, 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, 1,
+ 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, 69, 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,
+ 66, 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,
+ 134, 183, 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, 110, 191, 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, 202, 195,
+ 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, 242, 199,
+ 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, 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, 78, 204, 0, 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, 117,
+ 214, 0, 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, 209, 218, 0,
+ 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,
+ 188, 228, 0, 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,
+ 24, 233, 0, 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, 19, 244, 0,
+ 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, 111, 248, 0,
+ 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, 74, 253,
+ 0, 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, 166, 1, 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, 213, 1, 0, 0,
+ 185, 1, 0, 0, 0, 0,
+ 0, 0, 255, 255, 255, 255,
+ 0, 0, 0, 0, 217, 1,
+ 0, 0, 185, 1, 0, 0,
+ 0, 0, 0, 0, 255, 255,
+ 255, 255, 0, 0, 0, 0,
+ 224, 1, 0, 0, 185, 1,
+ 0, 0, 0, 0, 0, 0,
+ 255, 255, 255, 255, 0, 0,
+ 0, 0, 14, 2, 0, 0,
+ 242, 1, 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, 23, 2,
+ 0, 0, 55, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 213, 1, 0, 0,
+ 46, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 35, 2, 0, 0, 47, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 47, 2,
+ 0, 0, 0, 0, 0, 0,
+ 59, 2, 0, 0, 242, 1,
+ 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, 71, 2, 0, 0,
+ 55, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 217, 1, 0, 0, 46, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 83, 2,
+ 0, 0, 47, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 95, 2, 0, 0,
+ 0, 0, 0, 0, 107, 2,
+ 0, 0, 242, 1, 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,
+ 120, 2, 0, 0, 55, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 213, 1,
+ 0, 0, 46, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 132, 2, 0, 0,
+ 47, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 144, 2, 0, 0, 0, 0,
+ 0, 0, 156, 2, 0, 0,
+ 242, 1, 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, 171, 2,
+ 0, 0, 55, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 213, 1, 0, 0,
+ 46, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 183, 2, 0, 0, 47, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 195, 2,
+ 0, 0, 0, 0, 0, 0,
+ 207, 2, 0, 0, 242, 1,
+ 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, 220, 2, 0, 0,
+ 55, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 224, 1, 0, 0, 46, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 232, 2,
+ 0, 0, 47, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 244, 2, 0, 0,
+ 0, 0, 0, 0, 0, 3,
+ 0, 0, 242, 1, 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,
+ 15, 3, 0, 0, 55, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 213, 1,
+ 0, 0, 46, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 27, 3, 0, 0,
+ 47, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 39, 3, 0, 0, 52, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 51, 3,
+ 0, 0, 0, 0, 0, 0,
+ 131, 3, 0, 0, 103, 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, 143, 3, 0, 0,
+ 13, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 155, 3, 0, 0, 0, 0,
+ 0, 0, 206, 3, 0, 0,
+ 178, 3, 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, 219, 3,
+ 0, 0, 44, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 231, 3, 0, 0,
+ 0, 0, 0, 0, 243, 3,
+ 0, 0, 178, 3, 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,
+ 0, 4, 0, 0, 38, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 12, 4,
+ 0, 0, 39, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 24, 4, 0, 0,
+ 40, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 36, 4, 0, 0, 41, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 48, 4,
+ 0, 0, 42, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 60, 4, 0, 0,
+ 43, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 72, 4, 0, 0, 44, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 84, 4,
+ 0, 0, 0, 0, 0, 0,
+ 96, 4, 0, 0, 178, 3,
+ 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, 107, 4, 0, 0,
+ 37, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 119, 4, 0, 0, 38, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 131, 4,
+ 0, 0, 39, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 143, 4, 0, 0,
+ 40, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 155, 4, 0, 0, 41, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 167, 4,
+ 0, 0, 42, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 179, 4, 0, 0,
+ 43, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 191, 4, 0, 0, 44, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 203, 4,
+ 0, 0, 0, 0, 0, 0,
+ 215, 4, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 229, 4, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 131, 3, 0, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 48, 9,
+ 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 56, 9, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 28, 12, 0, 0, 36, 12,
+ 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 229, 4,
+ 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 131, 3,
+ 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 144, 16, 0, 0,
+ 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 152, 16, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 240, 29,
+ 0, 0, 248, 29, 0, 0,
+ 1, 0, 0, 0, 0, 0,
+ 0, 0, 229, 4, 0, 0,
+ 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 131, 3, 0, 0,
+ 6, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 100, 34, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 108, 34,
+ 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 212, 51, 0, 0,
+ 220, 51, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 229, 4, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 131, 3, 0, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 73, 56,
+ 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 81, 56, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 57, 94, 0, 0, 65, 94,
+ 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 86, 94,
+ 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 131, 3,
+ 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 139, 101, 0, 0,
+ 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 147, 101, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 131, 111,
+ 0, 0, 139, 111, 0, 0,
+ 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 131, 3, 0, 0,
+ 6, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 190, 118, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 198, 118,
+ 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 150, 126, 0, 0,
+ 158, 126, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 131, 3, 0, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 215, 133,
+ 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 223, 133, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 211, 143, 0, 0, 219, 143,
+ 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 131, 3,
+ 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 18, 151, 0, 0,
+ 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 26, 151, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 238, 158,
+ 0, 0, 246, 158, 0, 0,
+ 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 131, 3, 0, 0,
+ 6, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 49, 166, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 57, 166,
+ 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 49, 176, 0, 0,
+ 57, 176, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 131, 3, 0, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 114, 183,
+ 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 122, 183, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 82, 191, 0, 0, 90, 191,
+ 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 229, 4,
+ 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 131, 3,
+ 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 182, 195, 0, 0,
+ 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 190, 195, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 162, 199,
+ 0, 0, 170, 199, 0, 0,
+ 3, 0, 0, 0, 0, 0,
+ 0, 0, 229, 4, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 131, 3, 0, 0,
+ 10, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 194, 199, 0, 0, 11, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 230, 199,
+ 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 206, 3, 0, 0,
+ 6, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 58, 204, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 66, 204,
+ 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 58, 214, 0, 0,
+ 66, 214, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 131, 3, 0, 0, 10, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 69, 214,
+ 0, 0, 11, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 105, 214, 0, 0,
+ 2, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 243, 3, 0, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 189, 218,
+ 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 197, 218, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 129, 228, 0, 0, 137, 228,
+ 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 131, 3,
+ 0, 0, 10, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 140, 228, 0, 0,
+ 11, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 176, 228, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 243, 3,
+ 0, 0, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 4, 233, 0, 0,
+ 8, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 12, 233, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 192, 243,
+ 0, 0, 200, 243, 0, 0,
+ 2, 0, 0, 0, 0, 0,
+ 0, 0, 218, 243, 0, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 131, 3, 0, 0,
+ 10, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 227, 243, 0, 0, 11, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 7, 244,
+ 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0,
+ 0, 0, 96, 4, 0, 0,
+ 6, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 91, 248, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 99, 248,
+ 0, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0,
+ 0, 0, 11, 253, 0, 0,
+ 19, 253, 0, 0, 7, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 131, 3, 0, 0, 10, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 26, 253,
+ 0, 0, 11, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 62, 253, 0, 0,
+ 2, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 96, 4, 0, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0,
+ 7, 0, 0, 0, 146, 1,
+ 1, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 154, 1, 1, 0,
+ 7, 0, 0, 0, 0, 0,
+ 0, 0, 7, 0, 0, 0,
+ 54, 7, 1, 0
+};
diff --git a/gfx/2d/ShadersD2D1.h b/gfx/2d/ShadersD2D1.h new file mode 100644 index 000000000..2d004a000 --- /dev/null +++ b/gfx/2d/ShadersD2D1.h @@ -0,0 +1,1419 @@ +#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384
+//
+//
+// Buffer Definitions:
+//
+// cbuffer constants
+// {
+//
+// 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
+// constants 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, 20, 173,
+ 189, 124, 239, 6, 22, 67,
+ 226, 55, 243, 56, 30, 182,
+ 172, 36, 1, 0, 0, 0,
+ 180, 13, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0,
+ 136, 3, 0, 0, 232, 9,
+ 0, 0, 100, 10, 0, 0,
+ 4, 13, 0, 0, 128, 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,
+ 152, 2, 0, 0, 1, 0,
+ 0, 0, 0, 1, 0, 0,
+ 5, 0, 0, 0, 28, 0,
+ 0, 0, 0, 4, 255, 255,
+ 0, 1, 0, 0, 100, 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, 115, 116, 97, 110, 116,
+ 115, 0, 246, 0, 0, 0,
+ 8, 0, 0, 0, 24, 1,
+ 0, 0, 80, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 216, 1, 0, 0,
+ 0, 0, 0, 0, 12, 0,
+ 0, 0, 2, 0, 0, 0,
+ 224, 1, 0, 0, 0, 0,
+ 0, 0, 240, 1, 0, 0,
+ 16, 0, 0, 0, 8, 0,
+ 0, 0, 2, 0, 0, 0,
+ 248, 1, 0, 0, 0, 0,
+ 0, 0, 8, 2, 0, 0,
+ 24, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0,
+ 12, 2, 0, 0, 0, 0,
+ 0, 0, 28, 2, 0, 0,
+ 28, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0,
+ 12, 2, 0, 0, 0, 0,
+ 0, 0, 36, 2, 0, 0,
+ 32, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0,
+ 12, 2, 0, 0, 0, 0,
+ 0, 0, 47, 2, 0, 0,
+ 36, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0,
+ 12, 2, 0, 0, 0, 0,
+ 0, 0, 62, 2, 0, 0,
+ 40, 0, 0, 0, 4, 0,
+ 0, 0, 2, 0, 0, 0,
+ 12, 2, 0, 0, 0, 0,
+ 0, 0, 72, 2, 0, 0,
+ 48, 0, 0, 0, 28, 0,
+ 0, 0, 2, 0, 0, 0,
+ 84, 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 constants
+// {
+//
+// 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
+// constants 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, 47, 105,
+ 118, 126, 8, 122, 228, 233,
+ 56, 98, 50, 148, 135, 10,
+ 63, 196, 1, 0, 0, 0,
+ 120, 11, 0, 0, 6, 0,
+ 0, 0, 56, 0, 0, 0,
+ 212, 2, 0, 0, 172, 7,
+ 0, 0, 40, 8, 0, 0,
+ 200, 10, 0, 0, 68, 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, 152, 2,
+ 0, 0, 1, 0, 0, 0,
+ 0, 1, 0, 0, 5, 0,
+ 0, 0, 28, 0, 0, 0,
+ 0, 4, 255, 255, 0, 1,
+ 0, 0, 100, 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, 115,
+ 116, 97, 110, 116, 115, 0,
+ 246, 0, 0, 0, 8, 0,
+ 0, 0, 24, 1, 0, 0,
+ 80, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 216, 1, 0, 0, 0, 0,
+ 0, 0, 12, 0, 0, 0,
+ 2, 0, 0, 0, 224, 1,
+ 0, 0, 0, 0, 0, 0,
+ 240, 1, 0, 0, 16, 0,
+ 0, 0, 8, 0, 0, 0,
+ 2, 0, 0, 0, 248, 1,
+ 0, 0, 0, 0, 0, 0,
+ 8, 2, 0, 0, 24, 0,
+ 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 12, 2,
+ 0, 0, 0, 0, 0, 0,
+ 28, 2, 0, 0, 28, 0,
+ 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 12, 2,
+ 0, 0, 0, 0, 0, 0,
+ 36, 2, 0, 0, 32, 0,
+ 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 12, 2,
+ 0, 0, 0, 0, 0, 0,
+ 47, 2, 0, 0, 36, 0,
+ 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 12, 2,
+ 0, 0, 0, 0, 0, 0,
+ 62, 2, 0, 0, 40, 0,
+ 0, 0, 4, 0, 0, 0,
+ 2, 0, 0, 0, 12, 2,
+ 0, 0, 0, 0, 0, 0,
+ 72, 2, 0, 0, 48, 0,
+ 0, 0, 28, 0, 0, 0,
+ 2, 0, 0, 0, 84, 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
+};
diff --git a/gfx/2d/ShadersD2D1.hlsl b/gfx/2d/ShadersD2D1.hlsl new file mode 100644 index 000000000..42337afc2 --- /dev/null +++ b/gfx/2d/ShadersD2D1.hlsl @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Texture2D InputTexture : register(t0); +SamplerState InputSampler : register(s0); +Texture2D GradientTexture : register(t1); +SamplerState GradientSampler : register(s1); + +cbuffer constants : 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); +} + +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/SourceSurfaceCairo.cpp b/gfx/2d/SourceSurfaceCairo.cpp new file mode 100644 index 000000000..ba8498b84 --- /dev/null +++ b/gfx/2d/SourceSurfaceCairo.cpp @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 000000000..428c75957 --- /dev/null +++ b/gfx/2d/SourceSurfaceCairo.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_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) + // 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(); + + virtual SurfaceType GetType() const { return SurfaceType::CAIRO; } + virtual IntSize GetSize() const; + virtual SurfaceFormat GetFormat() const; + virtual already_AddRefed<DataSourceSurface> GetDataSurface(); + + 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) + explicit DataSourceSurfaceCairo(cairo_surface_t* imageSurf); + virtual ~DataSourceSurfaceCairo(); + virtual unsigned char *GetData(); + virtual int32_t Stride(); + + virtual SurfaceType GetType() const { return SurfaceType::CAIRO_IMAGE; } + virtual IntSize GetSize() const; + virtual SurfaceFormat GetFormat() const; + + 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 000000000..92b2e4d1d --- /dev/null +++ b/gfx/2d/SourceSurfaceD2D1.cpp @@ -0,0 +1,241 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceSurfaceD2D1.h" +#include "DrawTargetD2D1.h" +#include "Tools.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()) + , mDrawTarget(aDT) +{ + aImage->QueryInterface((ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap)); + + mFormat = aFormat; + mSize = aSize; +} + +SourceSurfaceD2D1::~SourceSurfaceD2D1() +{ +} + +bool +SourceSurfaceD2D1::IsValid() const +{ + return mDevice == Factory::GetD2D1Device(); +} + +already_AddRefed<DataSourceSurface> +SourceSurfaceD2D1::GetDataSurface() +{ + HRESULT hr; + + if (!EnsureRealizedBitmap()) { + gfxCriticalError() << "Failed to realize a bitmap, device " << hexa(mDevice); + return nullptr; + } + + 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? + 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() +{ + // 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); + + // 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) + , mMapped(false) +{ +} + +DataSourceSurfaceD2D1::~DataSourceSurfaceD2D1() +{ + if (mMapped) { + 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(!mMapped); + MOZ_ASSERT(!mIsMapped); + + D2D1_MAP_OPTIONS options; + if (aMapType == MapType::READ) { + options = D2D1_MAP_OPTIONS_READ; + } else { + 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 (mMapped) { + return; + } + if (FAILED(mBitmap->Map(D2D1_MAP_OPTIONS_READ, &mMap))) { + gfxCriticalError() << "Failed to map bitmap (EM)."; + return; + } + mMapped = true; +} + +} +} diff --git a/gfx/2d/SourceSurfaceD2D1.h b/gfx/2d/SourceSurfaceD2D1.h new file mode 100644 index 000000000..5b7cedf4c --- /dev/null +++ b/gfx/2d/SourceSurfaceD2D1.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_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) + SourceSurfaceD2D1(ID2D1Image* aImage, ID2D1DeviceContext *aDC, + SurfaceFormat aFormat, const IntSize &aSize, + DrawTargetD2D1 *aDT = nullptr); + ~SourceSurfaceD2D1(); + + virtual SurfaceType GetType() const { return SurfaceType::D2D1_1_IMAGE; } + virtual IntSize GetSize() const { return mSize; } + virtual SurfaceFormat GetFormat() const { return mFormat; } + virtual bool IsValid() const; + virtual already_AddRefed<DataSourceSurface> GetDataSurface(); + + 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; + + SurfaceFormat mFormat; + IntSize mSize; + DrawTargetD2D1* mDrawTarget; +}; + +class DataSourceSurfaceD2D1 : public DataSourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceD2D1) + DataSourceSurfaceD2D1(ID2D1Bitmap1 *aMappableBitmap, SurfaceFormat aFormat); + ~DataSourceSurfaceD2D1(); + + virtual SurfaceType GetType() const { return SurfaceType::DATA; } + virtual IntSize GetSize() const; + virtual SurfaceFormat GetFormat() const { return mFormat; } + virtual bool IsValid() const { return !!mBitmap; } + virtual uint8_t *GetData(); + virtual int32_t Stride(); + virtual bool Map(MapType, MappedSurface *aMappedSurface); + virtual void Unmap(); + +private: + friend class SourceSurfaceD2DTarget; + void EnsureMapped(); + + mutable RefPtr<ID2D1Bitmap1> mBitmap; + SurfaceFormat mFormat; + D2D1_MAPPED_RECT mMap; + bool mMapped; +}; + +} +} + +#endif /* MOZILLA_GFX_SOURCESURFACED2D2TARGET_H_ */ diff --git a/gfx/2d/SourceSurfaceDual.h b/gfx/2d/SourceSurfaceDual.h new file mode 100644 index 000000000..df81b87f2 --- /dev/null +++ b/gfx/2d/SourceSurfaceDual.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACEDUAL_H_ +#define MOZILLA_GFX_SOURCESURFACEDUAL_H_ + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class DualSurface; +class DualPattern; + +class SourceSurfaceDual : public SourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceDual) + SourceSurfaceDual(DrawTarget *aDTA, DrawTarget *aDTB) + : mA(aDTA->Snapshot()) + , mB(aDTB->Snapshot()) + { } + + virtual SurfaceType GetType() const { return SurfaceType::DUAL_DT; } + virtual IntSize GetSize() const { return mA->GetSize(); } + virtual SurfaceFormat GetFormat() const { return mA->GetFormat(); } + + // This is implemented for debugging purposes only (used by dumping + // client-side textures for paint dumps), for which we don't care about + // component alpha, so we just use the first of the two surfaces. + virtual already_AddRefed<DataSourceSurface> GetDataSurface() { + return mA->GetDataSurface(); + } +private: + friend class DualSurface; + friend class DualPattern; + + RefPtr<SourceSurface> mA; + RefPtr<SourceSurface> mB; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACEDUAL_H_ */ diff --git a/gfx/2d/SourceSurfaceRawData.cpp b/gfx/2d/SourceSurfaceRawData.cpp new file mode 100644 index 000000000..9d95e9900 --- /dev/null +++ b/gfx/2d/SourceSurfaceRawData.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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; + + if (aDeallocator) { + mOwnData = true; + } + mDeallocator = aDeallocator; + mClosure = aClosure; +} + +void +SourceSurfaceRawData::GuaranteePersistance() +{ + if (mOwnData) { + return; + } + + uint8_t* oldData = mRawData; + mRawData = new uint8_t[mStride * mSize.height]; + + memcpy(mRawData, oldData, mStride * mSize.height); + mOwnData = true; +} + +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; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SourceSurfaceRawData.h b/gfx/2d/SourceSurfaceRawData.h new file mode 100644 index 000000000..f4c707198 --- /dev/null +++ b/gfx/2d/SourceSurfaceRawData.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACERAWDATA_H_ +#define MOZILLA_GFX_SOURCESURFACERAWDATA_H_ + +#include "2D.h" +#include "Tools.h" +#include "mozilla/Atomics.h" + +namespace mozilla { +namespace gfx { + +class SourceSurfaceRawData : public DataSourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceRawData, override) + + SourceSurfaceRawData() + : mRawData(0) + , mStride(0) + , mFormat(SurfaceFormat::UNKNOWN) + , mMapCount(0) + , mOwnData(false) + , mDeallocator(nullptr) + , mClosure(nullptr) + { + } + + virtual ~SourceSurfaceRawData() + { + if (mDeallocator) { + mDeallocator(mClosure); + } else if (mOwnData) { + // The buffer is created from GuaranteePersistance(). + delete [] mRawData; + } + + MOZ_ASSERT(mMapCount == 0); + } + + 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; } + + virtual void GuaranteePersistance() override; + + // 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) override + { + aMappedSurface->mData = GetData(); + aMappedSurface->mStride = Stride(); + bool success = !!aMappedSurface->mData; + if (success) { + mMapCount++; + } + return success; + } + + virtual void Unmap() override + { + mMapCount--; + MOZ_ASSERT(mMapCount >= 0); + } + +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; + Atomic<int32_t> mMapCount; + + bool mOwnData; + Factory::SourceSurfaceDeallocator mDeallocator; + void* mClosure; +}; + +class SourceSurfaceAlignedRawData : public DataSourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceAlignedRawData, override) + SourceSurfaceAlignedRawData() + : mStride(0) + , mFormat(SurfaceFormat::UNKNOWN) + , mMapCount(0) + {} + ~SourceSurfaceAlignedRawData() + { + MOZ_ASSERT(mMapCount == 0); + } + + virtual uint8_t* GetData() override { return mArray; } + 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; } + + virtual bool Map(MapType, MappedSurface *aMappedSurface) override + { + aMappedSurface->mData = GetData(); + aMappedSurface->mStride = Stride(); + bool success = !!aMappedSurface->mData; + if (success) { + mMapCount++; + } + return success; + } + + virtual void Unmap() override + { + mMapCount--; + MOZ_ASSERT(mMapCount >= 0); + } + +private: + friend class Factory; + + bool Init(const IntSize &aSize, + SurfaceFormat aFormat, + bool aClearMem, + uint8_t aClearValue, + int32_t aStride = 0); + + AlignedArray<uint8_t> mArray; + int32_t mStride; + SurfaceFormat mFormat; + IntSize mSize; + Atomic<int32_t> mMapCount; +}; + +} // 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 000000000..14cbf6a84 --- /dev/null +++ b/gfx/2d/SourceSurfaceSkia.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "Logging.h" +#include "SourceSurfaceSkia.h" +#include "HelpersSkia.h" +#include "DrawTargetSkia.h" +#include "DataSurfaceHelpers.h" +#include "skia/include/core/SkData.h" +#include "mozilla/CheckedInt.h" + +namespace mozilla { +namespace gfx { + +SourceSurfaceSkia::SourceSurfaceSkia() + : mDrawTarget(nullptr) +{ +} + +SourceSurfaceSkia::~SourceSurfaceSkia() +{ + if (mDrawTarget) { + mDrawTarget->SnapshotDestroyed(); + mDrawTarget = nullptr; + } +} + +IntSize +SourceSurfaceSkia::GetSize() const +{ + return mSize; +} + +SurfaceFormat +SourceSurfaceSkia::GetFormat() const +{ + return mFormat; +} + +static sk_sp<SkData> +MakeSkData(unsigned char* aData, const IntSize& aSize, int32_t aStride) +{ + CheckedInt<size_t> size = aStride; + size *= aSize.height; + 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; +} + +bool +SourceSurfaceSkia::InitFromData(unsigned char* aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) +{ + sk_sp<SkData> data = MakeSkData(aData, aSize, 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(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 = SkAlign4(info.minRowBytes()); + } else { + return false; + } + + mImage = aImage; + + if (aOwner) { + mDrawTarget = aOwner; + } + + return true; +} + +uint8_t* +SourceSurfaceSkia::GetData() +{ +#ifdef USE_SKIA_GPU + if (mImage->isTextureBacked()) { + sk_sp<SkImage> raster; + if (sk_sp<SkData> data = MakeSkData(nullptr, mSize, mStride)) { + SkImageInfo info = MakeSkiaImageInfo(mSize, mFormat); + if (mImage->readPixels(info, data->writable_data(), mStride, 0, 0, SkImage::kDisallow_CachingHint)) { + raster = SkImage::MakeRasterData(info, data, mStride); + } + } + if (raster) { + mImage = raster; + } else { + gfxCriticalError() << "Failed making Skia raster image for GPU surface"; + } + } +#endif + SkPixmap pixmap; + if (!mImage->peekPixels(&pixmap)) { + gfxCriticalError() << "Failed accessing pixels for Skia raster image"; + } + return reinterpret_cast<uint8_t*>(pixmap.writable_addr()); +} + +void +SourceSurfaceSkia::DrawTargetWillChange() +{ + if (mDrawTarget) { + // 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 = SkImage::MakeRasterCopy(pixmap); + if (!mImage) { + gfxCriticalError() << "Failed copying Skia raster snapshot"; + } + } + mDrawTarget = nullptr; + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SourceSurfaceSkia.h b/gfx/2d/SourceSurfaceSkia.h new file mode 100644 index 000000000..a91ae93ed --- /dev/null +++ b/gfx/2d/SourceSurfaceSkia.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACESKIA_H_ +#define MOZILLA_GFX_SOURCESURFACESKIA_H_ + +#include "2D.h" +#include <vector> +#include "skia/include/core/SkCanvas.h" +#include "skia/include/core/SkImage.h" + +namespace mozilla { + +namespace gfx { + +class DrawTargetSkia; + +class SourceSurfaceSkia : public DataSourceSurface +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceSkia) + SourceSurfaceSkia(); + ~SourceSurfaceSkia(); + + virtual SurfaceType GetType() const { return SurfaceType::SKIA; } + virtual IntSize GetSize() const; + virtual SurfaceFormat GetFormat() const; + + sk_sp<SkImage>& GetImage() { return mImage; } + + bool InitFromData(unsigned char* aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat); + + bool InitFromImage(sk_sp<SkImage> aImage, + SurfaceFormat aFormat = SurfaceFormat::UNKNOWN, + DrawTargetSkia* aOwner = nullptr); + + virtual uint8_t* GetData(); + + virtual int32_t Stride() { return mStride; } + +private: + friend class DrawTargetSkia; + + void DrawTargetWillChange(); + + sk_sp<SkImage> mImage; + SurfaceFormat mFormat; + IntSize mSize; + int32_t mStride; + RefPtr<DrawTargetSkia> mDrawTarget; +}; + +} // 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 000000000..a2451f930 --- /dev/null +++ b/gfx/2d/StackArray.h @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 +{ +public: + 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/Tools.h b/gfx/2d/Tools.h new file mode 100644 index 000000000..585cbbb2c --- /dev/null +++ b/gfx/2d/Tools.h @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TOOLS_H_ +#define MOZILLA_GFX_TOOLS_H_ + +#include "mozilla/CheckedInt.h" +#include "mozilla/Move.h" +#include "mozilla/TypeTraits.h" +#include "Types.h" +#include "Point.h" +#include <math.h> + +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 Float +Distance(Point aA, Point aB) +{ + return hypotf(aB.x - aA.x, aB.y - aA.y); +} + +static inline int +BytesPerPixel(SurfaceFormat aFormat) +{ + switch (aFormat) { + case SurfaceFormat::A8: + return 1; + case SurfaceFormat::R5G6B5_UINT16: + 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; + } +} + +static inline bool +IsOpaqueFormat(SurfaceFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + case SurfaceFormat::X8R8G8B8: + case SurfaceFormat::YUV: + case SurfaceFormat::NV12: + case SurfaceFormat::YUV422: + case SurfaceFormat::R5G6B5_UINT16: + return true; + default: + return false; + } +} + +template<typename T, int alignment = 16> +struct AlignedArray +{ + typedef T value_type; + + AlignedArray() + : mPtr(nullptr) + , mStorage(nullptr) + { + } + + explicit MOZ_ALWAYS_INLINE AlignedArray(size_t aCount, bool aZero = false) + : 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(mozilla::IsPod<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(1, 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) + { + mozilla::Swap(mPtr, aOther.mPtr); + mozilla::Swap(mStorage, aOther.mStorage); + mozilla::Swap(mCount, aOther.mCount); + } + + 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 000000000..c667abe47 --- /dev/null +++ b/gfx/2d/Triangle.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TRIANGLE_H +#define MOZILLA_GFX_TRIANGLE_H + +#include <algorithm> + +#include "mozilla/Move.h" +#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; + F width, height; + + 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>(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.h b/gfx/2d/Types.h new file mode 100644 index 000000000..7b1676ab2 --- /dev/null +++ b/gfx/2d/Types.h @@ -0,0 +1,407 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TYPES_H_ +#define MOZILLA_GFX_TYPES_H_ + +#include "mozilla/EndianUtils.h" + +#include <stddef.h> +#include <stdint.h> + +namespace mozilla { +namespace gfx { + +typedef float Float; + +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 */ + DUAL_DT, /* Snapshot of a dual drawtarget */ + D2D1_1_IMAGE, /* A D2D 1.1 ID2D1Image SourceSurface */ + RECORDING, /* Surface used for recording */ + TILED /* Surface from a tiled DrawTarget */ +}; + +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, + + // These ones are their own special cases. + YUV, + NV12, + YUV422, + HSV, + Lab, + Depth, + + // This represents the unknown format. + UNKNOWN, + + // 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 +}; + +inline bool IsOpaque(SurfaceFormat aFormat) +{ + switch (aFormat) { + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::YUV: + case SurfaceFormat::NV12: + case SurfaceFormat::YUV422: + return true; + default: + return false; + } +} + +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 +}; + +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, + + // Add new entries above this line. + BACKEND_LAST +}; + +enum class FontType : int8_t { + DWRITE, + GDI, + MAC, + SKIA, + CAIRO, + COREGRAPHICS, + FONTCONFIG +}; + +enum class NativeSurfaceType : int8_t { + D3D10_TEXTURE, + CAIRO_CONTEXT, + CGCONTEXT, + CGCONTEXT_ACCELERATED, + OPENGL_TEXTURE +}; + +enum class NativeFontType : int8_t { + DWRITE_FONT_FACE, + GDI_FONT_FACE, + MAC_FONT_FACE, + SKIA_FONT_FACE, + CAIRO_FONT_FACE +}; + +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_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 +}; + +enum class PatternType : int8_t { + COLOR, + SURFACE, + LINEAR_GRADIENT, + RADIAL_GRADIENT +}; + +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 +}; + +/* Color is stored in non-premultiplied form */ +struct Color +{ +public: + Color() + : r(0.0f), g(0.0f), b(0.0f), a(0.0f) + {} + Color(Float aR, Float aG, Float aB, Float aA) + : r(aR), g(aG), b(aB), a(aA) + {} + Color(Float aR, Float aG, Float aB) + : r(aR), g(aG), b(aB), a(1.0f) + {} + + static Color FromABGR(uint32_t aColor) + { + Color 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 Color UnusualFromARGB(uint32_t aColor) + { + Color 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 Color& aColor) const { + return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a; + } + + bool operator!=(const Color& aColor) const { + return !(*this == aColor); + } + + Float r, g, b, a; +}; + +struct GradientStop +{ + bool operator<(const GradientStop& aOther) const { + return offset < aOther.offset; + } + + Float offset; + Color 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 { + +// We can't use MOZ_BEGIN_ENUM_CLASS here because that prevents the enum +// values from being used for indexing. Wrapping the enum in a struct does at +// least gives us name scoping. +struct RectCorner { + enum { + // This order is important since Rect::AtCorner, AppendRoundedRectToPath + // and other code depends on it! + TopLeft = 0, + TopRight = 1, + BottomRight = 2, + BottomLeft = 3, + Count = 4 + }; +}; + +// Side constants for use in various places. +enum Side { eSideTop, eSideRight, eSideBottom, eSideLeft }; + +enum SideBits { + eSideBitsNone = 0, + eSideBitsTop = 1 << eSideTop, + eSideBitsRight = 1 << eSideRight, + eSideBitsBottom = 1 << eSideBottom, + eSideBitsLeft = 1 << eSideLeft, + eSideBitsTopBottom = eSideBitsTop | eSideBitsBottom, + eSideBitsLeftRight = eSideBitsLeft | eSideBitsRight, + eSideBitsAll = eSideBitsTopBottom | eSideBitsLeftRight +}; + +} // namespace mozilla + +#define NS_SIDE_TOP mozilla::eSideTop +#define NS_SIDE_RIGHT mozilla::eSideRight +#define NS_SIDE_BOTTOM mozilla::eSideBottom +#define NS_SIDE_LEFT mozilla::eSideLeft + +#endif /* MOZILLA_GFX_TYPES_H_ */ diff --git a/gfx/2d/UserData.h b/gfx/2d/UserData.h new file mode 100644 index 000000000..a10dbc8b8 --- /dev/null +++ b/gfx/2d/UserData.h @@ -0,0 +1,128 @@ +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_USERDATA_H_ +#define MOZILLA_GFX_USERDATA_H_ + +#include <stdlib.h> +#include "Types.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace gfx { + +struct UserDataKey { + int unused; +}; + +/* this class is basically a clone of the user data concept from cairo */ +class UserData +{ + typedef void (*destroyFunc)(void *data); +public: + 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; + } + + /* 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() + { + 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; + +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_USERDATA_H_ */ diff --git a/gfx/2d/convolver.cpp b/gfx/2d/convolver.cpp new file mode 100644 index 000000000..0221f1563 --- /dev/null +++ b/gfx/2d/convolver.cpp @@ -0,0 +1,562 @@ +// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#include "2D.h" +#include "convolver.h" + +#include <algorithm> + +#include "skia/include/core/SkTypes.h" + + +#if defined(USE_SSE2) +#include "convolverSSE2.h" +#endif + +#if defined(_MIPS_ARCH_LOONGSON3A) +#include "convolverLS3.h" +#endif + +using mozilla::gfx::Factory; + +#if defined(SK_CPU_LENDIAN) +#define R_OFFSET_IDX 0 +#define G_OFFSET_IDX 1 +#define B_OFFSET_IDX 2 +#define A_OFFSET_IDX 3 +#else +#define R_OFFSET_IDX 3 +#define G_OFFSET_IDX 2 +#define B_OFFSET_IDX 1 +#define A_OFFSET_IDX 0 +#endif + +#if defined(USE_SSE2) +#define ConvolveHorizontally4_SIMD ConvolveHorizontally4_SSE2 +#define ConvolveHorizontally_SIMD ConvolveHorizontally_SSE2 +#define ConvolveVertically_SIMD ConvolveVertically_SSE2 +#endif + +#if defined(_MIPS_ARCH_LOONGSON3A) +#define ConvolveHorizontally4_SIMD ConvolveHorizontally4_LS3 +#define ConvolveHorizontally_SIMD ConvolveHorizontally_LS3 +#define ConvolveVertically_SIMD ConvolveVertically_LS3 +#endif + +namespace skia { + +namespace { + +// Converts the argument to an 8-bit unsigned value by clamping to the range +// 0-255. +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; +} + +// 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 |source_row_pixel_width|. + // The maximum number of rows needed in the buffer is |max_y_filter_size| + // (we only need to store enough rows for the biggest filter). + // + // We use the |first_input_row| to compute the coordinates of all of the + // following rows returned by Advance(). + CircularRowBuffer(int dest_row_pixel_width, int max_y_filter_size, + int first_input_row) + : row_byte_width_(dest_row_pixel_width * 4), + num_rows_(max_y_filter_size), + next_row_(0), + next_row_coordinate_(first_input_row) { + buffer_.resize(row_byte_width_ * max_y_filter_size); + row_addresses_.resize(num_rows_); + } + + // Moves to the next row in the buffer, returning a pointer to the beginning + // of it. + unsigned char* AdvanceRow() { + unsigned char* row = &buffer_[next_row_ * row_byte_width_]; + next_row_coordinate_++; + + // Set the pointer to the next row to use, wrapping around if necessary. + next_row_++; + if (next_row_ == num_rows_) + next_row_ = 0; + return row; + } + + // Returns a pointer to an "unrolled" array of rows. These rows will start + // at the y coordinate placed into |*first_row_index| and will continue in + // order for the maximum number of rows in this circular buffer. + // + // The |first_row_index_| 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* first_row_index) { + // Example for a 4-element circular buffer holding coords 6-9. + // Row 0 Coord 8 + // Row 1 Coord 9 + // Row 2 Coord 6 <- next_row_ = 2, next_row_coordinate_ = 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 first_row_index and the negative rows will never be used. + *first_row_index = next_row_coordinate_ - num_rows_; + + int cur_row = next_row_; + for (int i = 0; i < num_rows_; i++) { + row_addresses_[i] = &buffer_[cur_row * row_byte_width_]; + + // Advance to the next row, wrapping if necessary. + cur_row++; + if (cur_row == num_rows_) + cur_row = 0; + } + return &row_addresses_[0]; + } + + private: + // The buffer storing the rows. They are packed, each one row_byte_width_. + std::vector<unsigned char> buffer_; + + // Number of bytes per row in the |buffer_|. + int row_byte_width_; + + // The number of rows available in the buffer. + int num_rows_; + + // The next row index we should write into. This wraps around as the + // circular buffer is used. + int next_row_; + + // The y coordinate of the |next_row_|. This is incremented each time a + // new row is appended and does not wrap. + int next_row_coordinate_; + + // Buffer used by GetRowAddresses(). + std::vector<unsigned char*> row_addresses_; +}; + +} // namespace + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +template<bool has_alpha> +void ConvolveHorizontally(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row) { + int num_values = filter.num_values(); + // Loop over each pixel on this row in the output image. + for (int out_x = 0; out_x < num_values; out_x++) { + // Get the filter that determines the current output pixel. + int filter_offset, filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const unsigned char* row_to_filter = &src_data[filter_offset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int accum[4] = {0}; + for (int filter_x = 0; filter_x < filter_length; filter_x++) { + ConvolutionFilter1D::Fixed cur_filter = filter_values[filter_x]; + accum[0] += cur_filter * row_to_filter[filter_x * 4 + R_OFFSET_IDX]; + accum[1] += cur_filter * row_to_filter[filter_x * 4 + G_OFFSET_IDX]; + accum[2] += cur_filter * row_to_filter[filter_x * 4 + B_OFFSET_IDX]; + if (has_alpha) + accum[3] += cur_filter * row_to_filter[filter_x * 4 + A_OFFSET_IDX]; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum[0] >>= ConvolutionFilter1D::kShiftBits; + accum[1] >>= ConvolutionFilter1D::kShiftBits; + accum[2] >>= ConvolutionFilter1D::kShiftBits; + if (has_alpha) + accum[3] >>= ConvolutionFilter1D::kShiftBits; + + // Store the new pixel. + out_row[out_x * 4 + R_OFFSET_IDX] = ClampTo8(accum[0]); + out_row[out_x * 4 + G_OFFSET_IDX] = ClampTo8(accum[1]); + out_row[out_x * 4 + B_OFFSET_IDX] = ClampTo8(accum[2]); + if (has_alpha) + out_row[out_x * 4 + A_OFFSET_IDX] = 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 |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +template<bool has_alpha> +void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row) { + // We go through each column in the output and do a vertical convolution, + // generating one output pixel each time. + for (int out_x = 0; out_x < pixel_width; out_x++) { + // 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 byte_offset = out_x * 4; + + // Apply the filter to one column of pixels. + int accum[4] = {0}; + for (int filter_y = 0; filter_y < filter_length; filter_y++) { + ConvolutionFilter1D::Fixed cur_filter = filter_values[filter_y]; + accum[0] += cur_filter + * source_data_rows[filter_y][byte_offset + R_OFFSET_IDX]; + accum[1] += cur_filter + * source_data_rows[filter_y][byte_offset + G_OFFSET_IDX]; + accum[2] += cur_filter + * source_data_rows[filter_y][byte_offset + B_OFFSET_IDX]; + if (has_alpha) + accum[3] += cur_filter + * source_data_rows[filter_y][byte_offset + A_OFFSET_IDX]; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of precision. + accum[0] >>= ConvolutionFilter1D::kShiftBits; + accum[1] >>= ConvolutionFilter1D::kShiftBits; + accum[2] >>= ConvolutionFilter1D::kShiftBits; + if (has_alpha) + accum[3] >>= ConvolutionFilter1D::kShiftBits; + + // Store the new pixel. + out_row[byte_offset + R_OFFSET_IDX] = ClampTo8(accum[0]); + out_row[byte_offset + G_OFFSET_IDX] = ClampTo8(accum[1]); + out_row[byte_offset + B_OFFSET_IDX] = ClampTo8(accum[2]); + if (has_alpha) { + 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 max_color_channel = std::max(out_row[byte_offset + R_OFFSET_IDX], + std::max(out_row[byte_offset + G_OFFSET_IDX], out_row[byte_offset + B_OFFSET_IDX])); + if (alpha < max_color_channel) + out_row[byte_offset + A_OFFSET_IDX] = max_color_channel; + else + out_row[byte_offset + A_OFFSET_IDX] = alpha; + } else { + // No alpha channel, the image is opaque. + out_row[byte_offset + A_OFFSET_IDX] = 0xff; + } + } +} + +void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, unsigned char* out_row, + bool has_alpha, bool use_simd) { + +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) + // If the binary was not built with SSE2 support, we had to fallback to C version. + if (use_simd) { + ConvolveVertically_SIMD(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row, has_alpha); + } else +#endif + { + if (has_alpha) { + ConvolveVertically<true>(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row); + } else { + ConvolveVertically<false>(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row); + } + } +} + +void ConvolveHorizontally(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool has_alpha, bool use_simd) { + int width = filter.num_values(); + int processed = 0; +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) + int simd_width = width & ~3; + if (use_simd && simd_width) { + // SIMD implementation works with 4 pixels at a time. + // Therefore we process as much as we can using SSE and then use + // C implementation for leftovers + ConvolveHorizontally_SIMD(src_data, filter, out_row); + processed = simd_width; + } +#endif + + if (width > processed) { +#if defined(_MIPS_ARCH_LOONGSON3A) + ConvolveHorizontally1_LS3(src_data, filter, out_row); +#else + if (has_alpha) { + ConvolveHorizontally<true>(src_data, filter, out_row); + } else { + ConvolveHorizontally<false>(src_data, filter, out_row); + } +#endif + } +} + +// ConvolutionFilter1D --------------------------------------------------------- + +ConvolutionFilter1D::ConvolutionFilter1D() + : max_filter_(0) { +} + +ConvolutionFilter1D::~ConvolutionFilter1D() { +} + +void ConvolutionFilter1D::AddFilter(int filter_offset, + const float* filter_values, + int filter_length) { + SkASSERT(filter_length > 0); + + std::vector<Fixed> fixed_values; + fixed_values.reserve(filter_length); + + for (int i = 0; i < filter_length; ++i) + fixed_values.push_back(FloatToFixed(filter_values[i])); + + AddFilter(filter_offset, &fixed_values[0], filter_length); +} + +void ConvolutionFilter1D::AddFilter(int filter_offset, + const Fixed* filter_values, + int filter_length) { + // 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 first_non_zero = 0; + while (first_non_zero < filter_length && filter_values[first_non_zero] == 0) + first_non_zero++; + + if (first_non_zero < filter_length) { + // Here we have at least one non-zero factor. + int last_non_zero = filter_length - 1; + while (last_non_zero >= 0 && filter_values[last_non_zero] == 0) + last_non_zero--; + + filter_offset += first_non_zero; + filter_length = last_non_zero + 1 - first_non_zero; + SkASSERT(filter_length > 0); + + for (int i = first_non_zero; i <= last_non_zero; i++) + filter_values_.push_back(filter_values[i]); + } else { + // Here all the factors were zeroes. + filter_length = 0; + } + + FilterInstance instance; + + // We pushed filter_length elements onto filter_values_ + instance.data_location = (static_cast<int>(filter_values_.size()) - + filter_length); + instance.offset = filter_offset; + instance.length = filter_length; + filters_.push_back(instance); + + max_filter_ = std::max(max_filter_, filter_length); +} + +void BGRAConvolve2D(const unsigned char* source_data, + int source_byte_row_stride, + bool source_has_alpha, + const ConvolutionFilter1D& filter_x, + const ConvolutionFilter1D& filter_y, + int output_byte_row_stride, + unsigned char* output) { + bool use_simd = Factory::HasSSE2(); + +#if !defined(USE_SSE2) + // Even we have runtime support for SSE2 instructions, since the binary + // was not built with SSE2 support, we had to fallback to C version. + use_simd = false; +#endif + +#if defined(_MIPS_ARCH_LOONGSON3A) + use_simd = true; +#endif + + + int max_y_filter_size = filter_y.max_filter(); + + // 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 filter_offset, filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter_y.FilterForValue(0, &filter_offset, &filter_length); + int next_x_row = filter_offset; + + // 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 padding each row in row buffer to be aligned-up to + // 16 bytes. + // TODO(jiesun): We do not use aligned load from row buffer in vertical + // convolution pass yet. Somehow Windows does not like it. + int row_buffer_width = (filter_x.num_values() + 15) & ~0xF; + int row_buffer_height = max_y_filter_size + (use_simd ? 4 : 0); + CircularRowBuffer row_buffer(row_buffer_width, + row_buffer_height, + filter_offset); + + // Loop over every possible output row, processing just enough horizontal + // convolutions to run each subsequent vertical convolution. + SkASSERT(output_byte_row_stride >= filter_x.num_values() * 4); + int num_output_rows = filter_y.num_values(); + int pixel_width = filter_x.num_values(); + + + // We need to check which is the last line to convolve before we advance 4 + // lines in one iteration. + int last_filter_offset, last_filter_length; + // SSE2 can access up to 3 extra pixels past the end of the + // buffer. At the bottom of the image, we have to be careful + // not to access data past the end of the buffer. Normally + // we fall back to the C++ implementation for the last row. + // If the last row is less than 3 pixels wide, we may have to fall + // back to the C++ version for more rows. Compute how many + // rows we need to avoid the SSE implementation for here. + filter_x.FilterForValue(filter_x.num_values() - 1, &last_filter_offset, + &last_filter_length); +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) + int avoid_simd_rows = 1 + 3 / + (last_filter_offset + last_filter_length); +#endif + filter_y.FilterForValue(num_output_rows - 1, &last_filter_offset, + &last_filter_length); + + for (int out_y = 0; out_y < num_output_rows; out_y++) { + filter_values = filter_y.FilterForValue(out_y, + &filter_offset, &filter_length); + + // Generate output rows until we have enough to run the current filter. + if (use_simd) { +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) + // We don't want to process too much rows in batches of 4 because + // we can go out-of-bounds at the end + while (next_x_row < filter_offset + filter_length) { + if (next_x_row + 3 < last_filter_offset + last_filter_length - + avoid_simd_rows) { + const unsigned char* src[4]; + unsigned char* out_row[4]; + for (int i = 0; i < 4; ++i) { + src[i] = &source_data[(next_x_row + i) * source_byte_row_stride]; + out_row[i] = row_buffer.AdvanceRow(); + } + ConvolveHorizontally4_SIMD(src, filter_x, out_row); + next_x_row += 4; + } else { + // Check if we need to avoid SSE2 for this row. + if (next_x_row < last_filter_offset + last_filter_length - + avoid_simd_rows) { + ConvolveHorizontally_SIMD( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } else { + if (source_has_alpha) { + ConvolveHorizontally<true>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } else { + ConvolveHorizontally<false>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } + } + next_x_row++; + } + } +#endif + } else { + while (next_x_row < filter_offset + filter_length) { + if (source_has_alpha) { + ConvolveHorizontally<true>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } else { + ConvolveHorizontally<false>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } + next_x_row++; + } + } + + // Compute where in the output image this row of final data will go. + unsigned char* cur_output_row = &output[out_y * output_byte_row_stride]; + + // Get the list of rows that the circular buffer has, in order. + int first_row_in_circular_buffer; + unsigned char* const* rows_to_convolve = + row_buffer.GetRowAddresses(&first_row_in_circular_buffer); + + // Now compute the start of the subset of those rows that the filter + // needs. + unsigned char* const* first_row_for_filter = + &rows_to_convolve[filter_offset - first_row_in_circular_buffer]; + + ConvolveVertically(filter_values, filter_length, + first_row_for_filter, pixel_width, + cur_output_row, source_has_alpha, use_simd); + } +} + +} // namespace skia diff --git a/gfx/2d/convolver.h b/gfx/2d/convolver.h new file mode 100644 index 000000000..0c4591ac8 --- /dev/null +++ b/gfx/2d/convolver.h @@ -0,0 +1,201 @@ +// Copyright (c) 2006-2012 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#ifndef SKIA_EXT_CONVOLVER_H_ +#define SKIA_EXT_CONVOLVER_H_ + +#include <cmath> +#include <vector> + +#include "base/basictypes.h" +#include "mozilla/Assertions.h" +#include "skia/include/core/SkTypes.h" + +// avoid confusion with Mac OS X's math library (Carbon) +#if defined(__APPLE__) +#undef FloatToFixed +#undef FixedToFloat +#endif + +namespace skia { + +// 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 +// ConvolutionFilter1D, then convolving each column by another one. +// +// Entries are stored in fixed point, shifted left by kShiftBits. +class ConvolutionFilter1D { + public: + typedef short Fixed; + + // The number of bits that fixed point values are shifted by. + enum { kShiftBits = 14 }; + + ConvolutionFilter1D(); + ~ConvolutionFilter1D(); + + // Convert between floating point and our fixed point representation. + static Fixed FloatToFixed(float f) { + return static_cast<Fixed>(f * (1 << kShiftBits)); + } + static unsigned char FixedToChar(Fixed x) { + return static_cast<unsigned char>(x >> kShiftBits); + } + static float FixedToFloat(Fixed x) { + // The cast relies on Fixed being a short, implying that on + // the platforms we care about all (16) bits will fit into + // the mantissa of a (32-bit) float. + static_assert(sizeof(Fixed) == 2, + "fixed type should fit in float mantissa"); + float raw = static_cast<float>(x); + return ldexpf(raw, -kShiftBits); + } + + // Returns the maximum pixel span of a filter. + int max_filter() const { return max_filter_; } + + // Returns the number of filters in this filter. This is the dimension of the + // output image. + int num_values() const { return static_cast<int>(filters_.size()); } + + // Appends the given list of scaling values for generating a given output + // pixel. |filter_offset| 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 |filter_length| pixels. + // + // You will probably want to make sure your input is normalized (that is, + // all entries in |filter_values| sub to one) to prevent affecting the overall + // brighness of the image. + // + // The filter_length must be > 0. + // + // This version will automatically convert your input to fixed point. + void AddFilter(int filter_offset, + const float* filter_values, + int filter_length); + + // Same as the above version, but the input is already fixed point. + void AddFilter(int filter_offset, + const Fixed* filter_values, + int filter_length); + + // Retrieves a filter for the given |value_offset|, 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 |filter_length| values in this array. + inline const Fixed* FilterForValue(int value_offset, + int* filter_offset, + int* filter_length) const { + const FilterInstance& filter = filters_[value_offset]; + *filter_offset = filter.offset; + *filter_length = filter.length; + if (filter.length == 0) { + return NULL; + } + return &filter_values_[filter.data_location]; + } + + + inline void PaddingForSIMD(int padding_count) { + // Padding |padding_count| of more dummy coefficients after the coefficients + // of last filter to prevent SIMD instructions which load 8 or 16 bytes + // together to access invalid memory areas. We are not trying to align the + // coefficients right now due to the opaqueness of <vector> implementation. + // This has to be done after all |AddFilter| calls. + for (int i = 0; i < padding_count; ++i) + filter_values_.push_back(static_cast<Fixed>(0)); + } + + private: + struct FilterInstance { + // Offset within filter_values for this instance of the filter. + int data_location; + + // Distance from the left of the filter to the center. IN PIXELS + int offset; + + // Number of values in this filter instance. + int length; + }; + + // Stores the information for each filter added to this class. + std::vector<FilterInstance> filters_; + + // 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<Fixed> filter_values_; + + // The maximum size of any filter we've added. + int max_filter_; +}; + +// 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 |source_byte_row_stride| 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.num_values() * yfilter.num_values() pixels. It will be +// in rows of exactly xfilter.num_values() * 4 bytes. +// +// |source_has_alpha| 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). +void BGRAConvolve2D(const unsigned char* source_data, + int source_byte_row_stride, + bool source_has_alpha, + const ConvolutionFilter1D& xfilter, + const ConvolutionFilter1D& yfilter, + int output_byte_row_stride, + unsigned char* output); + +void ConvolveHorizontally(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool has_alpha, bool use_sse2); + +void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, unsigned char* out_row, + bool has_alpha, bool use_sse2); + +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_H_ diff --git a/gfx/2d/convolverLS3.cpp b/gfx/2d/convolverLS3.cpp new file mode 100644 index 000000000..a1a41c98b --- /dev/null +++ b/gfx/2d/convolverLS3.cpp @@ -0,0 +1,927 @@ +// Copyright (c) 2014-2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#include "convolver.h" +#include <algorithm> +#include "skia/include/core/SkTypes.h" + +#if defined(_MIPS_ARCH_LOONGSON3A) + +#include "MMIHelpers.h" + +namespace skia { + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the num_values() of the filter. +void ConvolveHorizontally_LS3(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row) { + int num_values = filter.num_values(); + int tmp, filter_offset, filter_length; + double zero, mask[4], shuf_50, shuf_fa; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "xor %[zero], %[zero], %[zero] \n\t" + // |mask| will be used to decimate all extra filter coefficients that are + // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. + "li %[tmp], 1 \n\t" + "dsll32 %[tmp], 0x10 \n\t" + "daddiu %[tmp], -1 \n\t" + "dmtc1 %[tmp], %[mask3] \n\t" + "dsrl %[tmp], 0x10 \n\t" + "mtc1 %[tmp], %[mask2] \n\t" + "dsrl %[tmp], 0x10 \n\t" + "mtc1 %[tmp], %[mask1] \n\t" + "ori %[tmp], $0, 0x50 \n\t" + "mtc1 %[tmp], %[shuf_50] \n\t" + "ori %[tmp], $0, 0xfa \n\t" + "mtc1 %[tmp], %[shuf_fa] \n\t" + ".set pop \n\t" + :[zero]"=f"(zero), [mask1]"=f"(mask[1]), + [mask2]"=f"(mask[2]), [mask3]"=f"(mask[3]), + [shuf_50]"=f"(shuf_50), [shuf_fa]"=f"(shuf_fa), + [tmp]"=&r"(tmp) + ); + + // Output one pixel each iteration, calculating all channels (RGBA) together. + for (int out_x = 0; out_x < num_values; out_x++) { + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + double accumh, accuml; + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const void *row_to_filter = + reinterpret_cast<const void*>(&src_data[filter_offset << 2]); + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(accum, accum, accum) + ".set pop \n\t" + :[accumh]"=f"(accumh), [accuml]"=f"(accuml) + ); + + // We will load and accumulate with four coefficients per iteration. + for (int filter_x = 0; filter_x < filter_length >> 2; filter_x++) { + double src16h, src16l, mul_hih, mul_hil, mul_loh, mul_lol; + double coeffh, coeffl, src8h, src8l, th, tl, coeff16h, coeff16l; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // Load 4 coefficients => duplicate 1st and 2nd of them for all channels. + // [16] xx xx xx xx c3 c2 c1 c0 + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" + "xor %[coeffh], %[coeffh], %[coeffh] \n\t" + // [16] xx xx xx xx c1 c1 c0 c0 + _mm_pshuflh(coeff16, coeff, shuf_50) + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + _mm_punpcklhw(coeff16, 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 + "gsldlc1 %[src8h], 0xf(%[rtf]) \n\t" + "gsldrc1 %[src8h], 0x8(%[rtf]) \n\t" + "gsldlc1 %[src8l], 0x7(%[rtf]) \n\t" + "gsldrc1 %[src8l], 0x0(%[rtf]) \n\t" + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + _mm_punpcklbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + // [32] a0*c0 b0*c0 g0*c0 r0*c0 + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum, accum, t) + // [32] a1*c1 b1*c1 g1*c1 r1*c1 + _mm_punpckhhw(t, mul_lo, mul_hi) + _mm_paddw(accum, 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 + _mm_pshuflh(coeff16, coeff, shuf_fa) + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + _mm_punpcklhw(coeff16, coeff16, coeff16) + // [16] a3 g3 b3 r3 a2 g2 b2 r2 + _mm_punpckhbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + // [32] a2*c2 b2*c2 g2*c2 r2*c2 + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum, accum, t) + // [32] a3*c3 b3*c3 g3*c3 r3*c3 + _mm_punpckhhw(t, mul_lo, mul_hi) + _mm_paddw(accum, accum, t) + ".set pop \n\t" + :[th]"=&f"(th), [tl]"=&f"(tl), + [src8h]"=&f"(src8h), [src8l]"=&f"(src8l), + [accumh]"+f"(accumh), [accuml]"+f"(accuml), + [src16h]"=&f"(src16h), [src16l]"=&f"(src16l), + [coeffh]"=&f"(coeffh), [coeffl]"=&f"(coeffl), + [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l), + [mul_hih]"=&f"(mul_hih), [mul_hil]"=&f"(mul_hil), + [mul_loh]"=&f"(mul_loh), [mul_lol]"=&f"(mul_lol) + :[zeroh]"f"(zero), [zerol]"f"(zero), + [shuf_50]"f"(shuf_50), [shuf_fa]"f"(shuf_fa), + [fval]"r"(filter_values), [rtf]"r"(row_to_filter) + ); + + // Advance the pixel and coefficients pointers. + row_to_filter += 16; + filter_values += 4; + } + + // When |filter_length| is not divisible by 4, we need to decimate some of + // the filter coefficient that was loaded incorrectly to zero; Other than + // that the algorithm is same with above, except that the 4th pixel will be + // always absent. + int r = filter_length & 3; + if (r) { + double coeffh, coeffl, th, tl, coeff16h, coeff16l; + double src8h, src8l, src16h, src16l, mul_hih, mul_hil, mul_loh, mul_lol; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" + "xor %[coeffh], %[coeffh], %[coeffh] \n\t" + // Mask out extra filter taps. + "and %[coeffl], %[coeffl], %[mask] \n\t" + _mm_pshuflh(coeff16, coeff, shuf_50) + _mm_punpcklhw(coeff16, coeff16, coeff16) + "gsldlc1 %[src8h], 0xf(%[rtf]) \n\t" + "gsldrc1 %[src8h], 0x8(%[rtf]) \n\t" + "gsldlc1 %[src8l], 0x7(%[rtf]) \n\t" + "gsldrc1 %[src8l], 0x0(%[rtf]) \n\t" + _mm_punpcklbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum, accum, t) + _mm_punpckhhw(t, mul_lo, mul_hi) + _mm_paddw(accum, accum, t) + _mm_punpckhbh(src16, src8, zero) + _mm_pshuflh(coeff16, coeff, shuf_fa) + _mm_punpcklhw(coeff16, coeff16, coeff16) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum, accum, t) + ".set pop \n\t" + :[th]"=&f"(th), [tl]"=&f"(tl), + [src8h]"=&f"(src8h), [src8l]"=&f"(src8l), + [accumh]"+f"(accumh), [accuml]"+f"(accuml), + [src16h]"=&f"(src16h), [src16l]"=&f"(src16l), + [coeffh]"=&f"(coeffh), [coeffl]"=&f"(coeffl), + [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l), + [mul_hih]"=&f"(mul_hih), [mul_hil]"=&f"(mul_hil), + [mul_loh]"=&f"(mul_loh), [mul_lol]"=&f"(mul_lol) + :[fval]"r"(filter_values), [rtf]"r"(row_to_filter), + [zeroh]"f"(zero), [zerol]"f"(zero), [mask]"f"(mask[r]), + [shuf_50]"f"(shuf_50), [shuf_fa]"f"(shuf_fa) + ); + } + + double t, sra; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "ori %[tmp], $0, %[sk_sra] \n\t" + "mtc1 %[tmp], %[sra] \n\t" + // Shift right for fixed point implementation. + _mm_psraw(accum, accum, sra) + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + _mm_packsswh(accum, accum, zero, t) + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + _mm_packushb(accum, accum, zero, t) + // Store the pixel value of 32 bits. + "swc1 %[accuml], (%[out_row]) \n\t" + ".set pop \n\t" + :[sra]"=&f"(sra), [t]"=&f"(t), [tmp]"=&r"(tmp), + [accumh]"+f"(accumh), [accuml]"+f"(accuml) + :[sk_sra]"i"(ConvolutionFilter1D::kShiftBits), + [out_row]"r"(out_row), [zeroh]"f"(zero), [zerol]"f"(zero) + :"memory" + ); + + out_row += 4; + } +} + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +// Process one pixel at a time. +void ConvolveHorizontally1_LS3(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row) { + int num_values = filter.num_values(); + double zero; + double sra; + + asm volatile ( + ".set push \n" + ".set arch=loongson3a \n" + "xor %[zero], %[zero], %[zero] \n" + "mtc1 %[sk_sra], %[sra] \n" + ".set pop \n" + :[zero]"=&f"(zero), [sra]"=&f"(sra) + :[sk_sra]"r"(ConvolutionFilter1D::kShiftBits) + ); + // Loop over each pixel on this row in the output image. + for (int out_x = 0; out_x < num_values; out_x++) { + // Get the filter that determines the current output pixel. + int filter_offset; + int filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const unsigned char* row_to_filter = &src_data[filter_offset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + double accuml; + double accumh; + asm volatile ( + ".set push \n" + ".set arch=loongson3a \n" + "xor %[accuml], %[accuml], %[accuml] \n" + "xor %[accumh], %[accumh], %[accumh] \n" + ".set pop \n" + :[accuml]"=&f"(accuml), [accumh]"=&f"(accumh) + ); + for (int filter_x = 0; filter_x < filter_length; filter_x++) { + double src8; + double src16; + double coeff; + double coeff16; + asm volatile ( + ".set push \n" + ".set arch=loongson3a \n" + "lwc1 %[src8], %[rtf] \n" + "mtc1 %[fv], %[coeff] \n" + "pshufh %[coeff16], %[coeff], %[zero] \n" + "punpcklbh %[src16], %[src8], %[zero] \n" + "pmullh %[src8], %[src16], %[coeff16] \n" + "pmulhh %[coeff], %[src16], %[coeff16] \n" + "punpcklhw %[src16], %[src8], %[coeff] \n" + "punpckhhw %[coeff16], %[src8], %[coeff] \n" + "paddw %[accuml], %[accuml], %[src16] \n" + "paddw %[accumh], %[accumh], %[coeff16] \n" + ".set pop \n" + :[accuml]"+f"(accuml), [accumh]"+f"(accumh), + [src8]"=&f"(src8), [src16]"=&f"(src16), + [coeff]"=&f"(coeff), [coeff16]"=&f"(coeff16) + :[rtf]"m"(row_to_filter[filter_x * 4]), + [fv]"r"(filter_values[filter_x]), [zero]"f"(zero) + ); + } + + asm volatile ( + ".set push \n" + ".set arch=loongson3a \n" + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + "psraw %[accuml], %[accuml], %[sra] \n" + "psraw %[accumh], %[accumh], %[sra] \n" + // Store the new pixel. + "packsswh %[accuml], %[accuml], %[accumh] \n" + "packushb %[accuml], %[accuml], %[zero] \n" + "swc1 %[accuml], %[out_row] \n" + ".set pop \n" + :[accuml]"+f"(accuml), [accumh]"+f"(accumh) + :[sra]"f"(sra), [zero]"f"(zero), [out_row]"m"(out_row[out_x * 4]) + :"memory" + ); + } +} + +// Convolves horizontally along four rows. The row data is given in +// |src_data| and continues for the num_values() of the filter. +// The algorithm is almost same as |ConvolveHorizontally_LS3|. Please +// refer to that function for detailed comments. +void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[4]) { + int num_values = filter.num_values(); + int tmp, filter_offset, filter_length; + double zero, mask[4], shuf_50, shuf_fa; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "xor %[zero], %[zero], %[zero] \n\t" + // |mask| will be used to decimate all extra filter coefficients that are + // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. + "li %[tmp], 1 \n\t" + "dsll32 %[tmp], 0x10 \n\t" + "daddiu %[tmp], -1 \n\t" + "dmtc1 %[tmp], %[mask3] \n\t" + "dsrl %[tmp], 0x10 \n\t" + "mtc1 %[tmp], %[mask2] \n\t" + "dsrl %[tmp], 0x10 \n\t" + "mtc1 %[tmp], %[mask1] \n\t" + "ori %[tmp], $0, 0x50 \n\t" + "mtc1 %[tmp], %[shuf_50] \n\t" + "ori %[tmp], $0, 0xfa \n\t" + "mtc1 %[tmp], %[shuf_fa] \n\t" + ".set pop \n\t" + :[zero]"=f"(zero), [mask1]"=f"(mask[1]), + [mask2]"=f"(mask[2]), [mask3]"=f"(mask[3]), + [shuf_50]"=f"(shuf_50), [shuf_fa]"=f"(shuf_fa), + [tmp]"=&r"(tmp) + ); + + // Output one pixel each iteration, calculating all channels (RGBA) together. + for (int out_x = 0; out_x < num_values; out_x++) { + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + double accum0h, accum0l, accum1h, accum1l; + double accum2h, accum2l, accum3h, accum3l; + + // four pixels in a column per iteration. + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(accum0, accum0, accum0) + _mm_xor(accum1, accum1, accum1) + _mm_xor(accum2, accum2, accum2) + _mm_xor(accum3, accum3, accum3) + ".set pop \n\t" + :[accum0h]"=f"(accum0h), [accum0l]"=f"(accum0l), + [accum1h]"=f"(accum1h), [accum1l]"=f"(accum1l), + [accum2h]"=f"(accum2h), [accum2l]"=f"(accum2l), + [accum3h]"=f"(accum3h), [accum3l]"=f"(accum3l) + ); + + int start = (filter_offset<<2); + // We will load and accumulate with four coefficients per iteration. + for (int filter_x = 0; filter_x < (filter_length >> 2); filter_x++) { + double src8h, src8l, src16h, src16l; + double mul_hih, mul_hil, mul_loh, mul_lol, th, tl; + double coeffh, coeffl, coeff16loh, coeff16lol, coeff16hih, coeff16hil; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // [16] xx xx xx xx c3 c2 c1 c0 + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" + "xor %[coeffh], %[coeffh], %[coeffh] \n\t" + // [16] xx xx xx xx c1 c1 c0 c0 + _mm_pshuflh(coeff16lo, coeff, shuf_50) + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + _mm_punpcklhw(coeff16lo, coeff16lo, coeff16lo) + // [16] xx xx xx xx c3 c3 c2 c2 + _mm_pshuflh(coeff16hi, coeff, shuf_fa) + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + _mm_punpcklhw(coeff16hi, coeff16hi, coeff16hi) + ".set pop \n\t" + :[coeffh]"=&f"(coeffh), [coeffl]"=&f"(coeffl), + [coeff16loh]"=&f"(coeff16loh), [coeff16lol]"=&f"(coeff16lol), + [coeff16hih]"=&f"(coeff16hih), [coeff16hil]"=&f"(coeff16hil) + :[fval]"r"(filter_values), [shuf_50]"f"(shuf_50), [shuf_fa]"f"(shuf_fa) + ); + +#define ITERATION(_src, _accumh, _accuml) \ + asm volatile ( \ + ".set push \n\t" \ + ".set arch=loongson3a \n\t" \ + "gsldlc1 %[src8h], 0xf(%[src]) \n\t" \ + "gsldrc1 %[src8h], 0x8(%[src]) \n\t" \ + "gsldlc1 %[src8l], 0x7(%[src]) \n\t" \ + "gsldrc1 %[src8l], 0x0(%[src]) \n\t" \ + _mm_punpcklbh(src16, src8, zero) \ + _mm_pmulhh(mul_hi, src16, coeff16lo) \ + _mm_pmullh(mul_lo, src16, coeff16lo) \ + _mm_punpcklhw(t, mul_lo, mul_hi) \ + _mm_paddw(accum, accum, t) \ + _mm_punpckhhw(t, mul_lo, mul_hi) \ + _mm_paddw(accum, accum, t) \ + _mm_punpckhbh(src16, src8, zero) \ + _mm_pmulhh(mul_hi, src16, coeff16hi) \ + _mm_pmullh(mul_lo, src16, coeff16hi) \ + _mm_punpcklhw(t, mul_lo, mul_hi) \ + _mm_paddw(accum, accum, t) \ + _mm_punpckhhw(t, mul_lo, mul_hi) \ + _mm_paddw(accum, accum, t) \ + ".set pop \n\t" \ + :[th]"=&f"(th), [tl]"=&f"(tl), \ + [src8h]"=&f"(src8h), [src8l]"=&f"(src8l), \ + [src16h]"=&f"(src16h), [src16l]"=&f"(src16l), \ + [mul_hih]"=&f"(mul_hih), [mul_hil]"=&f"(mul_hil), \ + [mul_loh]"=&f"(mul_loh), [mul_lol]"=&f"(mul_lol), \ + [accumh]"+f"(_accumh), [accuml]"+f"(_accuml) \ + :[zeroh]"f"(zero), [zerol]"f"(zero), [src]"r"(_src), \ + [coeff16loh]"f"(coeff16loh), [coeff16lol]"f"(coeff16lol), \ + [coeff16hih]"f"(coeff16hih), [coeff16hil]"f"(coeff16hil) \ + ); + + ITERATION(src_data[0] + start, accum0h, accum0l); + ITERATION(src_data[1] + start, accum1h, accum1l); + ITERATION(src_data[2] + start, accum2h, accum2l); + ITERATION(src_data[3] + start, accum3h, accum3l); + + start += 16; + filter_values += 4; + } + + int r = filter_length & 3; + if (r) { + double src8h, src8l, src16h, src16l; + double mul_hih, mul_hil, mul_loh, mul_lol, th, tl; + double coeffh, coeffl, coeff16loh, coeff16lol, coeff16hih, coeff16hil; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" + "xor %[coeffh], %[coeffh], %[coeffh] \n\t" + // Mask out extra filter taps. + "and %[coeffl], %[coeffl], %[mask] \n\t" + _mm_pshuflh(coeff16lo, coeff, shuf_50) + /* c1 c1 c1 c1 c0 c0 c0 c0 */ + _mm_punpcklhw(coeff16lo, coeff16lo, coeff16lo) + _mm_pshuflh(coeff16hi, coeff, shuf_fa) + _mm_punpcklhw(coeff16hi, coeff16hi, coeff16hi) + ".set pop \n\t" + :[coeffh]"=&f"(coeffh), [coeffl]"=&f"(coeffl), + [coeff16loh]"=&f"(coeff16loh), [coeff16lol]"=&f"(coeff16lol), + [coeff16hih]"=&f"(coeff16hih), [coeff16hil]"=&f"(coeff16hil) + :[fval]"r"(filter_values), [mask]"f"(mask[r]), + [shuf_50]"f"(shuf_50), [shuf_fa]"f"(shuf_fa) + ); + + ITERATION(src_data[0] + start, accum0h, accum0l); + ITERATION(src_data[1] + start, accum1h, accum1l); + ITERATION(src_data[2] + start, accum2h, accum2l); + ITERATION(src_data[3] + start, accum3h, accum3l); + } + + double t, sra; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "ori %[tmp], $0, %[sk_sra] \n\t" + "mtc1 %[tmp], %[sra] \n\t" + _mm_psraw(accum0, accum0, sra) + _mm_packsswh(accum0, accum0, zero, t) + _mm_packushb(accum0, accum0, zero, t) + _mm_psraw(accum1, accum1, sra) + _mm_packsswh(accum1, accum1, zero, t) + _mm_packushb(accum1, accum1, zero, t) + _mm_psraw(accum2, accum2, sra) + _mm_packsswh(accum2, accum2, zero, t) + _mm_packushb(accum2, accum2, zero, t) + _mm_psraw(accum3, accum3, sra) + _mm_packsswh(accum3, accum3, zero, t) + _mm_packushb(accum3, accum3, zero, t) + "swc1 %[accum0l], (%[out_row0]) \n\t" + "swc1 %[accum1l], (%[out_row1]) \n\t" + "swc1 %[accum2l], (%[out_row2]) \n\t" + "swc1 %[accum3l], (%[out_row3]) \n\t" + ".set pop \n\t" + :[accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), + [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), + [accum3h]"+f"(accum3h), [accum3l]"+f"(accum3l), + [sra]"=&f"(sra), [t]"=&f"(t), [tmp]"=&r"(tmp) + :[zeroh]"f"(zero), [zerol]"f"(zero), + [out_row0]"r"(out_row[0]), [out_row1]"r"(out_row[1]), + [out_row2]"r"(out_row[2]), [out_row3]"r"(out_row[3]), + [sk_sra]"i"(ConvolutionFilter1D::kShiftBits) + :"memory" + ); + + out_row[0] += 4; + out_row[1] += 4; + out_row[2] += 4; + out_row[3] += 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 |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +template<bool has_alpha> +void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row) { + uint64_t tmp; + int width = pixel_width & ~3; + double zero, sra, coeff16h, coeff16l; + double accum0h, accum0l, accum1h, accum1l; + double accum2h, accum2l, accum3h, accum3l; + const void *src; + int out_x; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "xor %[zero], %[zero], %[zero] \n\t" + "ori %[tmp], $0, %[sk_sra] \n\t" + "mtc1 %[tmp], %[sra] \n\t" + ".set pop \n\t" + :[zero]"=f"(zero), [sra]"=f"(sra), [tmp]"=&r"(tmp) + :[sk_sra]"i"(ConvolutionFilter1D::kShiftBits) + ); + + // Output four pixels per iteration (16 bytes). + for (out_x = 0; out_x < width; out_x += 4) { + // Accumulated result for each pixel. 32 bits per RGBA channel. + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(accum0, accum0, accum0) + _mm_xor(accum1, accum1, accum1) + _mm_xor(accum2, accum2, accum2) + _mm_xor(accum3, accum3, accum3) + ".set pop \n\t" + :[accum0h]"=f"(accum0h), [accum0l]"=f"(accum0l), + [accum1h]"=f"(accum1h), [accum1l]"=f"(accum1l), + [accum2h]"=f"(accum2h), [accum2l]"=f"(accum2l), + [accum3h]"=f"(accum3h), [accum3l]"=f"(accum3l) + ); + + // Convolve with one filter coefficient per iteration. + for (int filter_y = 0; filter_y < filter_length; filter_y++) { + double src8h, src8l, src16h, src16l; + double mul_hih, mul_hil, mul_loh, mul_lol, th, tl; + + src = reinterpret_cast<const void*>( + &source_data_rows[filter_y][out_x << 2]); + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // Duplicate the filter coefficient 8 times. + // [16] cj cj cj cj cj cj cj cj + "gsldlc1 %[coeff16l], 7+%[fval] \n\t" + "gsldrc1 %[coeff16l], %[fval] \n\t" + "pshufh %[coeff16l], %[coeff16l], %[zerol] \n\t" + "mov.d %[coeff16h], %[coeff16l] \n\t" + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + "gsldlc1 %[src8h], 0xf(%[src]) \n\t" + "gsldrc1 %[src8h], 0x8(%[src]) \n\t" + "gsldlc1 %[src8l], 0x7(%[src]) \n\t" + "gsldrc1 %[src8l], 0x0(%[src]) \n\t" + // 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 + _mm_punpcklbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + // [32] a0 b0 g0 r0 + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum0, accum0, t) + // [32] a1 b1 g1 r1 + _mm_punpckhhw(t, mul_lo, mul_hi) + _mm_paddw(accum1, 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 + _mm_punpckhbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + ".set pop \n\t" + :[th]"=&f"(th), [tl]"=&f"(tl), + [src8h]"=&f"(src8h), [src8l]"=&f"(src8l), + [src16h]"=&f"(src16h), [src16l]"=&f"(src16l), + [mul_hih]"=&f"(mul_hih), [mul_hil]"=&f"(mul_hil), + [mul_loh]"=&f"(mul_loh), [mul_lol]"=&f"(mul_lol), + [accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), + [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l) + :[zeroh]"f"(zero), [zerol]"f"(zero), + [fval]"m"(filter_values[filter_y]), + [src]"r"(src) + ); + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // [32] a2 b2 g2 r2 + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum2, accum2, t) + // [32] a3 b3 g3 r3 + _mm_punpckhhw(t, mul_lo, mul_hi) + _mm_paddw(accum3, accum3, t) + ".set pop \n\t" + :[th]"=&f"(th), [tl]"=&f"(tl), + [mul_hih]"+f"(mul_hih), [mul_hil]"+f"(mul_hil), + [mul_loh]"+f"(mul_loh), [mul_lol]"+f"(mul_lol), + [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), + [accum3h]"+f"(accum3h), [accum3l]"+f"(accum3l) + ); + } + + double t; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // Shift right for fixed point implementation. + _mm_psraw(accum0, accum0, sra) + _mm_psraw(accum1, accum1, sra) + _mm_psraw(accum2, accum2, sra) + _mm_psraw(accum3, accum3, sra) + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + _mm_packsswh(accum0, accum0, accum1, t) + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + _mm_packsswh(accum2, accum2, accum3, t) + // 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 + _mm_packushb(accum0, accum0, accum2, t) + ".set pop \n\t" + :[accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), + [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), + [accum3h]"+f"(accum3h), [accum3l]"+f"(accum3l), + [t]"=&f"(t) + :[sra]"f"(sra) + ); + + if (has_alpha) { + double ah, al, bh, bl, srl8, srl16, sll24; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 8 \n\t" + "mtc1 %[tmp], %[srl8] \n\t" + "li %[tmp], 16 \n\t" + "mtc1 %[tmp], %[srl16] \n\t" + "li %[tmp], 24 \n\t" + "mtc1 %[tmp], %[sll24] \n\t" + // 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 + _mm_psraw(a, accum0, srl8) + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + _mm_pmaxub(b, a, accum0) // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + _mm_psrlw(a, accum0, srl16) + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + _mm_pmaxub(b, 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 + _mm_psllw(b, b, sll24) + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + _mm_pmaxub(accum0, b, accum0) + ".set pop \n\t" + :[accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [tmp]"=&r"(tmp), [ah]"=&f"(ah), [al]"=&f"(al), + [bh]"=&f"(bh), [bl]"=&f"(bl), [srl8]"=&f"(srl8), + [srl16]"=&f"(srl16), [sll24]"=&f"(sll24) + ); + } else { + double maskh, maskl; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // Set value of alpha channels to 0xFF. + "li %[tmp], 0xff000000 \n\t" + "mtc1 %[tmp], %[maskl] \n\t" + "punpcklwd %[maskl], %[maskl], %[maskl] \n\t" + "mov.d %[maskh], %[maskl] \n\t" + _mm_or(accum0, accum0, mask) + ".set pop \n\t" + :[maskh]"=&f"(maskh), [maskl]"=&f"(maskl), + [accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [tmp]"=&r"(tmp) + ); + } + + // Store the convolution result (16 bytes) and advance the pixel pointers. + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gssdlc1 %[accum0h], 0xf(%[out_row]) \n\t" + "gssdrc1 %[accum0h], 0x8(%[out_row]) \n\t" + "gssdlc1 %[accum0l], 0x7(%[out_row]) \n\t" + "gssdrc1 %[accum0l], 0x0(%[out_row]) \n\t" + ".set pop \n\t" + ::[accum0h]"f"(accum0h), [accum0l]"f"(accum0l), + [out_row]"r"(out_row) + :"memory" + ); + out_row += 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. + if (pixel_width & 3) { + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(accum0, accum0, accum0) + _mm_xor(accum1, accum1, accum1) + _mm_xor(accum2, accum2, accum2) + ".set pop \n\t" + :[accum0h]"=&f"(accum0h), [accum0l]"=&f"(accum0l), + [accum1h]"=&f"(accum1h), [accum1l]"=&f"(accum1l), + [accum2h]"=&f"(accum2h), [accum2l]"=&f"(accum2l) + ); + for (int filter_y = 0; filter_y < filter_length; ++filter_y) { + double src8h, src8l, src16h, src16l; + double th, tl, mul_hih, mul_hil, mul_loh, mul_lol; + src = reinterpret_cast<const void*>( + &source_data_rows[filter_y][out_x<<2]); + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gsldlc1 %[coeff16l], 7+%[fval] \n\t" + "gsldrc1 %[coeff16l], %[fval] \n\t" + "pshufh %[coeff16l], %[coeff16l], %[zerol] \n\t" + "mov.d %[coeff16h], %[coeff16l] \n\t" + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + "gsldlc1 %[src8h], 0xf(%[src]) \n\t" + "gsldrc1 %[src8h], 0x8(%[src]) \n\t" + "gsldlc1 %[src8l], 0x7(%[src]) \n\t" + "gsldrc1 %[src8l], 0x0(%[src]) \n\t" + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + _mm_punpcklbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + // [32] a0 b0 g0 r0 + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum0, accum0, t) + // [32] a1 b1 g1 r1 + _mm_punpckhhw(t, mul_lo, mul_hi) + _mm_paddw(accum1, accum1, t) + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + _mm_punpckhbh(src16, src8, zero) + _mm_pmulhh(mul_hi, src16, coeff16) + _mm_pmullh(mul_lo, src16, coeff16) + // [32] a2 b2 g2 r2 + _mm_punpcklhw(t, mul_lo, mul_hi) + _mm_paddw(accum2, accum2, t) + ".set pop \n\t" + :[th]"=&f"(th), [tl]"=&f"(tl), + [src8h]"=&f"(src8h), [src8l]"=&f"(src8l), + [src16h]"=&f"(src16h), [src16l]"=&f"(src16l), + [mul_hih]"=&f"(mul_hih), [mul_hil]"=&f"(mul_hil), + [mul_loh]"=&f"(mul_loh), [mul_lol]"=&f"(mul_lol), + [accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), + [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), + [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l) + :[zeroh]"f"(zero), [zerol]"f"(zero), + [fval]"m"(filter_values[filter_y]), + [src]"r"(src) + ); + } + + double t; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_psraw(accum0, accum0, sra) + _mm_psraw(accum1, accum1, sra) + _mm_psraw(accum2, accum2, sra) + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + _mm_packsswh(accum0, accum0, accum1, t) + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + _mm_packsswh(accum2, accum2, zero, t) + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + _mm_packushb(accum0, accum0, accum2, t) + ".set pop \n\t" + :[accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), + [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), + [t]"=&f"(t) + :[zeroh]"f"(zero), [zerol]"f"(zero), [sra]"f"(sra) + ); + if (has_alpha) { + double ah, al, bh, bl, srl8, srl16, sll24; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 8 \n\t" + "mtc1 %[tmp], %[srl8] \n\t" + "li %[tmp], 16 \n\t" + "mtc1 %[tmp], %[srl16] \n\t" + "li %[tmp], 24 \n\t" + "mtc1 %[tmp], %[sll24] \n\t" + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + _mm_psrlw(a, accum0, srl8) + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + _mm_pmaxub(b, a, accum0) // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + _mm_psrlw(a, accum0, srl16) + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + _mm_pmaxub(b, 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 + _mm_psllw(b, b, sll24) + _mm_pmaxub(accum0, b, accum0) + ".set pop \n\t" + :[ah]"=&f"(ah), [al]"=&f"(al), [bh]"=&f"(bh), [bl]"=&f"(bl), + [accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), [tmp]"=&r"(tmp), + [srl8]"=&f"(srl8), [srl16]"=&f"(srl16), [sll24]"=&f"(sll24) + ); + } else { + double maskh, maskl; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + // Set value of alpha channels to 0xFF. + "li %[tmp], 0xff000000 \n\t" + "mtc1 %[tmp], %[maskl] \n\t" + "punpcklwd %[maskl], %[maskl], %[maskl] \n\t" + "mov.d %[maskh], %[maskl] \n\t" + _mm_or(accum0, accum0, mask) + ".set pop \n\t" + :[maskh]"=&f"(maskh), [maskl]"=&f"(maskl), + [accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l), + [tmp]"=&r"(tmp) + ); + } + + double s4, s64; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 4 \n\t" + "mtc1 %[tmp], %[s4] \n\t" + "li %[tmp], 64 \n\t" + "mtc1 %[tmp], %[s64] \n\t" + ".set pop \n\t" + :[s4]"=f"(s4), [s64]"=f"(s64), + [tmp]"=&r"(tmp) + ); + for (int out_x = width; out_x < pixel_width; out_x++) { + double t; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "swc1 %[accum0l], (%[out_row]) \n\t" + _mm_psrlq(accum0, accum0, s4, s64, t) + ".set pop \n\t" + :[t]"=&f"(t), + [accum0h]"+f"(accum0h), [accum0l]"+f"(accum0l) + :[out_row]"r"(out_row), [s4]"f"(s4), [s64]"f"(s64) + :"memory" + ); + out_row += 4; + } + } +} + +void ConvolveVertically_LS3(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, bool has_alpha) { + if (has_alpha) { + ConvolveVertically_LS3_impl<true>(filter_values, filter_length, + source_data_rows, pixel_width, out_row); + } else { + ConvolveVertically_LS3_impl<false>(filter_values, filter_length, + source_data_rows, pixel_width, out_row); + } +} + +} // namespace skia + +#endif /* _MIPS_ARCH_LOONGSON3A */ diff --git a/gfx/2d/convolverLS3.h b/gfx/2d/convolverLS3.h new file mode 100644 index 000000000..af531c927 --- /dev/null +++ b/gfx/2d/convolverLS3.h @@ -0,0 +1,75 @@ +// Copyright (c) 2014-2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#ifndef SKIA_EXT_CONVOLVER_LS3_H_ +#define SKIA_EXT_CONVOLVER_LS3_H_ + +#include "convolver.h" + +#include <algorithm> + +#include "skia/include/core/SkTypes.h" + +namespace skia { + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +void ConvolveHorizontally_LS3(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row); + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +// Process one pixel at a time. +void ConvolveHorizontally1_LS3(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row); + +// Convolves horizontally along four rows. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +// The algorithm is almost same as |ConvolveHorizontally_LS3|. Please +// refer to that function for detailed comments. +void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[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 |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +void ConvolveVertically_LS3(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, bool has_alpha); + +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_LS3_H_ diff --git a/gfx/2d/convolverSSE2.cpp b/gfx/2d/convolverSSE2.cpp new file mode 100644 index 000000000..20ddd45f1 --- /dev/null +++ b/gfx/2d/convolverSSE2.cpp @@ -0,0 +1,471 @@ +// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#include "convolver.h" +#include <algorithm> +#include "skia/include/core/SkTypes.h" + +#include <emmintrin.h> // ARCH_CPU_X86_FAMILY was defined in build/config.h + +namespace skia { + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the num_values() of the filter. +void ConvolveHorizontally_SSE2(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row) { + int num_values = filter.num_values(); + + int filter_offset, filter_length; + __m128i zero = _mm_setzero_si128(); + __m128i mask[4]; + // |mask| will be used to decimate all extra filter coefficients that are + // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. + mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); + mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); + mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); + + // Output one pixel each iteration, calculating all channels (RGBA) together. + for (int out_x = 0; out_x < num_values; out_x++) { + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + __m128i accum = _mm_setzero_si128(); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const __m128i* row_to_filter = + reinterpret_cast<const __m128i*>(&src_data[filter_offset << 2]); + + // We will load and accumulate with four coefficients per iteration. + for (int filter_x = 0; filter_x < filter_length >> 2; filter_x++) { + + // 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*>(filter_values)); + // [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(row_to_filter); + // [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. + row_to_filter += 1; + filter_values += 4; + } + + // When |filter_length| is not divisible by 4, we need to decimate some of + // the filter coefficient that was loaded incorrectly to zero; Other than + // that the algorithm is same with above, exceot that the 4th pixel will be + // always absent. + int r = filter_length&3; + if (r) { + // Note: filter_values must be padded to align_up(filter_offset, 8). + __m128i coeff, coeff16; + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // Mask out extra filter taps. + coeff = _mm_and_si128(coeff, mask[r]); + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + + // Note: line buffer must be padded to align_up(filter_offset, 16). + // We resolve this by use C-version for the last horizontal line. + __m128i src8 = _mm_loadu_si128(row_to_filter); + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + src16 = _mm_unpackhi_epi8(src8, zero); + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + } + + // Shift right for fixed point implementation. + accum = _mm_srai_epi32(accum, ConvolutionFilter1D::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*>(out_row)) = _mm_cvtsi128_si32(accum); + out_row += 4; + } +} + +// Convolves horizontally along four rows. The row data is given in +// |src_data| and continues for the num_values() of the filter. +// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please +// refer to that function for detailed comments. +void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[4]) { + int num_values = filter.num_values(); + + int filter_offset, filter_length; + __m128i zero = _mm_setzero_si128(); + __m128i mask[4]; + // |mask| will be used to decimate all extra filter coefficients that are + // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. + mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); + mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); + mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); + + // Output one pixel each iteration, calculating all channels (RGBA) together. + for (int out_x = 0; out_x < num_values; out_x++) { + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + // four pixels in a column per iteration. + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + __m128i accum3 = _mm_setzero_si128(); + int start = (filter_offset<<2); + // We will load and accumulate with four coefficients per iteration. + for (int filter_x = 0; filter_x < (filter_length >> 2); filter_x++) { + __m128i coeff, coeff16lo, coeff16hi; + // [16] xx xx xx xx c3 c2 c1 c0 + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // [16] xx xx xx xx c1 c1 c0 c0 + coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo); + // [16] xx xx xx xx c3 c3 c2 c2 + coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi); + + __m128i src8, src16, mul_hi, mul_lo, t; + +#define ITERATION(src, accum) \ + src8 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src)); \ + src16 = _mm_unpacklo_epi8(src8, zero); \ + mul_hi = _mm_mulhi_epi16(src16, coeff16lo); \ + mul_lo = _mm_mullo_epi16(src16, coeff16lo); \ + t = _mm_unpacklo_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + t = _mm_unpackhi_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + src16 = _mm_unpackhi_epi8(src8, zero); \ + mul_hi = _mm_mulhi_epi16(src16, coeff16hi); \ + mul_lo = _mm_mullo_epi16(src16, coeff16hi); \ + t = _mm_unpacklo_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + t = _mm_unpackhi_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t) + + ITERATION(src_data[0] + start, accum0); + ITERATION(src_data[1] + start, accum1); + ITERATION(src_data[2] + start, accum2); + ITERATION(src_data[3] + start, accum3); + + start += 16; + filter_values += 4; + } + + int r = filter_length & 3; + if (r) { + // Note: filter_values must be padded to align_up(filter_offset, 8); + __m128i coeff; + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // Mask out extra filter taps. + coeff = _mm_and_si128(coeff, mask[r]); + + __m128i coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + /* c1 c1 c1 c1 c0 c0 c0 c0 */ + coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo); + __m128i coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi); + + __m128i src8, src16, mul_hi, mul_lo, t; + + ITERATION(src_data[0] + start, accum0); + ITERATION(src_data[1] + start, accum1); + ITERATION(src_data[2] + start, accum2); + ITERATION(src_data[3] + start, accum3); + } + + accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits); + accum0 = _mm_packs_epi32(accum0, zero); + accum0 = _mm_packus_epi16(accum0, zero); + accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits); + accum1 = _mm_packs_epi32(accum1, zero); + accum1 = _mm_packus_epi16(accum1, zero); + accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits); + accum2 = _mm_packs_epi32(accum2, zero); + accum2 = _mm_packus_epi16(accum2, zero); + accum3 = _mm_srai_epi32(accum3, ConvolutionFilter1D::kShiftBits); + accum3 = _mm_packs_epi32(accum3, zero); + accum3 = _mm_packus_epi16(accum3, zero); + + *(reinterpret_cast<int*>(out_row[0])) = _mm_cvtsi128_si32(accum0); + *(reinterpret_cast<int*>(out_row[1])) = _mm_cvtsi128_si32(accum1); + *(reinterpret_cast<int*>(out_row[2])) = _mm_cvtsi128_si32(accum2); + *(reinterpret_cast<int*>(out_row[3])) = _mm_cvtsi128_si32(accum3); + + out_row[0] += 4; + out_row[1] += 4; + out_row[2] += 4; + out_row[3] += 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 |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +template<bool has_alpha> +void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row) { + int width = pixel_width & ~3; + + __m128i zero = _mm_setzero_si128(); + __m128i accum0, accum1, accum2, accum3, coeff16; + const __m128i* src; + // Output four pixels per iteration (16 bytes). + for (int out_x = 0; out_x < width; out_x += 4) { + + // Accumulated result for each pixel. 32 bits per RGBA channel. + accum0 = _mm_setzero_si128(); + accum1 = _mm_setzero_si128(); + accum2 = _mm_setzero_si128(); + accum3 = _mm_setzero_si128(); + + // Convolve with one filter coefficient per iteration. + for (int filter_y = 0; filter_y < filter_length; filter_y++) { + + // Duplicate the filter coefficient 8 times. + // [16] cj cj cj cj cj cj cj cj + coeff16 = _mm_set1_epi16(filter_values[filter_y]); + + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + src = reinterpret_cast<const __m128i*>( + &source_data_rows[filter_y][out_x << 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, ConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits); + accum3 = _mm_srai_epi32(accum3, ConvolutionFilter1D::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 (has_alpha) { + // 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*>(out_row), accum0); + out_row += 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. + if (pixel_width & 3) { + accum0 = _mm_setzero_si128(); + accum1 = _mm_setzero_si128(); + accum2 = _mm_setzero_si128(); + for (int filter_y = 0; filter_y < filter_length; ++filter_y) { + coeff16 = _mm_set1_epi16(filter_values[filter_y]); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + src = reinterpret_cast<const __m128i*>( + &source_data_rows[filter_y][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, ConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::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 (has_alpha) { + // [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 out_x = width; out_x < pixel_width; out_x++) { + *(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum0); + accum0 = _mm_srli_si128(accum0, 4); + out_row += 4; + } + } +} + +void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, bool has_alpha) { + if (has_alpha) { + ConvolveVertically_SSE2_impl<true>(filter_values, filter_length, + source_data_rows, pixel_width, out_row); + } else { + ConvolveVertically_SSE2_impl<false>(filter_values, filter_length, + source_data_rows, pixel_width, out_row); + } +} + +} // namespace skia diff --git a/gfx/2d/convolverSSE2.h b/gfx/2d/convolverSSE2.h new file mode 100644 index 000000000..a54ce676b --- /dev/null +++ b/gfx/2d/convolverSSE2.h @@ -0,0 +1,68 @@ +// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#ifndef SKIA_EXT_CONVOLVER_SSE_H_ +#define SKIA_EXT_CONVOLVER_SSE_H_ + +#include "convolver.h" + +#include <algorithm> + +#include "skia/include/core/SkTypes.h" + +namespace skia { + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +void ConvolveHorizontally_SSE2(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row); + +// Convolves horizontally along four rows. The row data is given in +// |src_data| and continues for the [begin, end) of the filter. +// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please +// refer to that function for detailed comments. +void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[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 |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, bool has_alpha); + +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_SSE_H_ diff --git a/gfx/2d/genshaders.sh b/gfx/2d/genshaders.sh new file mode 100644 index 000000000..74b1a3d7b --- /dev/null +++ b/gfx/2d/genshaders.sh @@ -0,0 +1,10 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 +rm tmpfile diff --git a/gfx/2d/gfx2d.sln b/gfx/2d/gfx2d.sln new file mode 100644 index 000000000..40a137a1c --- /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 000000000..8b4607899 --- /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/image_operations.cpp b/gfx/2d/image_operations.cpp new file mode 100644 index 000000000..62215f007 --- /dev/null +++ b/gfx/2d/image_operations.cpp @@ -0,0 +1,390 @@ +// Copyright (c) 2006-2012 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#include "base/basictypes.h" + +#include <algorithm> +#include <cmath> +#include <limits> + +#include "image_operations.h" + +#include "base/stack_container.h" +#include "convolver.h" +#include "skia/include/core/SkColorPriv.h" +#include "skia/include/core/SkBitmap.h" +#include "skia/include/core/SkRect.h" +#include "skia/include/core/SkFontLCDConfig.h" + +namespace skia { + +namespace resize { + +// TODO(egouriou): Take advantage of periods in the convolution. +// Practical resizing filters are periodic outside of the border area. +// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the +// source become p pixels in the destination) will have a period of p. +// A nice consequence is a period of 1 when downscaling by an integral +// factor. Downscaling from typical display resolutions is also bound +// to produce interesting periods as those are chosen to have multiple +// small factors. +// Small periods reduce computational load and improve cache usage if +// the coefficients can be shared. For periods of 1 we can consider +// loading the factors only once outside the borders. +void ComputeFilters(ImageOperations::ResizeMethod method, + int src_size, int dst_size, + int dest_subset_lo, int dest_subset_size, + ConvolutionFilter1D* output) { + // method_ will only ever refer to an "algorithm method". + SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) && + (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD)); + + float scale = static_cast<float>(dst_size) / static_cast<float>(src_size); + + int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi) + + // 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 clamped_scale = std::min(1.0f, scale); + + float src_support = GetFilterSupport(method, clamped_scale) / clamped_scale; + + // Speed up the divisions below by turning them into multiplies. + float inv_scale = 1.0f / scale; + + StackVector<float, 64> filter_values; + StackVector<int16_t, 64> fixed_filter_values; + + // 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. + for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi; + dest_subset_i++) { + // Reset the arrays. We don't declare them inside so they can re-use the + // same malloc-ed buffer. + filter_values->clear(); + fixed_filter_values->clear(); + + // 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 src_pixel = (static_cast<float>(dest_subset_i) + 0.5f) * inv_scale; + + // Compute the (inclusive) range of source pixels the filter covers. + int src_begin = std::max(0, FloorInt(src_pixel - src_support)); + int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support)); + + // Compute the unnormalized filter value at each location of the source + // it covers. + float filter_sum = 0.0f; // Sub of the filter values for normalizing. + for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end; + cur_filter_pixel++) { + // 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 'src_pixel'. 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). + float src_filter_dist = + ((static_cast<float>(cur_filter_pixel) + 0.5f) - src_pixel); + + // Since the filter really exists in dest space, map it there. + float dest_filter_dist = src_filter_dist * clamped_scale; + + // Compute the filter value at that location. + float filter_value = ComputeFilter(method, dest_filter_dist); + filter_values->push_back(filter_value); + + filter_sum += filter_value; + } + + // The filter must be normalized so that we don't affect the brightness of + // the image. Convert to normalized fixed point. + int16_t fixed_sum = 0; + for (size_t i = 0; i < filter_values->size(); i++) { + int16_t cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum); + fixed_sum += cur_fixed; + fixed_filter_values->push_back(cur_fixed); + } + + // 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). + int16_t leftovers = output->FloatToFixed(1.0f) - fixed_sum; + fixed_filter_values[fixed_filter_values->size() / 2] += leftovers; + + // Now it's ready to go. + output->AddFilter(src_begin, &fixed_filter_values[0], + static_cast<int>(fixed_filter_values->size())); + } + + output->PaddingForSIMD(8); +} + +} // namespace resize + +ImageOperations::ResizeMethod ResizeMethodToAlgorithmMethod( + ImageOperations::ResizeMethod method) { + // Convert any "Quality Method" into an "Algorithm Method" + if (method >= ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD && + method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD) { + return method; + } + // The call to ImageOperationsGtv::Resize() above took care of + // GPU-acceleration in the cases where it is possible. So now we just + // pick the appropriate software method for each resize quality. + switch (method) { + // Users of RESIZE_GOOD are willing to trade a lot of quality to + // get speed, allowing the use of linear resampling to get hardware + // acceleration (SRB). Hence any of our "good" software filters + // will be acceptable, and we use the fastest one, Hamming-1. + case ImageOperations::RESIZE_GOOD: + // Users of RESIZE_BETTER are willing to trade some quality in order + // to improve performance, but are guaranteed not to devolve to a linear + // resampling. In visual tests we see that Hamming-1 is not as good as + // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is + // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed + // an acceptable trade-off between quality and speed. + case ImageOperations::RESIZE_BETTER: + return ImageOperations::RESIZE_HAMMING1; + default: + return ImageOperations::RESIZE_LANCZOS3; + } +} + +// Resize ---------------------------------------------------------------------- + +// static +SkBitmap ImageOperations::Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + void* dest_pixels /* = nullptr */) { + if (method == ImageOperations::RESIZE_SUBPIXEL) + return ResizeSubpixel(source, dest_width, dest_height, dest_subset); + else + return ResizeBasic(source, method, dest_width, dest_height, dest_subset, + dest_pixels); +} + +// static +SkBitmap ImageOperations::ResizeSubpixel(const SkBitmap& source, + int dest_width, int dest_height, + const SkIRect& dest_subset) { + // Currently only works on Linux/BSD because these are the only platforms + // where SkFontLCDConfig::GetSubpixelOrder is defined. +#if defined(XP_UNIX) + // Understand the display. + const SkFontLCDConfig::LCDOrder order = SkFontLCDConfig::GetSubpixelOrder(); + const SkFontLCDConfig::LCDOrientation orientation = + SkFontLCDConfig::GetSubpixelOrientation(); + + // Decide on which dimension, if any, to deploy subpixel rendering. + int w = 1; + int h = 1; + switch (orientation) { + case SkFontLCDConfig::kHorizontal_LCDOrientation: + w = dest_width < source.width() ? 3 : 1; + break; + case SkFontLCDConfig::kVertical_LCDOrientation: + h = dest_height < source.height() ? 3 : 1; + break; + } + + // Resize the image. + const int width = dest_width * w; + const int height = dest_height * h; + SkIRect subset = { dest_subset.fLeft, dest_subset.fTop, + dest_subset.fLeft + dest_subset.width() * w, + dest_subset.fTop + dest_subset.height() * h }; + SkBitmap img = ResizeBasic(source, ImageOperations::RESIZE_LANCZOS3, width, + height, subset); + const int row_words = img.rowBytes() / 4; + if (w == 1 && h == 1) + return img; + + // Render into subpixels. + SkBitmap result; + SkImageInfo info = SkImageInfo::Make(dest_subset.width(), + dest_subset.height(), + kBGRA_8888_SkColorType, + kPremul_SkAlphaType); + + + result.allocPixels(info); + if (!result.readyToDraw()) + return img; + + SkAutoLockPixels locker(img); + if (!img.readyToDraw()) + return img; + + uint32_t* src_row = img.getAddr32(0, 0); + uint32_t* dst_row = result.getAddr32(0, 0); + for (int y = 0; y < dest_subset.height(); y++) { + uint32_t* src = src_row; + uint32_t* dst = dst_row; + for (int x = 0; x < dest_subset.width(); x++, src += w, dst++) { + uint8_t r = 0, g = 0, b = 0, a = 0; + switch (order) { + case SkFontLCDConfig::kRGB_LCDOrder: + switch (orientation) { + case SkFontLCDConfig::kHorizontal_LCDOrientation: + r = SkGetPackedR32(src[0]); + g = SkGetPackedG32(src[1]); + b = SkGetPackedB32(src[2]); + a = SkGetPackedA32(src[1]); + break; + case SkFontLCDConfig::kVertical_LCDOrientation: + r = SkGetPackedR32(src[0 * row_words]); + g = SkGetPackedG32(src[1 * row_words]); + b = SkGetPackedB32(src[2 * row_words]); + a = SkGetPackedA32(src[1 * row_words]); + break; + } + break; + case SkFontLCDConfig::kBGR_LCDOrder: + switch (orientation) { + case SkFontLCDConfig::kHorizontal_LCDOrientation: + b = SkGetPackedB32(src[0]); + g = SkGetPackedG32(src[1]); + r = SkGetPackedR32(src[2]); + a = SkGetPackedA32(src[1]); + break; + case SkFontLCDConfig::kVertical_LCDOrientation: + b = SkGetPackedB32(src[0 * row_words]); + g = SkGetPackedG32(src[1 * row_words]); + r = SkGetPackedR32(src[2 * row_words]); + a = SkGetPackedA32(src[1 * row_words]); + break; + } + break; + case SkFontLCDConfig::kNONE_LCDOrder: + break; + } + // Premultiplied alpha is very fragile. + a = a > r ? a : r; + a = a > g ? a : g; + a = a > b ? a : b; + *dst = SkPackARGB32(a, r, g, b); + } + src_row += h * row_words; + dst_row += result.rowBytes() / 4; + } + result.setAlphaType(img.alphaType()); + return result; +#else + return SkBitmap(); +#endif // OS_POSIX && !OS_MACOSX && !defined(OS_ANDROID) +} + +// static +SkBitmap ImageOperations::ResizeBasic(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + void* dest_pixels /* = nullptr */) { + // Ensure that the ResizeMethod enumeration is sound. + SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) && + (method <= RESIZE_LAST_QUALITY_METHOD)) || + ((RESIZE_FIRST_ALGORITHM_METHOD <= method) && + (method <= RESIZE_LAST_ALGORITHM_METHOD))); + + // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just + // return empty. + if (source.width() < 1 || source.height() < 1 || + dest_width < 1 || dest_height < 1) + return SkBitmap(); + + method = ResizeMethodToAlgorithmMethod(method); + // Check that we deal with an "algorithm methods" from this point onward. + SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) && + (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD)); + + SkAutoLockPixels locker(source); + if (!source.readyToDraw()) + return SkBitmap(); + + ConvolutionFilter1D x_filter; + ConvolutionFilter1D y_filter; + + resize::ComputeFilters(method, source.width(), dest_width, dest_subset.fLeft, dest_subset.width(), &x_filter); + resize::ComputeFilters(method, source.height(), dest_height, dest_subset.fTop, dest_subset.height(), &y_filter); + + // Get a source bitmap encompassing this touched area. We construct the + // offsets and row strides such that it looks like a new bitmap, while + // referring to the old data. + const uint8_t* source_subset = + reinterpret_cast<const uint8_t*>(source.getPixels()); + + // Convolve into the result. + SkBitmap result; + SkImageInfo info = SkImageInfo::Make(dest_subset.width(), + dest_subset.height(), + kBGRA_8888_SkColorType, + kPremul_SkAlphaType); + + if (dest_pixels) { + result.installPixels(info, dest_pixels, info.minRowBytes()); + } else { + result.allocPixels(info); + } + + if (!result.readyToDraw()) + return SkBitmap(); + + BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()), + !source.isOpaque(), x_filter, y_filter, + static_cast<int>(result.rowBytes()), + static_cast<unsigned char*>(result.getPixels())); + + // Preserve the "opaque" flag for use as an optimization later. + result.setAlphaType(source.alphaType()); + + return result; +} + +// static +SkBitmap ImageOperations::Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + void* dest_pixels /* = nullptr */) { + SkIRect dest_subset = { 0, 0, dest_width, dest_height }; + return Resize(source, method, dest_width, dest_height, dest_subset, + dest_pixels); +} + +} // namespace skia diff --git a/gfx/2d/image_operations.h b/gfx/2d/image_operations.h new file mode 100644 index 000000000..8e3191363 --- /dev/null +++ b/gfx/2d/image_operations.h @@ -0,0 +1,285 @@ +// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + +#ifndef SKIA_EXT_IMAGE_OPERATIONS_H_ +#define SKIA_EXT_IMAGE_OPERATIONS_H_ + +#include "skia/include/core/SkTypes.h" +#include "Types.h" +#include "convolver.h" +#include "skia/include/core/SkRect.h" + +class SkBitmap; +struct SkIRect; + +namespace skia { + +class ImageOperations { + public: + enum ResizeMethod { + // + // Quality Methods + // + // Those enumeration values express a desired quality/speed tradeoff. + // They are translated into an algorithm-specific method that depends + // on the capabilities (CPU, GPU) of the underlying platform. + // It is possible for all three methods to be mapped to the same + // algorithm on a given platform. + + // Good quality resizing. Fastest resizing with acceptable visual quality. + // This is typically intended for use during interactive layouts + // where slower platforms may want to trade image quality for large + // increase in resizing performance. + // + // For example the resizing implementation may devolve to linear + // filtering if this enables GPU acceleration to be used. + // + // Note that the underlying resizing method may be determined + // on the fly based on the parameters for a given resize call. + // For example an implementation using a GPU-based linear filter + // in the common case may still use a higher-quality software-based + // filter in cases where using the GPU would actually be slower - due + // to too much latency - or impossible - due to image format or size + // constraints. + RESIZE_GOOD, + + // Medium quality resizing. Close to high quality resizing (better + // than linear interpolation) with potentially some quality being + // traded-off for additional speed compared to RESIZE_BEST. + // + // This is intended, for example, for generation of large thumbnails + // (hundreds of pixels in each dimension) from large sources, where + // a linear filter would produce too many artifacts but where + // a RESIZE_HIGH might be too costly time-wise. + RESIZE_BETTER, + + // High quality resizing. The algorithm is picked to favor image quality. + RESIZE_BEST, + + // + // Algorithm-specific enumerations + // + + // Box filter. This is a weighted average of all of the pixels touching + // the destination pixel. For enlargement, this is nearest neighbor. + // + // You probably don't want this, it is here for testing since it is easy to + // compute. Use RESIZE_LANCZOS3 instead. + RESIZE_BOX, + + // 1-cycle Hamming filter. This is tall is the middle and falls off towards + // the window edges but without going to 0. This is about 40% faster than + // a 2-cycle Lanczos. + RESIZE_HAMMING1, + + // 2-cycle Lanczos filter. This is tall in the middle, goes negative on + // each side, then returns to zero. Does not provide as good a frequency + // response as a 3-cycle Lanczos but is roughly 30% faster. + RESIZE_LANCZOS2, + + // 3-cycle Lanczos filter. This is tall in the middle, goes negative on + // each side, then oscillates 2 more times. It gives nice sharp edges. + RESIZE_LANCZOS3, + + // Lanczos filter + subpixel interpolation. If subpixel rendering is not + // appropriate we automatically fall back to Lanczos. + RESIZE_SUBPIXEL, + + // enum aliases for first and last methods by algorithm or by quality. + RESIZE_FIRST_QUALITY_METHOD = RESIZE_GOOD, + RESIZE_LAST_QUALITY_METHOD = RESIZE_BEST, + RESIZE_FIRST_ALGORITHM_METHOD = RESIZE_BOX, + RESIZE_LAST_ALGORITHM_METHOD = RESIZE_SUBPIXEL, + }; + + // Resizes the given source bitmap using the specified resize method, so that + // the entire image is (dest_size) big. The dest_subset is the rectangle in + // this destination image that should actually be returned. + // + // The output image will be (dest_subset.width(), dest_subset.height()). This + // will save work if you do not need the entire bitmap. + // + // The destination subset must be smaller than the destination image. + static SkBitmap Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + void* dest_pixels = nullptr); + + // Alternate version for resizing and returning the entire bitmap rather than + // a subset. + static SkBitmap Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + void* dest_pixels = nullptr); + + private: + ImageOperations(); // Class for scoping only. + + // Supports all methods except RESIZE_SUBPIXEL. + static SkBitmap ResizeBasic(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + void* dest_pixels = nullptr); + + // Subpixel renderer. + static SkBitmap ResizeSubpixel(const SkBitmap& source, + int dest_width, int dest_height, + const SkIRect& dest_subset); +}; + +// Returns the ceiling/floor as an integer. +inline int CeilInt(float val) { + return static_cast<int>(ceil(val)); +} +inline int FloorInt(float val) { + return static_cast<int>(floor(val)); +} + +// Filter function computation ------------------------------------------------- + +// Evaluates the box filter, which goes from -0.5 to +0.5. +inline float EvalBox(float x) { + return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f; +} + +// Evaluates the Lanczos filter of the given filter size window for the given +// position. +// +// |filter_size| is the width of the filter (the "window"), outside of which +// the value of the function is 0. Inside of the window, the value is the +// normalized sinc function: +// lanczos(x) = sinc(x) * sinc(x / filter_size); +// where +// sinc(x) = sin(pi*x) / (pi*x); +inline float EvalLanczos(int filter_size, float x) { + if (x <= -filter_size || x >= filter_size) + return 0.0f; // Outside of the window. + if (x > -std::numeric_limits<float>::epsilon() && + x < std::numeric_limits<float>::epsilon()) + return 1.0f; // Special case the discontinuity at the origin. + float xpi = x * static_cast<float>(M_PI); + return (sinf(xpi) / xpi) * // sinc(x) + sinf(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size) +} + +// Evaluates the Hamming filter of the given filter size window for the given +// position. +// +// The filter covers [-filter_size, +filter_size]. Outside of this window +// the value of the function is 0. Inside of the window, the value is sinus +// cardinal multiplied by a recentered Hamming function. The traditional +// Hamming formula for a window of size N and n ranging in [0, N-1] is: +// hamming(n) = 0.54 - 0.46 * cos(2 * pi * n / (N-1))) +// In our case we want the function centered for x == 0 and at its minimum +// on both ends of the window (x == +/- filter_size), hence the adjusted +// formula: +// hamming(x) = (0.54 - +// 0.46 * cos(2 * pi * (x - filter_size)/ (2 * filter_size))) +// = 0.54 - 0.46 * cos(pi * x / filter_size - pi) +// = 0.54 + 0.46 * cos(pi * x / filter_size) +inline float EvalHamming(int filter_size, float x) { + if (x <= -filter_size || x >= filter_size) + return 0.0f; // Outside of the window. + if (x > -std::numeric_limits<float>::epsilon() && + x < std::numeric_limits<float>::epsilon()) + return 1.0f; // Special case the sinc discontinuity at the origin. + const float xpi = x * static_cast<float>(M_PI); + + return ((sinf(xpi) / xpi) * // sinc(x) + (0.54f + 0.46f * cosf(xpi / filter_size))); // hamming(x) +} + +// ResizeFilter ---------------------------------------------------------------- + +// Encapsulates computation and storage of the filters required for one complete +// resize operation. + +namespace resize { + + // Returns the number of pixels that the filer spans, in filter space (the + // destination image). + inline float GetFilterSupport(ImageOperations::ResizeMethod method, + float scale) { + switch (method) { + case ImageOperations::RESIZE_BOX: + // The box filter just scales with the image scaling. + return 0.5f; // Only want one side of the filter = /2. + case ImageOperations::RESIZE_HAMMING1: + // The Hamming filter takes as much space in the source image in + // each direction as the size of the window = 1 for Hamming1. + return 1.0f; + case ImageOperations::RESIZE_LANCZOS2: + // The Lanczos filter takes as much space in the source image in + // each direction as the size of the window = 2 for Lanczos2. + return 2.0f; + case ImageOperations::RESIZE_LANCZOS3: + // The Lanczos filter takes as much space in the source image in + // each direction as the size of the window = 3 for Lanczos3. + return 3.0f; + default: + return 1.0f; + } + } + + // Computes one set of filters either horizontally or vertically. The caller + // will specify the "min" and "max" rather than the bottom/top and + // right/bottom so that the same code can be re-used in each dimension. + // + // |src_depend_lo| and |src_depend_size| gives the range for the source + // depend rectangle (horizontally or vertically at the caller's discretion + // -- see above for what this means). + // + // Likewise, the range of destination values to compute and the scale factor + // for the transform is also specified. + void ComputeFilters(ImageOperations::ResizeMethod method, + int src_size, int dst_size, + int dest_subset_lo, int dest_subset_size, + ConvolutionFilter1D* output); + + // Computes the filter value given the coordinate in filter space. + inline float ComputeFilter(ImageOperations::ResizeMethod method, float pos) { + switch (method) { + case ImageOperations::RESIZE_BOX: + return EvalBox(pos); + case ImageOperations::RESIZE_HAMMING1: + return EvalHamming(1, pos); + case ImageOperations::RESIZE_LANCZOS2: + return EvalLanczos(2, pos); + case ImageOperations::RESIZE_LANCZOS3: + return EvalLanczos(3, pos); + default: + return 0; + } + } +} + +} // namespace skia + +#endif // SKIA_EXT_IMAGE_OPERATIONS_H_ diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build new file mode 100644 index 000000000..ad095503d --- /dev/null +++ b/gfx/2d/moz.build @@ -0,0 +1,230 @@ +# -*- 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', + 'DrawTargetTiled.h', + 'Filters.h', + 'Helpers.h', + 'HelpersCairo.h', + 'IterableArena.h', + 'JobScheduler.h', + 'JobScheduler_posix.h', + 'JobScheduler_win32.h', + 'Logging.h', + 'LoggingConstants.h', + 'Matrix.h', + 'MatrixFwd.h', + 'NumericTools.h', + 'PathHelpers.h', + 'PatternHelpers.h', + 'Point.h', + 'Polygon.h', + 'Quaternion.h', + 'RecordedEvent.h', + 'RecordingTypes.h', + 'Rect.h', + 'Scale.h', + 'ScaleFactor.h', + 'ScaleFactors2D.h', + 'SourceSurfaceCairo.h', + 'SourceSurfaceRawData.h', + 'StackArray.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', + ] + UNIFIED_SOURCES += [ + 'NativeFontResourceMac.cpp', + 'PathCG.cpp', + 'ScaledFontMac.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'DrawTargetD2D1.cpp', + 'ExtendInputEffectD2D1.cpp', + 'FilterNodeD2D1.cpp', + 'JobScheduler_win32.cpp', + 'NativeFontResourceDWrite.cpp', + 'NativeFontResourceGDI.cpp', + 'PathD2D.cpp', + 'RadialGradientEffectD2D1.cpp', + 'ScaledFontDWrite.cpp', + 'ScaledFontWin.cpp', + 'SourceSurfaceD2D1.cpp', + ] + DEFINES['WIN32'] = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows': + SOURCES += [ + 'JobScheduler_posix.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'): + SOURCES += [ + 'ScaledFontFontconfig.cpp', + ] + +if CONFIG['MOZ_ENABLE_SKIA']: + UNIFIED_SOURCES += [ + 'convolver.cpp', + ] + SOURCES += [ + 'DrawTargetSkia.cpp', + 'image_operations.cpp', # Uses _USE_MATH_DEFINES + 'PathSkia.cpp', + 'SourceSurfaceSkia.cpp', + ] + if CONFIG['CLANG_CXX']: + # Suppress warnings from Skia header files. + SOURCES['DrawTargetSkia.cpp'].flags += ['-Wno-implicit-fallthrough'] + SOURCES['PathSkia.cpp'].flags += ['-Wno-implicit-fallthrough'] + SOURCES['SourceSurfaceSkia.cpp'].flags += ['-Wno-implicit-fallthrough'] + EXPORTS.mozilla.gfx += [ + 'HelpersSkia.h', + ] + +# Are we targeting x86 or x64? If so, build SSE2 files. +if CONFIG['INTEL_ARCHITECTURE']: + SOURCES += [ + 'BlurSSE2.cpp', + 'FilterProcessingSSE2.cpp', + 'ImageScalingSSE2.cpp', + 'ssse3-scaler.c', + ] + if CONFIG['MOZ_ENABLE_SKIA']: + SOURCES += [ + 'convolverSSE2.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['FilterProcessingSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] + SOURCES['ImageScalingSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] + SOURCES['ssse3-scaler.c'].flags += CONFIG['SSSE3_FLAGS'] + if CONFIG['MOZ_ENABLE_SKIA']: + SOURCES['convolverSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] +elif CONFIG['CPU_ARCH'].startswith('mips'): + SOURCES += [ + 'BlurLS3.cpp', + ] + if CONFIG['MOZ_ENABLE_SKIA']: + SOURCES += [ + 'convolverLS3.cpp', + ] + +UNIFIED_SOURCES += [ + 'BezierUtils.cpp', + 'Blur.cpp', + 'DataSourceSurface.cpp', + 'DataSurfaceHelpers.cpp', + 'DrawEventRecorder.cpp', + 'DrawingJob.cpp', + 'DrawTarget.cpp', + 'DrawTargetCairo.cpp', + 'DrawTargetCapture.cpp', + 'DrawTargetDual.cpp', + 'DrawTargetRecording.cpp', + 'DrawTargetTiled.cpp', + 'FilterNodeSoftware.cpp', + 'FilterProcessing.cpp', + 'FilterProcessingScalar.cpp', + 'ImageScaling.cpp', + 'JobScheduler.cpp', + 'Matrix.cpp', + 'Path.cpp', + 'PathCairo.cpp', + 'PathHelpers.cpp', + 'PathRecording.cpp', + 'Quaternion.cpp', + 'RecordedEvent.cpp', + 'Scale.cpp', + 'ScaledFontBase.cpp', + 'ScaledFontCairo.cpp', + 'SFNTData.cpp', + 'SFNTNameTable.cpp', + 'SourceSurfaceCairo.cpp', + 'SourceSurfaceRawData.cpp', +] + +SOURCES += [ + 'Factory.cpp', # Need to suppress warnings in Skia header files. +] + +if CONFIG['CLANG_CXX']: + SOURCES['Factory.cpp'].flags += ['-Wno-implicit-fallthrough'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + EXPORTS.mozilla.gfx += [ + 'QuartzSupport.h', + ] + SOURCES += [ + 'MacIOSurface.cpp', + 'QuartzSupport.mm', + ] + +if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']: + SOURCES += ['BlurNEON.cpp'] + SOURCES['BlurNEON.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', 'gtk2', 'gtk3'): + DEFINES['MOZ_ENABLE_FREETYPE'] = True + +CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gtk3'): + CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS'] + +LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] + +if CONFIG['MOZ_ENABLE_SKIA']: + LOCAL_INCLUDES += [ + '/gfx/skia/skia/include/private', + '/gfx/skia/skia/src/core', + '/gfx/skia/skia/src/image', + ] +if CONFIG['MOZ_ENABLE_SKIA_GPU']: + LOCAL_INCLUDES += [ + '/gfx/skia/skia/src/gpu', + ] + diff --git a/gfx/2d/ssse3-scaler.c b/gfx/2d/ssse3-scaler.c new file mode 100644 index 000000000..345844b84 --- /dev/null +++ b/gfx/2d/ssse3-scaler.c @@ -0,0 +1,561 @@ +/* + * 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 <mmintrin.h> +#include <xmmintrin.h> +#include <emmintrin.h> +#include <tmmintrin.h> +#include <stdint.h> +#include <assert.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)) +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; + + /* 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 */ +void 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 = ℑ + 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.data) { + for (int iy = 0; iy < height; iy++) { + ssse3_fetch_bilinear_cover(&iter, NULL); + iter.buffer += dest_stride; + } + ssse3_bilinear_cover_iter_fini(&iter); + } +} diff --git a/gfx/2d/ssse3-scaler.h b/gfx/2d/ssse3-scaler.h new file mode 100644 index 000000000..b3b53ed64 --- /dev/null +++ b/gfx/2d/ssse3-scaler.h @@ -0,0 +1,22 @@ +#/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_2D_SSSE3_SCALER_H_ +#define MOZILLA_GFX_2D_SSSE3_SCALER_H_ + +#ifdef __cplusplus +extern "C" { +#endif +void 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/u16string.h b/gfx/2d/u16string.h new file mode 100644 index 000000000..61380ee6b --- /dev/null +++ b/gfx/2d/u16string.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_u16string_h +#define mozilla_gfx_u16string_h + +#include <string> + +#include "mozilla/Char16.h" + +namespace mozilla { + +#if defined(_MSC_VER) +typedef std::u16string u16string; +#else +typedef std::basic_string<char16_t> u16string; +#endif + +} // mozilla + +#endif // mozilla_gfx_u16string_h diff --git a/gfx/2d/unittest/Main.cpp b/gfx/2d/unittest/Main.cpp new file mode 100644 index 000000000..46a1af5b6 --- /dev/null +++ b/gfx/2d/unittest/Main.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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; +}; + + +using namespace std; + +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; + 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 000000000..7f109facd --- /dev/null +++ b/gfx/2d/unittest/SanityChecks.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 000000000..f2a4a0b59 --- /dev/null +++ b/gfx/2d/unittest/SanityChecks.h @@ -0,0 +1,16 @@ +/* -*- 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/. */ + +#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 000000000..5818a7299 --- /dev/null +++ b/gfx/2d/unittest/TestBase.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestBase.h" + +#include <sstream> + +using namespace std; + +int +TestBase::RunTests(int *aFailures) +{ + int testsRun = 0; + *aFailures = 0; + + for(unsigned int i = 0; i < mTests.size(); i++) { + 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(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 000000000..a57d6a730 --- /dev/null +++ b/gfx/2d/unittest/TestBase.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#pragma once + +#include <string> +#include <vector> + +#ifdef _MSC_VER +// On MSVC otherwise our generic member pointer trick doesn't work. +#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() {} + + 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 000000000..f127eed8b --- /dev/null +++ b/gfx/2d/unittest/TestBugs.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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(Color(1,0,0))); + + RefPtr<SourceSurface> surf1 = dt->Snapshot(); + RefPtr<SourceSurface> surf2 = ref->Snapshot(); + + RefPtr<DataSourceSurface> dataSurf1 = surf1->GetDataSurface(); + RefPtr<DataSourceSurface> dataSurf2 = surf2->GetDataSurface(); + + for (int y = 0; y < dt->GetSize().height; y++) { + VERIFY(memcmp(dataSurf1->GetData() + y * dataSurf1->Stride(), + dataSurf2->GetData() + y * dataSurf2->Stride(), + 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(Color(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 000000000..0c715df44 --- /dev/null +++ b/gfx/2d/unittest/TestBugs.h @@ -0,0 +1,18 @@ +/* -*- 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/. */ + +#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 000000000..7df604695 --- /dev/null +++ b/gfx/2d/unittest/TestCairo.cpp @@ -0,0 +1,96 @@ +/* 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 { + +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 000000000..2a0d95ed6 --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetBase.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestDrawTargetBase.h" +#include <sstream> + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace std; + +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(Color(0, 0.5f, 0, 1.0f))); + + RefreshSnapshot(); + + VerifyAllPixels(Color(0, 0.5f, 0, 1.0f)); +} + +void +TestDrawTargetBase::FillRect() +{ + mDT->FillRect(Rect(0, 0, DT_WIDTH, DT_HEIGHT), ColorPattern(Color(0, 0.5f, 0, 1.0f))); + mDT->FillRect(Rect(50, 50, 50, 50), ColorPattern(Color(0.5f, 0, 0, 1.0f))); + + RefreshSnapshot(); + + VerifyPixel(IntPoint(49, 49), Color(0, 0.5f, 0, 1.0f)); + VerifyPixel(IntPoint(50, 50), Color(0.5f, 0, 0, 1.0f)); + VerifyPixel(IntPoint(99, 99), Color(0.5f, 0, 0, 1.0f)); + VerifyPixel(IntPoint(100, 100), Color(0, 0.5f, 0, 1.0f)); +} + +void +TestDrawTargetBase::RefreshSnapshot() +{ + RefPtr<SourceSurface> snapshot = mDT->Snapshot(); + mDataSnapshot = snapshot->GetDataSurface(); +} + +void +TestDrawTargetBase::VerifyAllPixels(const Color &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::Color &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 Color &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 000000000..06a62413f --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetBase.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +#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::Color &aColor); + void VerifyPixel(const mozilla::gfx::IntPoint &aPoint, + mozilla::gfx::Color &aColor); + + uint32_t RGBAPixelFromColor(const mozilla::gfx::Color &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 000000000..0715f93d8 --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetD2D.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 000000000..97bb88cf2 --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetD2D.h @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +#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 000000000..6aa2b6a35 --- /dev/null +++ b/gfx/2d/unittest/TestPoint.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestPoint.h" + +#include "Point.h" + +using namespace mozilla::gfx; + +TestPoint::TestPoint() +{ + REGISTER_TEST(TestPoint, Addition); + REGISTER_TEST(TestPoint, Subtraction); +} + +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); +} diff --git a/gfx/2d/unittest/TestPoint.h b/gfx/2d/unittest/TestPoint.h new file mode 100644 index 000000000..813d66c2f --- /dev/null +++ b/gfx/2d/unittest/TestPoint.h @@ -0,0 +1,17 @@ +/* -*- 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/. */ + +#pragma once + +#include "TestBase.h" + +class TestPoint : public TestBase +{ +public: + TestPoint(); + + void Addition(); + void Subtraction(); +}; diff --git a/gfx/2d/unittest/TestScaling.cpp b/gfx/2d/unittest/TestScaling.cpp new file mode 100644 index 000000000..60e1acbf0 --- /dev/null +++ b/gfx/2d/unittest/TestScaling.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 000000000..e9bd1a8e0 --- /dev/null +++ b/gfx/2d/unittest/TestScaling.h @@ -0,0 +1,22 @@ +/* -*- 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/. */ + +#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 000000000..7ddf92530 --- /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 |